dsh-vibe-math 2.7.2 → 2.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +11 -10
- package/README.md +13 -12
- package/cordis.patch.yml +112 -16
- package/docs/AUDIT-CHECKLIST.md +229 -3
- package/docs/COMPAT-AUDIT-ROUND2.md +35 -9
- package/docs/final-paper.md +2 -2
- package/docs/math-computation.md +13 -6
- package/docs/parameter-schema.md +37 -0
- package/docs/release-notes/README.en.md +1 -0
- package/docs/release-notes/README.md +1 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +2 -2
- package/docs/release-notes/RELEASE-NOTES-2.7.2.md +3 -3
- package/docs/release-notes/RELEASE-NOTES-2.7.3.en.md +67 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.3.md +67 -0
- package/docs/release-notes/TEMPLATE.md +6 -0
- package/docs/status-report-fields.md +167 -0
- package/docs/test-timing.md +8 -5
- package/installer.js +27 -17
- package/package.json +250 -203
- package/prompt-corpus-persona/persona-corpus.json +9 -9
- package/prompt-corpus-persona/persona-corpus.md +225 -33
- package/prompt-corpus-v2/formal-verify-v2.json +295 -115
- package/prompt-corpus-v2/formal-verify-v2.md +2287 -283
- package/prompt-corpus-v3/formal-verify-v3.json +105 -105
- package/prompt-corpus-v3/formal-verify-v3.md +213 -143
- package/prompt-corpus-v4/formal-verify-v4.json +18 -18
- package/prompt-corpus-v4/formal-verify-v4.md +49 -44
- package/prompt-corpus-v5/prompt-corpus-v5.json +82 -82
- package/prompt-corpus-v5/prompt-corpus-v5.md +451 -207
- package/scripts/update-doc-counts.mjs +71 -0
- package/tests/audit-artifact-docs.mjs +72 -0
- package/tests/audit-artifact-docs.mutants.mjs +58 -0
- package/tests/audit-installer-assertions.mutants.mjs +63 -0
- package/tests/audit-installer-compat.test.mjs +52 -4
- package/tests/audit-installer-policy.test.mjs +41 -8
- package/tests/audit-math-computation-contract.mjs +64 -8
- package/tests/audit-math-computation-contract.mutants.mjs +106 -0
- package/tests/audit-math-computation-parity.mjs +164 -3
- package/tests/audit-math-computation-sensitivity.mjs +4 -2
- package/tests/audit-package-membership.mjs +57 -0
- package/tests/audit-package-membership.mutants.mjs +28 -0
- package/tests/audit-participant-set-parity.mjs +160 -0
- package/tests/audit-path-discipline.mjs +83 -0
- package/tests/audit-path-discipline.mutants.mjs +51 -0
- package/tests/audit-persona-sensitivity.mjs +14 -0
- package/tests/audit-persona-surface.mutants.mjs +69 -0
- package/tests/audit-persona-surface.test.mjs +63 -22
- package/tests/audit-prompt-duplication.mjs +143 -0
- package/tests/audit-prompt-duplication.mutants.mjs +50 -0
- package/tests/audit-prompt-invariants.mjs +43 -5
- package/tests/audit-readme-bilingual.test.mjs +152 -0
- package/tests/audit-readme-counts.mjs +95 -0
- package/tests/audit-readme-counts.mutants.mjs +52 -0
- package/tests/audit-registration.mjs +78 -0
- package/tests/audit-status-report-fields.mjs +172 -0
- package/tests/audit-status-report-fields.mutants.mjs +64 -0
- package/tests/audit-v3-registration-parity.mjs +156 -0
- package/tests/audit-v5-integrity.mjs +300 -13
- package/tests/audit-v5-lean-abstention.mjs +392 -0
- package/tests/audit-v5-lean-abstention.mutants.mjs +106 -0
- package/tests/audit-v5-prompt-duplication.mjs +88 -0
- package/tests/audit-v5-prompt-duplication.mutants.mjs +34 -0
- package/tests/audit-v5-sensitivity.mjs +12 -7
- package/tests/e2e-identity-a6.mutants.mjs +65 -0
- package/tests/e2e-v5-round2.test.mjs +458 -13
- package/tests/formal-verify-v2.test.mjs +91 -43
- package/tests/formal-verify-v3.mutants.mjs +117 -0
- package/tests/formal-verify-v3.test.mjs +85 -39
- package/tests/formal-verify-v4.mutants.mjs +152 -0
- package/tests/formal-verify-v4.test.mjs +541 -9
- package/tests/formal-verify-v5.test.mjs +68 -9
- package/tests/helpers/doc-counts-format.mjs +41 -0
- package/tests/helpers/math-computation-fake-seam.mjs +4 -0
- package/tests/math-computation-a1.mutants.mjs +72 -0
- package/tests/math-computation-archive-rerun.mutants.mjs +65 -0
- package/tests/math-computation-discovery.mutants.mjs +76 -0
- package/tests/math-computation-shared.test.mjs +274 -5
- package/tests/math-computation-v2.test.mjs +767 -0
- package/tests/math-computation-v3.test.mjs +759 -0
- package/tests/math-computation-v4.mutants.mjs +133 -0
- package/tests/math-computation-v4.test.mjs +739 -0
- package/tests/prompt-v5-integrity.test.mjs +13 -2
- package/tests/run-tests.mjs +139 -9
- package/tests/run-tests.mutants.mjs +70 -0
- package/tests/selfdrive-v5.mjs +53 -1
- package/tests/selfdrive-v5.mutants.mjs +132 -0
- package/tests/v2-fix-probes.mutants.mjs +100 -0
- package/tests/v2-fix-probes.test.mjs +1039 -0
- package/tests/v2-list-agents-and-next-step.mutants.mjs +106 -0
- package/tests/v3-fix-probes.mutants.mjs +95 -0
- package/tests/v3-fix-probes.test.mjs +891 -0
- package/tests/v5-institute-fixes.mutants.mjs +190 -0
- package/vibe-math-v2/agent.cordis.yml +62 -6
- package/vibe-math-v2/math-computation.js +130 -41
- package/vibe-math-v2/math-engines.js +12 -12
- package/vibe-math-v2/vibe-math-v2.js +388 -113
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +5 -1
- package/vibe-math-v3/agent.cordis.yml +72 -6
- package/vibe-math-v3/math-computation.js +130 -41
- package/vibe-math-v3/math-engines.js +12 -12
- package/vibe-math-v3/vibe-math-v3.js +422 -126
- package/vibe-math-v4/agent.cordis.yml +44 -8
- package/vibe-math-v4/math-computation.js +130 -41
- package/vibe-math-v4/math-engines.js +12 -12
- package/vibe-math-v4/vibe-math-v4.js +196 -43
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +2 -0
- package/vibe-math-v5/agent.cordis.yml +46 -12
- package/vibe-math-v5/math-computation.js +130 -41
- package/vibe-math-v5/math-engines.js +12 -12
- package/vibe-math-v5/vibe-math-v5.js +1173 -218
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +80 -12
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +2 -2
|
@@ -484,4 +484,8 @@ v2 **没有会话投影**,所以记录与待办一起持久化在 v2 自己的
|
|
|
484
484
|
`require` 档**必须**出现(那里门禁真的会搁置裁定)。
|
|
485
485
|
- `kind='def'/'lemma'` 运行未通过时,工具返回值**不得**声称"可直接 import 复用"。
|
|
486
486
|
|
|
487
|
-
套件支持 `V2_PLUGIN` 环境变量指向变异副本,供灵敏度探针使用。
|
|
487
|
+
套件支持 `V2_PLUGIN` 环境变量指向变异副本,供灵敏度探针使用。
|
|
488
|
+
|
|
489
|
+
## 状态 / 报告字段:`verifyTasksView`(单一来源)
|
|
490
|
+
|
|
491
|
+
v2 的 **status 与 report 两个视图**里出现的验证任务列表由**同一个** helper `verifyTasksView(tasks)` 产出(`vibe-math-v2.js:2995`;调用点 `:2046` 与 `:4039`),因此两处字段必须**逐个字段一致**;任何"在某一侧顺手补一个字段"的改动都会让两个视图分叉。该一致性由 `tests/formal-verify-v2.test.mjs:1694-1698` 钉住(两侧取自同一 helper 且字段集合相等)。
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
- vibe_math_index — rebuild State/index.json from the Markdown knowledge base.
|
|
57
57
|
- vibe_math_method_add / vibe_math_method_list — manually add / list method cards (Methods/ + global).
|
|
58
58
|
- vibe_math_lock_status — project lock occupancy (the lock is a renewed lease: while a session is running it never expires; taking it over needs override=true, or happens automatically once the owner's lease has clearly expired after a crash).
|
|
59
|
+
- vibe_math_claim_write / vibe_math_release_write — (member) 写共用 Markdown(含 Progress/<qid>/<dirId>.md)前先取该文件的写锁、写完立刻释放;锁被别的成员持有时报出持有者,不要硬写。
|
|
59
60
|
Only ONE session may run a project: a second session is refused with PROJECT_LOCKED instead of silently writing the same knowledge base in parallel.
|
|
60
61
|
- vibe_math_new_project / vibe_math_set_project / vibe_math_list_projects — per-project folders.
|
|
61
62
|
- vibe_math_list_decisions / vibe_math_decide {id, action: approve|reject|override, verdict?} — resolve manual decisions.
|
|
@@ -69,7 +70,7 @@
|
|
|
69
70
|
|
|
70
71
|
- math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
|
|
71
72
|
- 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
|
|
72
|
-
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd
|
|
73
|
+
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:'file' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true` 与 `previousReceipt`(指向上一次)。**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
|
|
73
74
|
|
|
74
75
|
A /vibe slash command mirrors the main controls (/vibe start [override]|resume [override]|pause|abort|status|report|mode
|
|
75
76
|
<auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|methods|index|plan|lock|project [list|new <name>|<name>]|decisions|agents|paper [lang=zh|en] [format=both|md|tex] [force]).
|
|
@@ -95,8 +96,15 @@
|
|
|
95
96
|
match the proposition as stated) | 'require' (same, plus a gate: a true/false verdict is
|
|
96
97
|
withheld as 未定论 with reason formal-required until the object is Lean-passed or carries an
|
|
97
98
|
explicit, reasoned blocker record; the scheduler is never wedged by it).
|
|
98
|
-
- Paths
|
|
99
|
-
|
|
99
|
+
- Paths (every path here is resolved against the SESSION WORKING DIRECTORY, i.e. the cwd
|
|
100
|
+
printed at the end of this prompt; the project root is the `Data lives under …`
|
|
101
|
+
directory above):
|
|
102
|
+
institute work file `<project root>/Formal/<id>.lean`;
|
|
103
|
+
archived proof `<project root>/Verified/Lean/<id>.lean`;
|
|
104
|
+
reusable definitions `VibeMath/Formal/Lib/` (workspace-root relative);
|
|
105
|
+
proved lemmas `VibeMath/Formal/Proved/` (workspace-root relative);
|
|
106
|
+
short form `Formal/<id>.lean` (project-root relative) names the SAME file as
|
|
107
|
+
`<project root>/Formal/<id>.lean`.
|
|
100
108
|
- The toolchain knobs leanCommand / leanArgs / leanTimeoutMs / leanAsync / leanInitiative / leanSearchPaths / leanJobsMaxParallel are tunable as well
|
|
101
109
|
(e.g. leanCommand='lake' with leanArgs=['env','lean']); a missing Lean binary is
|
|
102
110
|
reported as LEAN_NOT_FOUND and still lets the code be written and archived.
|
|
@@ -104,6 +112,31 @@
|
|
|
104
112
|
formalization TODO (Formal/TODO.md). The framework never installs Lean and never judges
|
|
105
113
|
fidelity for you.
|
|
106
114
|
When the user asks about progress, call vibe_math_report and summarize in plain language.
|
|
115
|
+
|
|
116
|
+
DISCLOSURES (existing behaviour, not rules to follow):
|
|
117
|
+
- DISCLOSURE (existing behaviour): the FINAL PAPER flow can finish without you — when it hits
|
|
118
|
+
its round/token cap the framework force-finalises the paper (`forcedAfterCap`) and reaps an
|
|
119
|
+
in-flight writer that never came back; the activity log says so. You do not lose the paper,
|
|
120
|
+
you lose the chance to keep iterating on it.
|
|
121
|
+
- DISCLOSURE (existing behaviour): switching the mode to `auto` immediately resolves EVERY
|
|
122
|
+
pending decision as auto-decided (the activity log records "auto-resolved N pending
|
|
123
|
+
decision(s)"). Anything you leave pending will not wait for you.
|
|
124
|
+
- DISCLOSURE (existing behaviour): the framework itself pushes a progress update to the office
|
|
125
|
+
(reportMode / reportIntervalMs) — a summary you did not ask for can arrive at a round
|
|
126
|
+
boundary; it is a report, not a new instruction.
|
|
127
|
+
- DISCLOSURE (existing behaviour): a second session can TAKE OVER the project lock
|
|
128
|
+
(the activity log records "project lock taken over from session …") — another office may
|
|
129
|
+
resume the same project while you are away; the state file stays authoritative and is not
|
|
130
|
+
merged.
|
|
131
|
+
- DISCLOSURE (existing behaviour): the framework can adjust problem/proposition priorities
|
|
132
|
+
itself (priorityAdjust / proposPriorityAdjust) — the activity log records "priorities
|
|
133
|
+
auto-adjusted (…)"; a problem you deprioritised can come back on its own.
|
|
134
|
+
- DISCLOSURE (existing behaviour): when mathMode is `typed`, the shell fallback is refused
|
|
135
|
+
(the math tool returns REFUSED with reason=policy) instead of silently falling back.
|
|
136
|
+
- math_computation policy: mathComputation = off | auto | on (off = the math surface is
|
|
137
|
+
not mentioned in prompts at all); mathMode = typed | typed+shell (typed refuses the shell
|
|
138
|
+
fallback); mathEngines (cli / python / …); mathTimeoutMs; mathPackages + mathInstallScope
|
|
139
|
+
for preflight package installs (missing packages are reported with an install plan only).
|
|
107
140
|
suffix: Your working directory is {{cwd}}.
|
|
108
141
|
text: |-
|
|
109
142
|
You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.
|
|
@@ -148,6 +181,7 @@
|
|
|
148
181
|
- vibe_math_index — rebuild State/index.json from the Markdown knowledge base.
|
|
149
182
|
- vibe_math_method_add / vibe_math_method_list — manually add / list method cards (Methods/ + global).
|
|
150
183
|
- vibe_math_lock_status — project lock occupancy (the lock is a renewed lease: while a session is running it never expires; taking it over needs override=true, or happens automatically once the owner's lease has clearly expired after a crash).
|
|
184
|
+
- vibe_math_claim_write / vibe_math_release_write — (member) 写共用 Markdown(含 Progress/<qid>/<dirId>.md)前先取该文件的写锁、写完立刻释放;锁被别的成员持有时报出持有者,不要硬写。
|
|
151
185
|
Only ONE session may run a project: a second session is refused with PROJECT_LOCKED instead of silently writing the same knowledge base in parallel.
|
|
152
186
|
- vibe_math_new_project / vibe_math_set_project / vibe_math_list_projects — per-project folders.
|
|
153
187
|
- vibe_math_list_decisions / vibe_math_decide {id, action: approve|reject|override, verdict?} — resolve manual decisions.
|
|
@@ -161,7 +195,7 @@
|
|
|
161
195
|
|
|
162
196
|
- math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
|
|
163
197
|
- 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
|
|
164
|
-
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd
|
|
198
|
+
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:'file' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true` 与 `previousReceipt`(指向上一次)。**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
|
|
165
199
|
|
|
166
200
|
A /vibe slash command mirrors the main controls (/vibe start [override]|resume [override]|pause|abort|status|report|mode
|
|
167
201
|
<auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|methods|index|plan|lock|project [list|new <name>|<name>]|decisions|agents|paper [lang=zh|en] [format=both|md|tex] [force]).
|
|
@@ -187,8 +221,15 @@
|
|
|
187
221
|
match the proposition as stated) | 'require' (same, plus a gate: a true/false verdict is
|
|
188
222
|
withheld as 未定论 with reason formal-required until the object is Lean-passed or carries an
|
|
189
223
|
explicit, reasoned blocker record; the scheduler is never wedged by it).
|
|
190
|
-
- Paths
|
|
191
|
-
|
|
224
|
+
- Paths (every path here is resolved against the SESSION WORKING DIRECTORY, i.e. the cwd
|
|
225
|
+
printed at the end of this prompt; the project root is the `Data lives under …`
|
|
226
|
+
directory above):
|
|
227
|
+
institute work file `<project root>/Formal/<id>.lean`;
|
|
228
|
+
archived proof `<project root>/Verified/Lean/<id>.lean`;
|
|
229
|
+
reusable definitions `VibeMath/Formal/Lib/` (workspace-root relative);
|
|
230
|
+
proved lemmas `VibeMath/Formal/Proved/` (workspace-root relative);
|
|
231
|
+
short form `Formal/<id>.lean` (project-root relative) names the SAME file as
|
|
232
|
+
`<project root>/Formal/<id>.lean`.
|
|
192
233
|
- The toolchain knobs leanCommand / leanArgs / leanTimeoutMs / leanAsync / leanInitiative / leanSearchPaths / leanJobsMaxParallel are tunable as well
|
|
193
234
|
(e.g. leanCommand='lake' with leanArgs=['env','lean']); a missing Lean binary is
|
|
194
235
|
reported as LEAN_NOT_FOUND and still lets the code be written and archived.
|
|
@@ -197,6 +238,31 @@
|
|
|
197
238
|
fidelity for you.
|
|
198
239
|
When the user asks about progress, call vibe_math_report and summarize in plain language.
|
|
199
240
|
|
|
241
|
+
DISCLOSURES (existing behaviour, not rules to follow):
|
|
242
|
+
- DISCLOSURE (existing behaviour): the FINAL PAPER flow can finish without you — when it hits
|
|
243
|
+
its round/token cap the framework force-finalises the paper (`forcedAfterCap`) and reaps an
|
|
244
|
+
in-flight writer that never came back; the activity log says so. You do not lose the paper,
|
|
245
|
+
you lose the chance to keep iterating on it.
|
|
246
|
+
- DISCLOSURE (existing behaviour): switching the mode to `auto` immediately resolves EVERY
|
|
247
|
+
pending decision as auto-decided (the activity log records "auto-resolved N pending
|
|
248
|
+
decision(s)"). Anything you leave pending will not wait for you.
|
|
249
|
+
- DISCLOSURE (existing behaviour): the framework itself pushes a progress update to the office
|
|
250
|
+
(reportMode / reportIntervalMs) — a summary you did not ask for can arrive at a round
|
|
251
|
+
boundary; it is a report, not a new instruction.
|
|
252
|
+
- DISCLOSURE (existing behaviour): a second session can TAKE OVER the project lock
|
|
253
|
+
(the activity log records "project lock taken over from session …") — another office may
|
|
254
|
+
resume the same project while you are away; the state file stays authoritative and is not
|
|
255
|
+
merged.
|
|
256
|
+
- DISCLOSURE (existing behaviour): the framework can adjust problem/proposition priorities
|
|
257
|
+
itself (priorityAdjust / proposPriorityAdjust) — the activity log records "priorities
|
|
258
|
+
auto-adjusted (…)"; a problem you deprioritised can come back on its own.
|
|
259
|
+
- DISCLOSURE (existing behaviour): when mathMode is `typed`, the shell fallback is refused
|
|
260
|
+
(the math tool returns REFUSED with reason=policy) instead of silently falling back.
|
|
261
|
+
- math_computation policy: mathComputation = off | auto | on (off = the math surface is
|
|
262
|
+
not mentioned in prompts at all); mathMode = typed | typed+shell (typed refuses the shell
|
|
263
|
+
fallback); mathEngines (cli / python / …); mathTimeoutMs; mathPackages + mathInstallScope
|
|
264
|
+
for preflight package installs (missing packages are reported with an install plan only).
|
|
265
|
+
|
|
200
266
|
# ── agent-instructions ──────────────────────────────────────────────────────
|
|
201
267
|
|
|
202
268
|
- id: agent-instructions
|
|
@@ -12,6 +12,21 @@ import { MATH_ENGINES, MATH_ENGINE_ORDER, mathEngineCandidates, mathEngineArgErr
|
|
|
12
12
|
|
|
13
13
|
export const MATH_TOOL_NAME = 'math_computation'
|
|
14
14
|
|
|
15
|
+
// Sweep ruling: the cli policy is declared ONCE, in the descriptor (math-engines.js `cli.policy`).
|
|
16
|
+
// These two readers are the only place the rule is applied, so descriptor and behaviour cannot drift.
|
|
17
|
+
const CLI_POLICY = (MATH_ENGINES.cli && MATH_ENGINES.cli.policy) || {}
|
|
18
|
+
|
|
19
|
+
// Item-2 ruling: a commercial template's "VERIFY" provenance must REACH the user, and it is declared
|
|
20
|
+
// ONCE (descriptor `verify` + `verifyReason`). Assemble the fields ONLY when a reason exists, so an
|
|
21
|
+
// engine that declares nothing prints no empty slot.
|
|
22
|
+
function verifyFields(engine) {
|
|
23
|
+
const d = MATH_ENGINES[engine] || {}
|
|
24
|
+
if (!d.verify) return {}
|
|
25
|
+
const out = { verify: true }
|
|
26
|
+
if (typeof d.verifyReason === 'string' && d.verifyReason) out.verifyReason = d.verifyReason
|
|
27
|
+
return out
|
|
28
|
+
}
|
|
29
|
+
|
|
15
30
|
export const MATH_PARAM_NAMES = Object.freeze([
|
|
16
31
|
'mathComputation', 'mathMode', 'mathEngines', 'mathTimeoutMs', 'mathPackages', 'mathInstallScope',
|
|
17
32
|
])
|
|
@@ -73,11 +88,12 @@ export const MATH_PERSONA_TOOL_LINE = '- math_computation {op: probe|run|receipt
|
|
|
73
88
|
// to both persona blocks) so an agent cannot miss that (a) the script is archived, (b) it may edit
|
|
74
89
|
// it, and (c) re-running is what produces evidence for the edited code.
|
|
75
90
|
export const MATH_ARCHIVE_WORKFLOW_LINE = '- 归档→编辑→重跑:mode:\'code\' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);'
|
|
76
|
-
+ '**成员的文件工具是按会话 cwd
|
|
77
|
-
+ '
|
|
78
|
-
+ '
|
|
91
|
+
+ '**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。'
|
|
92
|
+
+ '要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:\'file\' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true` 与 `previousReceipt`(指向上一次)。'
|
|
93
|
+
+ '**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。'
|
|
94
|
+
+ '**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。'
|
|
79
95
|
|
|
80
|
-
export const MATH_ARCHIVE_WORKFLOW_LINE_EN = '- Archive -> edit -> re-run: for mode:\'code\' the script original is at the receipt\'s scriptPath (Computation/<id>/script.<ext>, **relative to the project root**); member file tools resolve paths against the SESSION CWD, so
|
|
96
|
+
export const MATH_ARCHIVE_WORKFLOW_LINE_EN = '- Archive -> edit -> re-run: for mode:\'code\' the script original is at the receipt\'s scriptPath (Computation/<id>/script.<ext>, **relative to the project root**); member file tools resolve paths against the SESSION CWD, so READ it via the ABSOLUTE `receipt.scriptAbs`, or join `receipt.cwd` with `receipt.scriptPath` (both are in the receipt). To produce evidence for EDITED code, edit the SOURCE FILE you originally ran and re-run mode:\'file\' pointing at THAT SAME PATH: the archive id is keyed by the SOURCE PATH, so this lands on the SAME archive as attempt >= 2, with `scriptChanged:true` and `previousReceipt` pointing at the previous attempt. Pointing mode:\'file\' at the ARCHIVED COPY itself (the `receipt.scriptAbs` path) is a DIFFERENT archive BY DESIGN - a new id, attempt 1, no `previousReceipt`, `scriptChanged:false`: the earlier attempt is never overwritten, but it is NOT "a new attempt of the same archive", and the tool says so via `fileIsArchivedScript` and the `ARCHIVED_SCRIPT_RERUN` warning. **An old receipt is NOT evidence for edited code** - cite the receipt whose scriptHash matches the current code; the tool warns explicitly via scriptChanged / scriptChangedDuringRun.'
|
|
81
97
|
|
|
82
98
|
// Round-6 (A): honesty about SUBSTITUTIONS. An alternative that weakens exactness or conclusion
|
|
83
99
|
// strength must be declared, and the conclusion must never read as if the requested (exact) result
|
|
@@ -255,8 +271,10 @@ export function validateMathArgs(args, params) {
|
|
|
255
271
|
if (mode !== 'code' && mode !== 'file' && mode !== 'expr') return bad('mode must be code|file|expr for op=run')
|
|
256
272
|
const engine = args.engine || 'auto'
|
|
257
273
|
if (engine === 'cli') {
|
|
258
|
-
|
|
259
|
-
|
|
274
|
+
// Sweep ruling (single source): the cli policy lives in the DESCRIPTOR; the code reads it here
|
|
275
|
+
// rather than hard-coding 'typed+shell', so the two can no longer drift apart.
|
|
276
|
+
if (CLI_POLICY.requiresMathMode && p.mathMode !== CLI_POLICY.requiresMathMode) return bad('engine=cli is disabled while mathMode=' + p.mathMode + ' (requires ' + CLI_POLICY.requiresMathMode + ')', 'MATH_REFUSED', next('reason', { reason: 'policy', requiresMathMode: CLI_POLICY.requiresMathMode }))
|
|
277
|
+
if (CLI_POLICY.requiresEngineInList && p.mathEngines.indexOf('cli') === -1) return bad('engine=cli is not in mathEngines', 'MATH_REFUSED', next('reason', { reason: 'engine-not-allowed', requiresEngineInList: true }))
|
|
260
278
|
if (!isObj(args.cli)) return bad('engine=cli requires cli:{command,argv}', 'MATH_REFUSED', next('reason', { reason: 'missing-cli-command' }))
|
|
261
279
|
if (mode === 'expr') return bad('cli does not support mode=expr', 'MATH_REFUSED', next('reason', { reason: 'expr-not-supported' }))
|
|
262
280
|
}
|
|
@@ -277,6 +295,19 @@ export function validateMathArgs(args, params) {
|
|
|
277
295
|
|
|
278
296
|
// ── failure / return shells ─────────────────────────────────────────────────────────────────────
|
|
279
297
|
function next(kind, extra) { return Object.assign({ kind: kind }, extra || {}) }
|
|
298
|
+
/**
|
|
299
|
+
* S6: ONE wording for the version policy, so the install plan, the run path and the missing-package
|
|
300
|
+
* failure cannot drift into different sentences describing the same rule. Grep anchor:
|
|
301
|
+
* MATH_VERSION_POLICY (and its single call site family mathVersionPolicy).
|
|
302
|
+
*/
|
|
303
|
+
/** S6: the ONE short clause that states the policy; the long sentence and the two human messages
|
|
304
|
+
* below are all built from it, so the semantics cannot drift. Grep anchor: MATH_VERSION_POLICY_CLAUSE. */
|
|
305
|
+
const MATH_VERSION_POLICY_CLAUSE = '只检查是否存在;版本求解交给包管理器'
|
|
306
|
+
const MATH_VERSION_POLICY = 'version-constraints-are-existence-only: 只按 base name 检查是否存在,约束本身从不校验(' + MATH_VERSION_POLICY_CLAUSE + ')'
|
|
307
|
+
function mathVersionPolicy(pinned) {
|
|
308
|
+
return MATH_VERSION_POLICY + ((pinned && pinned.length) ? ';本次未校验的约束:' + pinned.join(', ') : ';本次没有版本约束')
|
|
309
|
+
}
|
|
310
|
+
|
|
280
311
|
function fail(code, engine, message, extra) {
|
|
281
312
|
const out = { ok: false, op: null, engine: engine || null, code: code, message: message }
|
|
282
313
|
if (extra && extra.next) out.next = extra.next
|
|
@@ -285,6 +316,8 @@ function fail(code, engine, message, extra) {
|
|
|
285
316
|
if (extra && extra.missing) out.missing = extra.missing
|
|
286
317
|
if (extra && extra.engines) out.engines = extra.engines
|
|
287
318
|
if (extra && extra.available !== undefined) out.available = extra.available
|
|
319
|
+
if (extra && extra.configured) out.configured = extra.configured
|
|
320
|
+
if (extra && Array.isArray(extra.absent)) out.absent = extra.absent
|
|
288
321
|
if (extra && extra.packages) out.packages = extra.packages
|
|
289
322
|
// round-7 (live-session fix): the probe diagnostic must survive into the response, otherwise
|
|
290
323
|
// "存在但不可用" is opaque (a real session showed exactly that).
|
|
@@ -318,6 +351,8 @@ async function userInstallNext(H, engine) {
|
|
|
318
351
|
packageManager: mgr,
|
|
319
352
|
packageManagerAvailable: mgr ? available : null,
|
|
320
353
|
vendorUrl: d.vendor || null,
|
|
354
|
+
// Item-2: the not-found guidance carries the same provenance when the descriptor declares it.
|
|
355
|
+
...verifyFields(engine),
|
|
321
356
|
note: (mgr && !available)
|
|
322
357
|
? ('本机没有检测到包管理器 ' + mgr + ':建议命令无法运行,请按上面的官方地址手动安装(或先自行安装一个包管理器)。')
|
|
323
358
|
: null,
|
|
@@ -345,6 +380,9 @@ function adaptHost(host) {
|
|
|
345
380
|
// round-7 (fix 2): optional bundled-runtime discovery (absolute roots + absolute listing).
|
|
346
381
|
runtimeRoots: typeof h.runtimeRoots === 'function' ? h.runtimeRoots : null,
|
|
347
382
|
listDirAbs: typeof h.listDirAbs === 'function' ? h.listDirAbs : null,
|
|
383
|
+
// round-B (F4): an OPTIONAL host field - a host (or a test) that knows its own install layout can
|
|
384
|
+
// supply the per-engine candidate roots, so discovery never has to trust this process's environment.
|
|
385
|
+
installRoots: typeof h.installRoots === 'function' ? h.installRoots : null,
|
|
348
386
|
log: typeof h.log === 'function' ? h.log : function () {},
|
|
349
387
|
}
|
|
350
388
|
adapted.__mathHost = true
|
|
@@ -372,7 +410,7 @@ export async function probeMathEngines(host, opts) {
|
|
|
372
410
|
if (d.license === 'commercial') continue // installed but not usable: reported by op=run as LICENSE_REQUIRED
|
|
373
411
|
continue
|
|
374
412
|
}
|
|
375
|
-
engines.push({ name: name, version: v.version, path: hit, license: d.license })
|
|
413
|
+
engines.push(Object.assign({ name: name, version: v.version, path: hit, license: d.license }, verifyFields(name)))
|
|
376
414
|
}
|
|
377
415
|
const result = { ok: true, engines: engines, available: engines.length > 0 }
|
|
378
416
|
PROBE_CACHE.set(H, result)
|
|
@@ -383,34 +421,66 @@ export async function probeMathEngines(host, opts) {
|
|
|
383
421
|
// looked at the server process's PATH (R 4.6.1 landed in C:\Program Files\R\R-4.6.1\bin and was NOT
|
|
384
422
|
// added to PATH). Add the known per-OS INSTALL locations, globbed by version, still AFTER PATH and
|
|
385
423
|
// after the bundled runtime; existence is always proven by LISTING, never assumed.
|
|
424
|
+
// round-B (F4/F3): the candidate roots come from the HOST when it can supply them (`H.installRoots`),
|
|
425
|
+
// and only otherwise from this process's environment - so a test (or a host with its own layout) never
|
|
426
|
+
// depends on the ambient machine. ONE generic walker serves every engine: adding an engine is a table
|
|
427
|
+
// entry (roots + descriptor candidates), never a new code path. Existence is always proven by LISTING.
|
|
428
|
+
export function mathInstallRoots(engineName, env, win) {
|
|
429
|
+
const pf = String(env.ProgramFiles || 'C:/Program Files')
|
|
430
|
+
const pf86 = String(env['ProgramFiles(x86)'] || 'C:/Program Files (x86)')
|
|
431
|
+
const local = env.LOCALAPPDATA ? String(env.LOCALAPPDATA) : null
|
|
432
|
+
const home = env.USERPROFILE || env.HOME || null
|
|
433
|
+
const table = {
|
|
434
|
+
r: win
|
|
435
|
+
? [pf + '/R', pf86 + '/R', local ? local + '/Programs/R' : null]
|
|
436
|
+
: ['/usr/lib/R', '/usr/lib64/R', '/Library/Frameworks/R.framework/Resources', '/usr/local/lib/R', '/opt/R'],
|
|
437
|
+
python: win
|
|
438
|
+
? [local ? local + '/Programs/Python' : null, pf]
|
|
439
|
+
: ['/usr/local', '/usr', '/opt/python', '/opt/homebrew'],
|
|
440
|
+
octave: win
|
|
441
|
+
? [pf + '/GNU Octave', local ? local + '/Programs/GNU Octave' : null]
|
|
442
|
+
: ['/usr/lib/octave', '/usr/local/octave', '/opt/octave', '/usr/share/octave'],
|
|
443
|
+
julia: win
|
|
444
|
+
? [local ? local + '/Programs' : null, pf, home ? home + '/.juliaup/bin' : null]
|
|
445
|
+
: ['/opt', '/usr/local', home ? home + '/.juliaup/bin' : null],
|
|
446
|
+
matlab: win ? [pf + '/MATLAB'] : ['/usr/local/MATLAB', '/Applications'],
|
|
447
|
+
}
|
|
448
|
+
return (table[engineName] || []).filter((x) => typeof x === 'string' && x)
|
|
449
|
+
}
|
|
450
|
+
|
|
386
451
|
async function knownInstallCandidates(H, engineName) {
|
|
387
452
|
if (typeof H.listDirAbs !== 'function') return []
|
|
388
453
|
const cands = mathEngineCandidates(engineName)
|
|
389
454
|
if (!cands) return []
|
|
390
455
|
const env = (typeof process !== 'undefined' && process.env) || {}
|
|
391
456
|
const win = !!(typeof process !== 'undefined' && process.platform === 'win32')
|
|
457
|
+
let injected = null
|
|
458
|
+
if (typeof H.installRoots === 'function') {
|
|
459
|
+
try {
|
|
460
|
+
const r = await H.installRoots(engineName)
|
|
461
|
+
if (Array.isArray(r)) injected = r.filter((x) => typeof x === 'string' && x)
|
|
462
|
+
} catch (e) { injected = null }
|
|
463
|
+
}
|
|
464
|
+
const roots = injected || mathInstallRoots(engineName, env, win)
|
|
392
465
|
const exts = win ? ['', '.exe', '.cmd'] : ['']
|
|
393
466
|
const out = []
|
|
394
|
-
const
|
|
395
|
-
|
|
467
|
+
const listNames = async (dir, kind) => {
|
|
468
|
+
try { return ((await H.listDirAbs(dir)) || []).filter((e) => e && e.type === kind).map((e) => e.name) } catch (e) { return [] }
|
|
469
|
+
}
|
|
396
470
|
const match = async (dir) => {
|
|
397
|
-
const files = await
|
|
471
|
+
const files = await listNames(dir, 'file')
|
|
398
472
|
for (const c of cands) for (const x of exts) if (files.indexOf(c + x) !== -1) out.push(dir + '/' + c + x)
|
|
399
473
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
for (const
|
|
407
|
-
|
|
474
|
+
for (const root of roots) {
|
|
475
|
+
await match(root) // flat layout (juliaup, /usr/local)
|
|
476
|
+
await match(root + '/bin') // unix / framework layout
|
|
477
|
+
for (const d of await listNames(root, 'directory')) {
|
|
478
|
+
await match(root + '/' + d) // versioned flat (Python312, Julia-1.10)
|
|
479
|
+
await match(root + '/' + d + '/bin') // versioned install (R-4.6.1/bin)
|
|
480
|
+
for (const s of await listNames(root + '/' + d, 'directory')) {
|
|
481
|
+
await match(root + '/' + d + '/' + s + '/bin') // nested layout (Octave-9.2.0/mingw64/bin)
|
|
482
|
+
}
|
|
408
483
|
}
|
|
409
|
-
} else if (engineName === 'python' && win) {
|
|
410
|
-
const local = env.LOCALAPPDATA ? String(env.LOCALAPPDATA) + '/Programs/Python' : null
|
|
411
|
-
for (const d of (local ? await listDirs(local) : [])) await match(local + '/' + d)
|
|
412
|
-
const pf = String(env.ProgramFiles || 'C:/Program Files')
|
|
413
|
-
for (const d of await listDirs(pf)) if (/^Python[0-9]/i.test(d)) await match(pf + '/' + d)
|
|
414
484
|
}
|
|
415
485
|
return Array.from(new Set(out))
|
|
416
486
|
}
|
|
@@ -576,7 +646,7 @@ async function resolveEngine(H, requested, params, args) {
|
|
|
576
646
|
const d = applyOverride({ name: name, desc: d0 }, params, args)
|
|
577
647
|
if (params.mathEngines.indexOf(name) === -1) return fail('MATH_REFUSED', name, '引擎 ' + name + ' 不在 mathEngines 允许列表内', { next: next('reason', { reason: 'engine-not-allowed' }) })
|
|
578
648
|
if (name === 'cli') {
|
|
579
|
-
if (params.mathMode !==
|
|
649
|
+
if (CLI_POLICY.requiresMathMode && params.mathMode !== CLI_POLICY.requiresMathMode) return fail('MATH_REFUSED', name, 'cli 被策略禁用(需要 mathMode=' + CLI_POLICY.requiresMathMode + ',当前 ' + params.mathMode + ')', { next: next('reason', { reason: 'policy', requiresMathMode: CLI_POLICY.requiresMathMode }) })
|
|
580
650
|
let exe = null
|
|
581
651
|
try { exe = await H.resolveExecutable(args.cli.command) } catch (e) { exe = null }
|
|
582
652
|
if (!exe) return fail('MATH_ENGINE_NOT_FOUND', name, 'cli 命令无法解析:' + args.cli.command, { next: await userInstallNext(H, 'cli') })
|
|
@@ -598,7 +668,7 @@ async function resolveEngine(H, requested, params, args) {
|
|
|
598
668
|
if (!v.ok) {
|
|
599
669
|
if (d.license === 'commercial' || d.versionOptional) {
|
|
600
670
|
const lic = await checkLicence(H, d, hit)
|
|
601
|
-
if (!lic.ok) return fail('MATH_ENGINE_LICENSE_REQUIRED', name, name + ' 已安装但许可不可用(仅厂商可激活)', { next: next('vendor', { engine: name, url: d.vendor || '' }) })
|
|
671
|
+
if (!lic.ok) return fail('MATH_ENGINE_LICENSE_REQUIRED', name, name + ' 已安装但许可不可用(仅厂商可激活)', { next: next('vendor', Object.assign({ engine: name, url: d.vendor || '' }, verifyFields(name))) })
|
|
602
672
|
return { ok: true, name: name, desc: d, exe: hit, version: 'unknown' }
|
|
603
673
|
}
|
|
604
674
|
// round-7 (live-session fix): the engine WAS found - so an install guide would be misleading
|
|
@@ -610,7 +680,7 @@ async function resolveEngine(H, requested, params, args) {
|
|
|
610
680
|
})
|
|
611
681
|
}
|
|
612
682
|
const lic = await checkLicence(H, d, hit)
|
|
613
|
-
if (!lic.ok) return fail('MATH_ENGINE_LICENSE_REQUIRED', name, name + ' 已安装但许可不可用(仅厂商可激活)', { next: next('vendor', { engine: name, url: d.vendor || '' }) })
|
|
683
|
+
if (!lic.ok) return fail('MATH_ENGINE_LICENSE_REQUIRED', name, name + ' 已安装但许可不可用(仅厂商可激活)', { next: next('vendor', Object.assign({ engine: name, url: d.vendor || '' }, verifyFields(name))) })
|
|
614
684
|
return { ok: true, name: name, desc: d, exe: hit, version: v.version }
|
|
615
685
|
}
|
|
616
686
|
const first = requested === 'auto' ? (params.mathEngines[0] || 'python') : requested
|
|
@@ -709,11 +779,14 @@ function assembleArgv(det, mode, payload) {
|
|
|
709
779
|
}
|
|
710
780
|
}
|
|
711
781
|
if (mode === 'code' || mode === 'file') {
|
|
712
|
-
|
|
782
|
+
// Sweep finding (HIGH): substitution must be STRING-level, not exact-element equality - matlab's
|
|
783
|
+
// descriptor embeds the placeholder (`run('<script>')`), so an exact match let the literal
|
|
784
|
+
// `run('<script>')` reach the engine and the archived script never ran.
|
|
785
|
+
return { argv: [det.exe].concat((d.scriptArgv || []).map((a) => String(a).split('<script>').join(payload.scriptAbs))) }
|
|
713
786
|
}
|
|
714
787
|
if (mode === 'expr') {
|
|
715
788
|
if (!d.evalArgv) return { refused: 'engine does not support mode=expr' }
|
|
716
|
-
return { argv: [det.exe].concat(d.evalArgv.map((a) => a
|
|
789
|
+
return { argv: [det.exe].concat(d.evalArgv.map((a) => String(a).split('<expr>').join(payload.expr))) }
|
|
717
790
|
}
|
|
718
791
|
return { refused: 'unknown mode' }
|
|
719
792
|
}
|
|
@@ -865,6 +938,14 @@ async function opProbe(H, args, params) {
|
|
|
865
938
|
const probeDet = familyProbeDet(chosen)
|
|
866
939
|
if (want.length && probeDet) packages = await probePackages(H, probeDet, want)
|
|
867
940
|
else if (want.length && chosen && chosen.ok && chosen.name === 'cli') packages = { requested: want.map((s) => parsePackageSpec(s).name || s), found: {}, precheckSkipped: 'engine=cli:无法从命令名判断引擎族(不阻塞)' }
|
|
941
|
+
// S2 + round-9 F3: the absence list must exist BEFORE the early failure return, because the
|
|
942
|
+
// failure a user actually hits must carry the same evidence as the success shape.
|
|
943
|
+
const absentFor = (names) => params.mathEngines
|
|
944
|
+
.filter((e) => names.indexOf(e) === -1)
|
|
945
|
+
.map((e) => ((MATH_ENGINES[e] && MATH_ENGINES[e].resolveFrom === 'cli.command')
|
|
946
|
+
? { engine: e, why: 'needs-a-caller-supplied-command' }
|
|
947
|
+
: { engine: e, why: 'not-found-on-this-machine' }))
|
|
948
|
+
|
|
868
949
|
if (requested && chosen && !chosen.ok) {
|
|
869
950
|
// Policy/absence refusal for the REQUESTED engine, with its own guidance.
|
|
870
951
|
const out = Object.assign({}, chosen)
|
|
@@ -876,7 +957,7 @@ async function opProbe(H, args, params) {
|
|
|
876
957
|
}
|
|
877
958
|
if (!probe.available && !(chosen && chosen.ok)) {
|
|
878
959
|
const first = requested || params.mathEngines[0] || 'python'
|
|
879
|
-
const out = fail('MATH_ENGINE_NOT_FOUND', first, '本机没有可用的计算引擎(请求:' + first + ')', { next: await userInstallNext(H, first), engines: [], available: false, packages: packages })
|
|
960
|
+
const out = fail('MATH_ENGINE_NOT_FOUND', first, '本机没有可用的计算引擎(请求:' + first + ')', { next: await userInstallNext(H, first), engines: [], available: false, configured: params.mathEngines.slice(), absent: absentFor(probe.engines.map((e) => e.name)), packages: packages })
|
|
880
961
|
out.op = 'probe'
|
|
881
962
|
return out
|
|
882
963
|
}
|
|
@@ -884,18 +965,15 @@ async function opProbe(H, args, params) {
|
|
|
884
965
|
// available even when auto-detection found nothing - otherwise probe denied what run would do.
|
|
885
966
|
const engines = probe.engines.slice()
|
|
886
967
|
if (chosen && chosen.ok && !engines.some((e) => e.name === chosen.name)) {
|
|
887
|
-
engines.push({ name: chosen.name, path: chosen.exe, version: chosen.version, license: (MATH_ENGINES[chosen.name] || {}).license })
|
|
968
|
+
engines.push(Object.assign({ name: chosen.name, path: chosen.exe, version: chosen.version, license: (MATH_ENGINES[chosen.name] || {}).license }, verifyFields(chosen.name)))
|
|
888
969
|
}
|
|
889
|
-
const engineInfo = (chosen && chosen.ok) ? { name: chosen.name, path: chosen.exe, version: chosen.version } : null
|
|
970
|
+
const engineInfo = (chosen && chosen.ok) ? Object.assign({ name: chosen.name, path: chosen.exe, version: chosen.version }, verifyFields(chosen.name)) : null
|
|
890
971
|
// round-9 (F3): the availability line must never be silently partial - name the CONFIGURED engines
|
|
891
972
|
// that were not found (and why), instead of just listing the ones that happened to resolve.
|
|
892
973
|
const foundNames = engines.map((e) => e.name)
|
|
893
974
|
// Per-engine absence explanation. The key is `why` (NOT the reserved machine-readable vocabulary
|
|
894
975
|
// key), so the parity guard's exhaustive `next.reason` set stays exactly the refusal vocabulary.
|
|
895
|
-
const absent =
|
|
896
|
-
if (MATH_ENGINES[e] && MATH_ENGINES[e].resolveFrom === 'cli.command') return { engine: e, why: 'needs-a-caller-supplied-command' }
|
|
897
|
-
return { engine: e, why: 'not-found-on-this-machine' }
|
|
898
|
-
})
|
|
976
|
+
const absent = absentFor(foundNames)
|
|
899
977
|
return {
|
|
900
978
|
ok: true, op: 'probe', engine: engineInfo ? engineInfo.name : null, engineInfo: engineInfo,
|
|
901
979
|
engines: engines, available: true, packages: packages,
|
|
@@ -931,7 +1009,7 @@ async function opRun(H, args, params) {
|
|
|
931
1009
|
const want = (args.packages && args.packages.length) ? args.packages : params.mathPackages
|
|
932
1010
|
// round-9 (F7): version constraints are NOT enforced - we only check existence by base name.
|
|
933
1011
|
const pinnedSpecs = want.filter((sp) => parsePackageSpec(sp).pinned)
|
|
934
|
-
const versionPolicy =
|
|
1012
|
+
const versionPolicy = mathVersionPolicy(pinnedSpecs)
|
|
935
1013
|
// round-7 (finding 3): for cli the precheck runs against the FAMILY of `cli.command` (same
|
|
936
1014
|
// discovery-shaped logic); an unrecognisable command SKIPS the precheck instead of reporting a
|
|
937
1015
|
// false "missing" that would block the escape hatch.
|
|
@@ -942,7 +1020,7 @@ async function opRun(H, args, params) {
|
|
|
942
1020
|
// timeout) must not be read as "missing" - that misled a real session.
|
|
943
1021
|
const missing = (probeDet && !pk.probeFailed) ? want.map((s) => parsePackageSpec(s).name || s).filter((p) => Object.prototype.hasOwnProperty.call(pk.found, p) && !pk.found[p]) : []
|
|
944
1022
|
if (missing.length) {
|
|
945
|
-
return fail('MATH_MISSING_PACKAGES', det.name, '需要的包未安装:' + missing.join(', ') + '
|
|
1023
|
+
return fail('MATH_MISSING_PACKAGES', det.name, '需要的包未安装:' + missing.join(', ') + '(' + MATH_VERSION_POLICY_CLAUSE + ')', {
|
|
946
1024
|
missing: missing,
|
|
947
1025
|
next: next('agent-install', { engine: det.name, packages: missing, specs: want.slice(), dryRun: true }),
|
|
948
1026
|
packages: pk,
|
|
@@ -1058,6 +1136,9 @@ async function opRun(H, args, params) {
|
|
|
1058
1136
|
argv: assembled.argv.slice(),
|
|
1059
1137
|
cwd: root,
|
|
1060
1138
|
packages: { requested: want.slice(), found: pk.found },
|
|
1139
|
+
// round-B (F6): the constraint policy is durable evidence too - a receipt read months later must
|
|
1140
|
+
// show that constraints were existence-only and WHICH ones were not checked.
|
|
1141
|
+
versionPolicy: versionPolicy, constraintsNotEnforced: pinnedSpecs.slice(),
|
|
1061
1142
|
exit: r.timedOut ? null : (r.exit === undefined ? null : r.exit),
|
|
1062
1143
|
timedOut: !!r.timedOut,
|
|
1063
1144
|
ms: Number(r.ms || 0),
|
|
@@ -1119,11 +1200,13 @@ async function opRun(H, args, params) {
|
|
|
1119
1200
|
next: next('engine-override', { engine: det.name, argv: assembled.argv.slice(), hint: MATH_BAD_ARGV_HINT }),
|
|
1120
1201
|
})
|
|
1121
1202
|
out.exit = r.exit
|
|
1203
|
+
out.stdout = fullOut.slice(0, MATH_CAPS.stdout)
|
|
1122
1204
|
out.stderr = fullErr.slice(0, MATH_CAPS.stderr)
|
|
1123
1205
|
return Object.assign(out, archiveFields())
|
|
1124
1206
|
}
|
|
1125
1207
|
const out = fail('MATH_NONZERO_EXIT', engineLabel, '引擎以非零退出码结束:' + r.exit, { argv: assembled.argv.slice(), receipt: receiptRef })
|
|
1126
1208
|
out.exit = r.exit
|
|
1209
|
+
out.stdout = fullOut.slice(0, MATH_CAPS.stdout)
|
|
1127
1210
|
out.stderr = fullErr.slice(0, MATH_CAPS.stderr)
|
|
1128
1211
|
return Object.assign(out, archiveFields())
|
|
1129
1212
|
}
|
|
@@ -1212,7 +1295,7 @@ async function opInstall(H, args, params) {
|
|
|
1212
1295
|
if (!d) return fail('MATH_INVALID_ARGUMENT', engineName, 'unknown engine: ' + engineName)
|
|
1213
1296
|
if (d.license === 'commercial' || !d.install) {
|
|
1214
1297
|
return fail('MATH_REFUSED', engineName, engineName + ' 永不代装(商业引擎只给厂商指引)', {
|
|
1215
|
-
next: next('vendor', { engine: engineName, url: d.vendor || '' }),
|
|
1298
|
+
next: next('vendor', Object.assign({ engine: engineName, url: d.vendor || '' }, verifyFields(engineName))),
|
|
1216
1299
|
})
|
|
1217
1300
|
}
|
|
1218
1301
|
const scope = args.scope || params.mathInstallScope || 'user' // per-call only; never persisted anywhere
|
|
@@ -1248,7 +1331,7 @@ async function opInstall(H, args, params) {
|
|
|
1248
1331
|
const plan = {
|
|
1249
1332
|
engine: engineName, scope: scope, manager: activeManager, commands: commands,
|
|
1250
1333
|
managerAssumed: !!(managerInfo && managerInfo.assumed), managerWhy: managerInfo ? managerInfo.why : '',
|
|
1251
|
-
versionPolicy:
|
|
1334
|
+
versionPolicy: mathVersionPolicy(args.packages || []),
|
|
1252
1335
|
note: scope === 'user' ? '将安装到用户级目录' : '仅本次系统作用域(不会被记住)',
|
|
1253
1336
|
}
|
|
1254
1337
|
const planToken = sha256(JSON.stringify({ engine: engineName, packages: args.packages, scope: scope, commands: commands }) + '|planVersion=1').slice(0, 32)
|
|
@@ -1269,12 +1352,18 @@ async function opInstall(H, args, params) {
|
|
|
1269
1352
|
const code = (r && r.timedOut) ? 'MATH_TIMEOUT' : 'MATH_NONZERO_EXIT'
|
|
1270
1353
|
const out = fail(code, engineName, '安装失败(' + c.argv.join(' ') + '):' + (r ? ('exit=' + r.exit) : 'no subprocess'), { next: next('note', { audit: 'Computation/installs/' + planToken + '.json' }) })
|
|
1271
1354
|
out.audit = 'Computation/installs/' + planToken + '.json'
|
|
1355
|
+
// S4/S5 (non-breaking): install failures are discriminated by op + installedSoFar + timedOut,
|
|
1356
|
+
// NOT by a new failure code (MATH_FAILURE_CODES is frozen). Mirrors the `out.audit`
|
|
1357
|
+
// post-assignment and needs nothing from fail()'s key whitelist.
|
|
1358
|
+
out.op = 'install'
|
|
1359
|
+
out.timedOut = !!(r && r.timedOut)
|
|
1360
|
+
out.installedSoFar = results.filter((x) => x.exit === 0).map((x) => x.argv)
|
|
1272
1361
|
return out
|
|
1273
1362
|
}
|
|
1274
1363
|
}
|
|
1275
1364
|
const audit = { schema: 'vibe-math/math-computation-install@1', planToken: planToken, scope: scope, manager: activeManager, commands: commands, results: results, exit: 0, timedOut: false, installed: args.packages, before: {}, after: {}, rollback: rollbackFor(d, exe, args.packages, uninstallTmpl), network: 'not-enforced-by-plugin' }
|
|
1276
1365
|
await H.writeText('Computation/installs/' + planToken + '.json', JSON.stringify(audit, null, 2))
|
|
1277
|
-
return { ok: true, op: 'install', engine: engineName, executed: true, scope: scope, audit: 'Computation/installs/' + planToken + '.json', message: '已安装:' + args.packages.join(', ') + '(审计:Computation/installs/' + planToken + '.json)' }
|
|
1366
|
+
return { ok: true, op: 'install', engine: engineName, executed: true, scope: scope, installedSoFar: commands.map((c) => c.argv), audit: 'Computation/installs/' + planToken + '.json', message: '已安装:' + args.packages.join(', ') + '(审计:Computation/installs/' + planToken + '.json)' }
|
|
1278
1367
|
}
|
|
1279
1368
|
|
|
1280
1369
|
// ── round-6 (B/D): package specs + Python package-manager dispatch ──────────────────────────────
|
|
@@ -1287,7 +1376,7 @@ export function parsePackageSpec(spec) {
|
|
|
1287
1376
|
const raw = String(spec == null ? '' : spec).trim()
|
|
1288
1377
|
if (!raw) return { ok: false, problem: 'empty package spec' }
|
|
1289
1378
|
if (!PKG_SPEC_RE.test(raw)) {
|
|
1290
|
-
return { ok: false, problem: 'unsupported version syntax: "' + raw + '"
|
|
1379
|
+
return { ok: false, problem: 'unsupported version syntax: "' + raw + '"(' + MATH_VERSION_POLICY_CLAUSE + ':请用它能理解的形式,例如 pip 的 pkg==1.2 或 conda 的 pkg=1.2)' }
|
|
1291
1380
|
}
|
|
1292
1381
|
const name = raw.replace(/(?:==|>=|<=|~=|!=|>|<|=).*$/, '').replace(/\[.*$/, '')
|
|
1293
1382
|
return { ok: true, name: name, spec: raw, pinned: raw !== name }
|