dsh-vibe-math 2.7.2 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.en.md +682 -681
- package/README.md +683 -682
- package/cordis.patch.yml +112 -16
- package/docs/AUDIT-CHECKLIST.md +755 -518
- package/docs/COMPAT-AUDIT-ROUND2.md +354 -328
- package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
- package/docs/audit-methodology.md +93 -0
- package/docs/final-paper.md +172 -172
- package/docs/formal-verification.md +613 -588
- package/docs/generate_framework_diagram_v2.py +114 -114
- package/docs/generate_framework_diagram_v3.py +127 -127
- package/docs/math-computation.md +195 -187
- package/docs/parameter-schema.md +37 -0
- package/docs/release-notes/README.en.md +2 -0
- package/docs/release-notes/README.md +2 -0
- package/docs/release-notes/RELEASE-NOTES-2.0.22.md +112 -112
- package/docs/release-notes/RELEASE-NOTES-2.1.0.md +143 -143
- package/docs/release-notes/RELEASE-NOTES-2.2.0.md +266 -266
- package/docs/release-notes/RELEASE-NOTES-2.2.1.md +43 -43
- package/docs/release-notes/RELEASE-NOTES-2.2.2.md +88 -88
- package/docs/release-notes/RELEASE-NOTES-2.3.0.md +207 -207
- package/docs/release-notes/RELEASE-NOTES-2.3.1.md +134 -134
- package/docs/release-notes/RELEASE-NOTES-2.3.10.md +105 -105
- package/docs/release-notes/RELEASE-NOTES-2.3.11.md +57 -57
- package/docs/release-notes/RELEASE-NOTES-2.3.12.md +80 -80
- package/docs/release-notes/RELEASE-NOTES-2.3.2.md +145 -145
- package/docs/release-notes/RELEASE-NOTES-2.3.3.md +115 -115
- package/docs/release-notes/RELEASE-NOTES-2.3.4.md +69 -69
- package/docs/release-notes/RELEASE-NOTES-2.3.5.md +63 -63
- package/docs/release-notes/RELEASE-NOTES-2.3.6.md +66 -66
- package/docs/release-notes/RELEASE-NOTES-2.3.7.md +59 -59
- package/docs/release-notes/RELEASE-NOTES-2.3.8.md +45 -45
- package/docs/release-notes/RELEASE-NOTES-2.3.9.md +70 -70
- 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/RELEASE-NOTES-2.8.0.en.md +65 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.0.md +65 -0
- package/docs/release-notes/TEMPLATE.md +6 -0
- package/docs/release-playbook.md +103 -0
- package/docs/slv-playbook.md +117 -0
- package/docs/status-report-fields.md +167 -0
- package/docs/test-timing.md +158 -154
- package/docs//346/236/266/346/236/204/345/233/276.md +77 -77
- package/installer.js +793 -783
- package/package.json +260 -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 +2290 -282
- package/prompt-corpus-v3/formal-verify-v3.json +112 -112
- package/prompt-corpus-v3/formal-verify-v3.md +227 -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 +573 -207
- package/scripts/clean-temp.mjs +234 -0
- 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-engine-faces.mjs +298 -0
- package/tests/audit-formal-sensitivity.mjs +345 -342
- package/tests/audit-installer-assertions.mutants.mjs +63 -0
- package/tests/audit-installer-compat.test.mjs +256 -208
- 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 +5 -3
- 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 +323 -309
- package/tests/audit-persona-surface.mutants.mjs +69 -0
- package/tests/audit-persona-surface.test.mjs +431 -390
- package/tests/audit-prompt-duplication.mjs +143 -0
- package/tests/audit-prompt-duplication.mutants.mjs +51 -0
- package/tests/audit-prompt-invariants.mjs +589 -508
- 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-spec-traceability.mjs +193 -193
- 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 +795 -508
- 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 +433 -427
- package/tests/e2e-identity-a6.mutants.mjs +65 -0
- package/tests/e2e-v5-round2.test.mjs +2453 -1981
- package/tests/formal-verify-v2.test.mjs +1778 -1724
- package/tests/formal-verify-v3.mutants.mjs +117 -0
- package/tests/formal-verify-v3.test.mjs +1724 -1671
- package/tests/formal-verify-v4.mutants.mjs +152 -0
- package/tests/formal-verify-v4.test.mjs +2114 -1574
- package/tests/formal-verify-v5.test.mjs +790 -724
- 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 +392 -6
- package/tests/math-computation-v2.test.mjs +779 -0
- package/tests/math-computation-v3.test.mjs +771 -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 +1291 -1280
- package/tests/run-tests.mjs +393 -202
- package/tests/run-tests.mutants.mjs +70 -0
- package/tests/selfdrive-v5.mjs +560 -508
- package/tests/selfdrive-v5.mutants.mjs +132 -0
- package/tests/temp-hygiene.mutants.mjs +80 -0
- package/tests/v2-fix-probes.mutants.mjs +104 -0
- package/tests/v2-fix-probes.test.mjs +1040 -0
- package/tests/v2-list-agents-and-next-step.mutants.mjs +106 -0
- package/tests/v2v3-interrupt.mutants.mjs +111 -0
- package/tests/v3-fix-probes.mutants.mjs +99 -0
- package/tests/v3-fix-probes.test.mjs +892 -0
- package/tests/v4-final-paper.mutants.mjs +108 -0
- package/tests/v5-institute-fixes.mutants.mjs +218 -0
- package/vibe-math-v2/agent.cordis.yml +378 -322
- package/vibe-math-v2/math-computation.js +144 -53
- package/vibe-math-v2/math-engines.js +12 -12
- package/vibe-math-v2/preset.yml +2 -2
- package/vibe-math-v2/v2/347/232/204/351/242/235/345/244/226/350/246/201/346/261/202.txt +21 -21
- package/vibe-math-v2/vibe-math-v2.js +5097 -4755
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +491 -487
- package/vibe-math-v3/agent.cordis.yml +422 -356
- package/vibe-math-v3/math-computation.js +144 -53
- package/vibe-math-v3/math-engines.js +12 -12
- package/vibe-math-v3/preset.yml +2 -2
- package/vibe-math-v3/vibe-math-v3.js +6235 -5873
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +649 -649
- package/vibe-math-v4/agent.cordis.yml +438 -402
- package/vibe-math-v4/math-computation.js +144 -53
- package/vibe-math-v4/math-engines.js +12 -12
- package/vibe-math-v4/preset.yml +2 -2
- package/vibe-math-v4/vibe-math-v4.js +4490 -4269
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1015 -1013
- package/vibe-math-v5/agent.cordis.yml +46 -12
- package/vibe-math-v5/math-computation.js +144 -53
- package/vibe-math-v5/math-engines.js +12 -12
- package/vibe-math-v5/vibe-math-v5.js +1266 -229
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1660
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
|
@@ -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
|
|
@@ -367,12 +405,12 @@ export async function probeMathEngines(host, opts) {
|
|
|
367
405
|
if (name === 'cli') continue // cli needs a caller-supplied command, so it is not auto-detected
|
|
368
406
|
const hit = await resolveCandidate(H, d)
|
|
369
407
|
if (!hit) continue
|
|
370
|
-
const v = await probeVersion(H, d, hit)
|
|
408
|
+
const v = await probeVersion(H, d, hit.exe)
|
|
371
409
|
if (!v.ok) {
|
|
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.exe, foundVia: hit.via, 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,68 @@ 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
|
+
// A JS empty array is TRUTHY: a host that answers `[]` must fall back to the built-in table,
|
|
465
|
+
// otherwise the whole per-OS stage is silently suppressed (task-2).
|
|
466
|
+
const roots = (Array.isArray(injected) && injected.length) ? injected : mathInstallRoots(engineName, env, win)
|
|
392
467
|
const exts = win ? ['', '.exe', '.cmd'] : ['']
|
|
393
468
|
const out = []
|
|
394
|
-
const
|
|
395
|
-
|
|
469
|
+
const listNames = async (dir, kind) => {
|
|
470
|
+
try { return ((await H.listDirAbs(dir)) || []).filter((e) => e && e.type === kind).map((e) => e.name) } catch (e) { return [] }
|
|
471
|
+
}
|
|
396
472
|
const match = async (dir) => {
|
|
397
|
-
const files = await
|
|
473
|
+
const files = await listNames(dir, 'file')
|
|
398
474
|
for (const c of cands) for (const x of exts) if (files.indexOf(c + x) !== -1) out.push(dir + '/' + c + x)
|
|
399
475
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
for (const
|
|
407
|
-
|
|
476
|
+
for (const root of roots) {
|
|
477
|
+
await match(root) // flat layout (juliaup, /usr/local)
|
|
478
|
+
await match(root + '/bin') // unix / framework layout
|
|
479
|
+
for (const d of await listNames(root, 'directory')) {
|
|
480
|
+
await match(root + '/' + d) // versioned flat (Python312, Julia-1.10)
|
|
481
|
+
await match(root + '/' + d + '/bin') // versioned install (R-4.6.1/bin)
|
|
482
|
+
for (const s of await listNames(root + '/' + d, 'directory')) {
|
|
483
|
+
await match(root + '/' + d + '/' + s + '/bin') // nested layout (Octave-9.2.0/mingw64/bin)
|
|
484
|
+
}
|
|
408
485
|
}
|
|
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
486
|
}
|
|
415
487
|
return Array.from(new Set(out))
|
|
416
488
|
}
|
|
@@ -421,17 +493,17 @@ async function resolveCandidate(H, d) {
|
|
|
421
493
|
for (const c of cands) {
|
|
422
494
|
try {
|
|
423
495
|
const p = await H.resolveExecutable(c)
|
|
424
|
-
if (typeof p === 'string' && p) return p
|
|
496
|
+
if (typeof p === 'string' && p) return { exe: p, via: 'path' }
|
|
425
497
|
} catch (e) { /* not this one */ }
|
|
426
498
|
}
|
|
427
499
|
// round-7 (fix 2): DSH ships its own runtimes; PATH always wins, so this is a LAST resort. The
|
|
428
500
|
// tree name is globbed (never hard-coded) and executable names come from the descriptor candidates.
|
|
429
501
|
for (const p of await dshRuntimeCandidates(H, d.name)) {
|
|
430
|
-
if (typeof p === 'string' && p) return p
|
|
502
|
+
if (typeof p === 'string' && p) return { exe: p, via: 'host-runtime' }
|
|
431
503
|
}
|
|
432
504
|
// round-9: then the known per-OS install locations (an engine installed without touching PATH).
|
|
433
505
|
for (const p of await knownInstallCandidates(H, d.name)) {
|
|
434
|
-
if (typeof p === 'string' && p) return p
|
|
506
|
+
if (typeof p === 'string' && p) return { exe: p, via: 'known-install' }
|
|
435
507
|
}
|
|
436
508
|
return null
|
|
437
509
|
}
|
|
@@ -576,7 +648,7 @@ async function resolveEngine(H, requested, params, args) {
|
|
|
576
648
|
const d = applyOverride({ name: name, desc: d0 }, params, args)
|
|
577
649
|
if (params.mathEngines.indexOf(name) === -1) return fail('MATH_REFUSED', name, '引擎 ' + name + ' 不在 mathEngines 允许列表内', { next: next('reason', { reason: 'engine-not-allowed' }) })
|
|
578
650
|
if (name === 'cli') {
|
|
579
|
-
if (params.mathMode !==
|
|
651
|
+
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
652
|
let exe = null
|
|
581
653
|
try { exe = await H.resolveExecutable(args.cli.command) } catch (e) { exe = null }
|
|
582
654
|
if (!exe) return fail('MATH_ENGINE_NOT_FOUND', name, 'cli 命令无法解析:' + args.cli.command, { next: await userInstallNext(H, 'cli') })
|
|
@@ -590,16 +662,16 @@ async function resolveEngine(H, requested, params, args) {
|
|
|
590
662
|
cliProbe = fv.diag || null
|
|
591
663
|
if (fv.ok) cliVersion = fv.version
|
|
592
664
|
}
|
|
593
|
-
return { ok: true, name: name, desc: d, exe: exe, version: cliVersion, family: fam || null, probe: cliProbe }
|
|
665
|
+
return { ok: true, name: name, desc: d, exe: exe, version: cliVersion, family: fam || null, probe: cliProbe, via: 'cli' }
|
|
594
666
|
}
|
|
595
667
|
const hit = await resolveCandidate(H, d)
|
|
596
668
|
if (!hit) continue
|
|
597
|
-
const v = await probeVersionWithRetry(H, d, hit)
|
|
669
|
+
const v = await probeVersionWithRetry(H, d, hit.exe)
|
|
598
670
|
if (!v.ok) {
|
|
599
671
|
if (d.license === 'commercial' || d.versionOptional) {
|
|
600
|
-
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 || '' }) })
|
|
602
|
-
return { ok: true, name: name, desc: d, exe: hit, version: 'unknown' }
|
|
672
|
+
const lic = await checkLicence(H, d, hit.exe)
|
|
673
|
+
if (!lic.ok) return fail('MATH_ENGINE_LICENSE_REQUIRED', name, name + ' 已安装但许可不可用(仅厂商可激活)', { next: next('vendor', Object.assign({ engine: name, url: d.vendor || '' }, verifyFields(name))) })
|
|
674
|
+
return { ok: true, name: name, desc: d, exe: hit.exe, version: 'unknown', via: hit.via }
|
|
603
675
|
}
|
|
604
676
|
// round-7 (live-session fix): the engine WAS found - so an install guide would be misleading
|
|
605
677
|
// (the user does not need to install anything; the host path is what failed). Report the probe
|
|
@@ -609,9 +681,9 @@ async function resolveEngine(H, requested, params, args) {
|
|
|
609
681
|
next: next('note', { guidance: '引擎已找到但探测失败:请检查宿主 subprocess 通路(冷启动/超时/权限),或用 mathEngineOverride 调整 versionArgv 模板;已装的引擎不需要重装。' }),
|
|
610
682
|
})
|
|
611
683
|
}
|
|
612
|
-
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 || '' }) })
|
|
614
|
-
return { ok: true, name: name, desc: d, exe: hit, version: v.version }
|
|
684
|
+
const lic = await checkLicence(H, d, hit.exe)
|
|
685
|
+
if (!lic.ok) return fail('MATH_ENGINE_LICENSE_REQUIRED', name, name + ' 已安装但许可不可用(仅厂商可激活)', { next: next('vendor', Object.assign({ engine: name, url: d.vendor || '' }, verifyFields(name))) })
|
|
686
|
+
return { ok: true, name: name, desc: d, exe: hit.exe, version: v.version, via: hit.via }
|
|
615
687
|
}
|
|
616
688
|
const first = requested === 'auto' ? (params.mathEngines[0] || 'python') : requested
|
|
617
689
|
return fail('MATH_ENGINE_NOT_FOUND', first, '本机没有可用的计算引擎(试过:' + names.join(', ') + ')', { next: await userInstallNext(H, first) })
|
|
@@ -709,11 +781,14 @@ function assembleArgv(det, mode, payload) {
|
|
|
709
781
|
}
|
|
710
782
|
}
|
|
711
783
|
if (mode === 'code' || mode === 'file') {
|
|
712
|
-
|
|
784
|
+
// Sweep finding (HIGH): substitution must be STRING-level, not exact-element equality - matlab's
|
|
785
|
+
// descriptor embeds the placeholder (`run('<script>')`), so an exact match let the literal
|
|
786
|
+
// `run('<script>')` reach the engine and the archived script never ran.
|
|
787
|
+
return { argv: [det.exe].concat((d.scriptArgv || []).map((a) => String(a).split('<script>').join(payload.scriptAbs))) }
|
|
713
788
|
}
|
|
714
789
|
if (mode === 'expr') {
|
|
715
790
|
if (!d.evalArgv) return { refused: 'engine does not support mode=expr' }
|
|
716
|
-
return { argv: [det.exe].concat(d.evalArgv.map((a) => a
|
|
791
|
+
return { argv: [det.exe].concat(d.evalArgv.map((a) => String(a).split('<expr>').join(payload.expr))) }
|
|
717
792
|
}
|
|
718
793
|
return { refused: 'unknown mode' }
|
|
719
794
|
}
|
|
@@ -865,6 +940,14 @@ async function opProbe(H, args, params) {
|
|
|
865
940
|
const probeDet = familyProbeDet(chosen)
|
|
866
941
|
if (want.length && probeDet) packages = await probePackages(H, probeDet, want)
|
|
867
942
|
else if (want.length && chosen && chosen.ok && chosen.name === 'cli') packages = { requested: want.map((s) => parsePackageSpec(s).name || s), found: {}, precheckSkipped: 'engine=cli:无法从命令名判断引擎族(不阻塞)' }
|
|
943
|
+
// S2 + round-9 F3: the absence list must exist BEFORE the early failure return, because the
|
|
944
|
+
// failure a user actually hits must carry the same evidence as the success shape.
|
|
945
|
+
const absentFor = (names) => params.mathEngines
|
|
946
|
+
.filter((e) => names.indexOf(e) === -1)
|
|
947
|
+
.map((e) => ((MATH_ENGINES[e] && MATH_ENGINES[e].resolveFrom === 'cli.command')
|
|
948
|
+
? { engine: e, why: 'needs-a-caller-supplied-command' }
|
|
949
|
+
: { engine: e, why: 'not-found-on-this-machine' }))
|
|
950
|
+
|
|
868
951
|
if (requested && chosen && !chosen.ok) {
|
|
869
952
|
// Policy/absence refusal for the REQUESTED engine, with its own guidance.
|
|
870
953
|
const out = Object.assign({}, chosen)
|
|
@@ -876,7 +959,7 @@ async function opProbe(H, args, params) {
|
|
|
876
959
|
}
|
|
877
960
|
if (!probe.available && !(chosen && chosen.ok)) {
|
|
878
961
|
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 })
|
|
962
|
+
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
963
|
out.op = 'probe'
|
|
881
964
|
return out
|
|
882
965
|
}
|
|
@@ -884,18 +967,15 @@ async function opProbe(H, args, params) {
|
|
|
884
967
|
// available even when auto-detection found nothing - otherwise probe denied what run would do.
|
|
885
968
|
const engines = probe.engines.slice()
|
|
886
969
|
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 })
|
|
970
|
+
engines.push(Object.assign({ name: chosen.name, path: chosen.exe, version: chosen.version, license: (MATH_ENGINES[chosen.name] || {}).license }, verifyFields(chosen.name)))
|
|
888
971
|
}
|
|
889
|
-
const engineInfo = (chosen && chosen.ok) ? { name: chosen.name, path: chosen.exe, version: chosen.version } : null
|
|
972
|
+
const engineInfo = (chosen && chosen.ok) ? Object.assign({ name: chosen.name, path: chosen.exe, version: chosen.version, foundVia: chosen.via }, verifyFields(chosen.name)) : null
|
|
890
973
|
// round-9 (F3): the availability line must never be silently partial - name the CONFIGURED engines
|
|
891
974
|
// that were not found (and why), instead of just listing the ones that happened to resolve.
|
|
892
975
|
const foundNames = engines.map((e) => e.name)
|
|
893
976
|
// Per-engine absence explanation. The key is `why` (NOT the reserved machine-readable vocabulary
|
|
894
977
|
// 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
|
-
})
|
|
978
|
+
const absent = absentFor(foundNames)
|
|
899
979
|
return {
|
|
900
980
|
ok: true, op: 'probe', engine: engineInfo ? engineInfo.name : null, engineInfo: engineInfo,
|
|
901
981
|
engines: engines, available: true, packages: packages,
|
|
@@ -922,7 +1002,7 @@ async function opRun(H, args, params) {
|
|
|
922
1002
|
const det = await resolveEngine(H, args.engine || 'auto', params, args)
|
|
923
1003
|
if (!det.ok) return det
|
|
924
1004
|
const desc = applyOverride(det, params, args)
|
|
925
|
-
const det2 = { ok: true, name: det.name, desc: desc, exe: det.exe, version: det.version }
|
|
1005
|
+
const det2 = { ok: true, name: det.name, desc: desc, exe: det.exe, version: det.version, via: det.via }
|
|
926
1006
|
// The generic escape hatch is recorded as `cli:<command>` (spec §4.3 / guards §18): the model and
|
|
927
1007
|
// the receipt must be able to tell WHICH command was run, not just that it was "cli".
|
|
928
1008
|
const cliCommand = args.cli && typeof args.cli.command === 'string' ? args.cli.command : ''
|
|
@@ -931,7 +1011,7 @@ async function opRun(H, args, params) {
|
|
|
931
1011
|
const want = (args.packages && args.packages.length) ? args.packages : params.mathPackages
|
|
932
1012
|
// round-9 (F7): version constraints are NOT enforced - we only check existence by base name.
|
|
933
1013
|
const pinnedSpecs = want.filter((sp) => parsePackageSpec(sp).pinned)
|
|
934
|
-
const versionPolicy =
|
|
1014
|
+
const versionPolicy = mathVersionPolicy(pinnedSpecs)
|
|
935
1015
|
// round-7 (finding 3): for cli the precheck runs against the FAMILY of `cli.command` (same
|
|
936
1016
|
// discovery-shaped logic); an unrecognisable command SKIPS the precheck instead of reporting a
|
|
937
1017
|
// false "missing" that would block the escape hatch.
|
|
@@ -942,7 +1022,7 @@ async function opRun(H, args, params) {
|
|
|
942
1022
|
// timeout) must not be read as "missing" - that misled a real session.
|
|
943
1023
|
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
1024
|
if (missing.length) {
|
|
945
|
-
return fail('MATH_MISSING_PACKAGES', det.name, '需要的包未安装:' + missing.join(', ') + '
|
|
1025
|
+
return fail('MATH_MISSING_PACKAGES', det.name, '需要的包未安装:' + missing.join(', ') + '(' + MATH_VERSION_POLICY_CLAUSE + ')', {
|
|
946
1026
|
missing: missing,
|
|
947
1027
|
next: next('agent-install', { engine: det.name, packages: missing, specs: want.slice(), dryRun: true }),
|
|
948
1028
|
packages: pk,
|
|
@@ -1040,7 +1120,7 @@ async function opRun(H, args, params) {
|
|
|
1040
1120
|
schema: 'vibe-math/math-computation-receipt@1',
|
|
1041
1121
|
runId: runId, preset: H.designator, project: slug(root), op: 'run', mode: mode,
|
|
1042
1122
|
attempt: attempt, attemptDir: dir, baseRunDir: baseDir,
|
|
1043
|
-
engine: { name: engineLabel, path: det.exe, version: det.version, source: det.name === 'cli' ? 'cli' : 'path' },
|
|
1123
|
+
engine: { name: engineLabel, path: det.exe, version: det.version, source: det.name === 'cli' ? 'cli' : 'path', foundVia: det2.via },
|
|
1044
1124
|
script: { path: scriptRel, sha256: scriptHash, bytes: Buffer.byteLength(scriptText, 'utf8') },
|
|
1045
1125
|
scriptPath: scriptRel, scriptHash: scriptHash,
|
|
1046
1126
|
// round-8 (P0/D3): member file tools are session-cwd relative, so the ABSOLUTE locations must be
|
|
@@ -1058,6 +1138,9 @@ async function opRun(H, args, params) {
|
|
|
1058
1138
|
argv: assembled.argv.slice(),
|
|
1059
1139
|
cwd: root,
|
|
1060
1140
|
packages: { requested: want.slice(), found: pk.found },
|
|
1141
|
+
// round-B (F6): the constraint policy is durable evidence too - a receipt read months later must
|
|
1142
|
+
// show that constraints were existence-only and WHICH ones were not checked.
|
|
1143
|
+
versionPolicy: versionPolicy, constraintsNotEnforced: pinnedSpecs.slice(),
|
|
1061
1144
|
exit: r.timedOut ? null : (r.exit === undefined ? null : r.exit),
|
|
1062
1145
|
timedOut: !!r.timedOut,
|
|
1063
1146
|
ms: Number(r.ms || 0),
|
|
@@ -1119,11 +1202,13 @@ async function opRun(H, args, params) {
|
|
|
1119
1202
|
next: next('engine-override', { engine: det.name, argv: assembled.argv.slice(), hint: MATH_BAD_ARGV_HINT }),
|
|
1120
1203
|
})
|
|
1121
1204
|
out.exit = r.exit
|
|
1205
|
+
out.stdout = fullOut.slice(0, MATH_CAPS.stdout)
|
|
1122
1206
|
out.stderr = fullErr.slice(0, MATH_CAPS.stderr)
|
|
1123
1207
|
return Object.assign(out, archiveFields())
|
|
1124
1208
|
}
|
|
1125
1209
|
const out = fail('MATH_NONZERO_EXIT', engineLabel, '引擎以非零退出码结束:' + r.exit, { argv: assembled.argv.slice(), receipt: receiptRef })
|
|
1126
1210
|
out.exit = r.exit
|
|
1211
|
+
out.stdout = fullOut.slice(0, MATH_CAPS.stdout)
|
|
1127
1212
|
out.stderr = fullErr.slice(0, MATH_CAPS.stderr)
|
|
1128
1213
|
return Object.assign(out, archiveFields())
|
|
1129
1214
|
}
|
|
@@ -1212,7 +1297,7 @@ async function opInstall(H, args, params) {
|
|
|
1212
1297
|
if (!d) return fail('MATH_INVALID_ARGUMENT', engineName, 'unknown engine: ' + engineName)
|
|
1213
1298
|
if (d.license === 'commercial' || !d.install) {
|
|
1214
1299
|
return fail('MATH_REFUSED', engineName, engineName + ' 永不代装(商业引擎只给厂商指引)', {
|
|
1215
|
-
next: next('vendor', { engine: engineName, url: d.vendor || '' }),
|
|
1300
|
+
next: next('vendor', Object.assign({ engine: engineName, url: d.vendor || '' }, verifyFields(engineName))),
|
|
1216
1301
|
})
|
|
1217
1302
|
}
|
|
1218
1303
|
const scope = args.scope || params.mathInstallScope || 'user' // per-call only; never persisted anywhere
|
|
@@ -1248,7 +1333,7 @@ async function opInstall(H, args, params) {
|
|
|
1248
1333
|
const plan = {
|
|
1249
1334
|
engine: engineName, scope: scope, manager: activeManager, commands: commands,
|
|
1250
1335
|
managerAssumed: !!(managerInfo && managerInfo.assumed), managerWhy: managerInfo ? managerInfo.why : '',
|
|
1251
|
-
versionPolicy:
|
|
1336
|
+
versionPolicy: mathVersionPolicy(args.packages || []),
|
|
1252
1337
|
note: scope === 'user' ? '将安装到用户级目录' : '仅本次系统作用域(不会被记住)',
|
|
1253
1338
|
}
|
|
1254
1339
|
const planToken = sha256(JSON.stringify({ engine: engineName, packages: args.packages, scope: scope, commands: commands }) + '|planVersion=1').slice(0, 32)
|
|
@@ -1269,12 +1354,18 @@ async function opInstall(H, args, params) {
|
|
|
1269
1354
|
const code = (r && r.timedOut) ? 'MATH_TIMEOUT' : 'MATH_NONZERO_EXIT'
|
|
1270
1355
|
const out = fail(code, engineName, '安装失败(' + c.argv.join(' ') + '):' + (r ? ('exit=' + r.exit) : 'no subprocess'), { next: next('note', { audit: 'Computation/installs/' + planToken + '.json' }) })
|
|
1271
1356
|
out.audit = 'Computation/installs/' + planToken + '.json'
|
|
1357
|
+
// S4/S5 (non-breaking): install failures are discriminated by op + installedSoFar + timedOut,
|
|
1358
|
+
// NOT by a new failure code (MATH_FAILURE_CODES is frozen). Mirrors the `out.audit`
|
|
1359
|
+
// post-assignment and needs nothing from fail()'s key whitelist.
|
|
1360
|
+
out.op = 'install'
|
|
1361
|
+
out.timedOut = !!(r && r.timedOut)
|
|
1362
|
+
out.installedSoFar = results.filter((x) => x.exit === 0).map((x) => x.argv)
|
|
1272
1363
|
return out
|
|
1273
1364
|
}
|
|
1274
1365
|
}
|
|
1275
1366
|
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
1367
|
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)' }
|
|
1368
|
+
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
1369
|
}
|
|
1279
1370
|
|
|
1280
1371
|
// ── round-6 (B/D): package specs + Python package-manager dispatch ──────────────────────────────
|
|
@@ -1287,7 +1378,7 @@ export function parsePackageSpec(spec) {
|
|
|
1287
1378
|
const raw = String(spec == null ? '' : spec).trim()
|
|
1288
1379
|
if (!raw) return { ok: false, problem: 'empty package spec' }
|
|
1289
1380
|
if (!PKG_SPEC_RE.test(raw)) {
|
|
1290
|
-
return { ok: false, problem: 'unsupported version syntax: "' + raw + '"
|
|
1381
|
+
return { ok: false, problem: 'unsupported version syntax: "' + raw + '"(' + MATH_VERSION_POLICY_CLAUSE + ':请用它能理解的形式,例如 pip 的 pkg==1.2 或 conda 的 pkg=1.2)' }
|
|
1291
1382
|
}
|
|
1292
1383
|
const name = raw.replace(/(?:==|>=|<=|~=|!=|>|<|=).*$/, '').replace(/\[.*$/, '')
|
|
1293
1384
|
return { ok: true, name: name, spec: raw, pinned: raw !== name }
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// No repo imports — node builtins only (this file imports nothing at all).
|
|
7
7
|
//
|
|
8
8
|
// Placeholders used in the argv templates (each substitutes into ONE argv element only):
|
|
9
|
-
// <script> <expr> <pkgs> <probeCode> <exe> <pkg>
|
|
9
|
+
// <script> <expr> <pkgs> <probeCode> <exe> <pkg>
|
|
10
10
|
// A descriptor is pure data; every template can be overridden by the user via mathEngineOverride.
|
|
11
11
|
|
|
12
12
|
export const MATH_ENGINE_ORDER = ['python', 'r', 'octave', 'julia', 'matlab', 'maple', 'wolfram', 'cli']
|
|
@@ -19,9 +19,9 @@ const ARG_ERROR_HINTS = ['unknown option', 'invalid option', 'unrecognized optio
|
|
|
19
19
|
export const MATH_ENGINES = {
|
|
20
20
|
python: {
|
|
21
21
|
name: 'python', phase: 'P1',
|
|
22
|
-
candidates: ['python3', 'python', 'py'],
|
|
22
|
+
candidates: ['python3', 'python', 'py'],
|
|
23
23
|
versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+\\.\\d+)',
|
|
24
|
-
scriptArgv: ['<script>'], evalArgv: ['-c', '<expr>'],
|
|
24
|
+
scriptArgv: ['<script>'], evalArgv: ['-c', '<expr>'], ext: '.py',
|
|
25
25
|
// round-7 (live): NO shell metacharacters (`|`, `%`) in any probe argv - a real host rejected
|
|
26
26
|
// them with a null spawn. One line per package: "name ok|missing".
|
|
27
27
|
probeCode: "import importlib.util,sys\nfor p in sys.argv[1:]: print(p, 'ok' if importlib.util.find_spec(p) is not None else 'missing')",
|
|
@@ -70,7 +70,7 @@ export const MATH_ENGINES = {
|
|
|
70
70
|
name: 'r', phase: 'P1',
|
|
71
71
|
candidates: ['Rscript', 'R'],
|
|
72
72
|
versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+\\.\\d+)',
|
|
73
|
-
scriptArgv: ['<script>'], evalArgv: ['-e', '<expr>'],
|
|
73
|
+
scriptArgv: ['<script>'], evalArgv: ['-e', '<expr>'], ext: '.R',
|
|
74
74
|
// round-7 (live): line-per-package, no `|` in the argv.
|
|
75
75
|
probeCode: "for (p in strsplit('__PKGS__',',')[[1]]) cat(p, if (requireNamespace(p, quietly=TRUE)) 'ok' else 'missing', '\\n')",
|
|
76
76
|
packageProbe: { argv: ['-e', '<probeCode>'], parse: 'pairs' },
|
|
@@ -89,7 +89,7 @@ export const MATH_ENGINES = {
|
|
|
89
89
|
candidates: ['octave', 'octave-cli'],
|
|
90
90
|
versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+\\.\\d+)',
|
|
91
91
|
scriptArgv: ['--no-gui', '--quiet', '<script>'], evalArgv: ['--no-gui', '--quiet', '--eval', '<expr>'],
|
|
92
|
-
|
|
92
|
+
ext: '.m',
|
|
93
93
|
// round-7 (live): line-per-package via disp, no `|` and no `%` in the argv.
|
|
94
94
|
probeCode: "s=pkg('list'); n={'__QPKGS__'}; for i=1:numel(n); if any(cellfun(@(x) strcmp(x.name,n{i}),s)); disp([n{i} ' ok']); else; disp([n{i} ' missing']); end; end",
|
|
95
95
|
packageProbe: { argv: ['--no-gui', '--quiet', '--eval', '<probeCode>'], parse: 'pairs' },
|
|
@@ -106,7 +106,7 @@ export const MATH_ENGINES = {
|
|
|
106
106
|
name: 'julia', phase: 'P1',
|
|
107
107
|
candidates: ['julia'],
|
|
108
108
|
versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+\\.\\d+)',
|
|
109
|
-
scriptArgv: ['<script>'], evalArgv: ['-e', '<expr>'],
|
|
109
|
+
scriptArgv: ['<script>'], evalArgv: ['-e', '<expr>'], ext: '.jl',
|
|
110
110
|
probeCode: 'for p in [__QPKGS__]; println(p, ":", Base.find_package(p) === nothing ? "missing" : "ok"); end',
|
|
111
111
|
packageProbe: { argv: ['-e', '<probeCode>'], parse: 'pairs' },
|
|
112
112
|
license: 'free', argErrorHints: ARG_ERROR_HINTS,
|
|
@@ -122,7 +122,7 @@ export const MATH_ENGINES = {
|
|
|
122
122
|
name: 'matlab', phase: 'P1',
|
|
123
123
|
candidates: ['matlab'],
|
|
124
124
|
versionArgv: ['-batch', 'disp(version)'], versionRe: '(\\d+\\.\\d+)',
|
|
125
|
-
scriptArgv: ['-batch', "run('<script>')"], evalArgv: ['-batch', '<expr>'],
|
|
125
|
+
scriptArgv: ['-batch', "run('<script>')"], evalArgv: ['-batch', '<expr>'], ext: '.m',
|
|
126
126
|
// round-7 (live): no `|`/`%` in any probe argv (a real host rejected such argv with a null spawn).
|
|
127
127
|
probeCode: "t={'__QPKGS__'}; for i=1:numel(t); if license('test',t{i}); disp([t{i} ' ok']); else; disp([t{i} ' missing']); end; end",
|
|
128
128
|
packageProbe: { argv: ['-batch', '<probeCode>'], parse: 'pairs' },
|
|
@@ -135,7 +135,7 @@ export const MATH_ENGINES = {
|
|
|
135
135
|
name: 'maple', phase: 'P1',
|
|
136
136
|
candidates: ['maple'],
|
|
137
137
|
versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+)',
|
|
138
|
-
scriptArgv: ['-q', '<script>'], evalArgv: ['-q', '-c', '<expr>'],
|
|
138
|
+
scriptArgv: ['-q', '<script>'], evalArgv: ['-q', '-c', '<expr>'], ext: '.mpl',
|
|
139
139
|
// round-7 (live): `print` instead of printf so the argv carries no `|`/`%`.
|
|
140
140
|
probeCode: 'for p in ["__PKGS__"] do try with(p) catch: print(p, " missing"); next end try; print(p, " ok") end do',
|
|
141
141
|
packageProbe: { argv: ['-q', '-c', '<probeCode>'], parse: 'pairs' },
|
|
@@ -149,7 +149,7 @@ export const MATH_ENGINES = {
|
|
|
149
149
|
name: 'wolfram', phase: 'P1',
|
|
150
150
|
candidates: ['wolframscript', 'WolframKernel', 'math'],
|
|
151
151
|
versionArgv: ['-code', '$Version'], versionRe: '(\\d+\\.\\d+(\\.\\d+)?)',
|
|
152
|
-
scriptArgv: ['-file', '<script>'], evalArgv: ['-code', '<expr>'],
|
|
152
|
+
scriptArgv: ['-file', '<script>'], evalArgv: ['-code', '<expr>'], ext: '.wl',
|
|
153
153
|
probeCode: 'Do[Print[p,":",If[Quiet[Check[Needs[p];True,False]],"ok","missing"]],{p,{__QPKGS__}}]',
|
|
154
154
|
packageProbe: { argv: ['-code', '<probeCode>'], parse: 'pairs' },
|
|
155
155
|
license: 'commercial', argErrorHints: ARG_ERROR_HINTS,
|
|
@@ -158,10 +158,10 @@ export const MATH_ENGINES = {
|
|
|
158
158
|
userInstall: { windows: 'vendor installer + activation', macos: 'vendor installer + activation', linux: 'vendor installer + activation' },
|
|
159
159
|
},
|
|
160
160
|
cli: {
|
|
161
|
-
name: 'cli', phase: 'P1',
|
|
161
|
+
name: 'cli', phase: 'P1',
|
|
162
162
|
candidates: [], resolveFrom: 'cli.command',
|
|
163
163
|
versionArgv: ['--version'], versionRe: '([^\\s]+)', versionOptional: true,
|
|
164
|
-
|
|
164
|
+
evalArgv: null, ext: '.txt',
|
|
165
165
|
probeCode: null, packageProbe: null,
|
|
166
166
|
license: 'user', argErrorHints: ARG_ERROR_HINTS,
|
|
167
167
|
install: null, vendor: null,
|
|
@@ -175,7 +175,7 @@ export const MATH_P2_ENGINES = {
|
|
|
175
175
|
sage: {
|
|
176
176
|
name: 'sage', phase: 'P2', candidates: ['sage'],
|
|
177
177
|
versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+)',
|
|
178
|
-
scriptArgv: ['<script>'], evalArgv: ['-c', '<expr>'],
|
|
178
|
+
scriptArgv: ['<script>'], evalArgv: ['-c', '<expr>'], ext: '.sage',
|
|
179
179
|
probeCode: null, packageProbe: { argv: ['-pip', 'list'], parse: 'lines' },
|
|
180
180
|
license: 'free', argErrorHints: ARG_ERROR_HINTS, install: null,
|
|
181
181
|
},
|
package/vibe-math-v2/preset.yml
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
name: Vibe Math V2
|
|
2
|
-
description: 多代理数学问题求解与验证框架(经典):概率驱动调度 —— qs.json 问题清单 + Propos/ 命题库 + Reliable/ 参考文献,explorer 拆方向 → 逐方向 Solver 迭代 → 多验证器独立审查→辩论→裁决;断点续跑、人工/自动干预、file/push 双模式进度汇报。
|
|
1
|
+
name: Vibe Math V2
|
|
2
|
+
description: 多代理数学问题求解与验证框架(经典):概率驱动调度 —— qs.json 问题清单 + Propos/ 命题库 + Reliable/ 参考文献,explorer 拆方向 → 逐方向 Solver 迭代 → 多验证器独立审查→辩论→裁决;断点续跑、人工/自动干预、file/push 双模式进度汇报。
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
判断当前 v2 的下述是否已实现、当前是否符合下述,若未实现则请实现,过程中有不明确或觉得不合理或有自己建议时请询问我来决策:
|
|
2
|
-
|
|
3
|
-
1 .当前是否按我说的只允许“命题、命题+证明/证伪、问题+解法”被调度到验证器?以及是否按我说的只允许“问题”被调度到求解器solve。
|
|
4
|
-
|
|
5
|
-
2 .当前是否实现:当“命题”、“问题”的“证明/证伪/解法”的正确概率为1或0,或“命题”的布尔估计(正确概率)为1或0 或 问题已被解决时,则该证明/证伪/解法永不会被调度到去验证。当 “命题”、“问题”的证明/证伪/解法列表中存在它的正确概率为1的解法/证明/证伪时,则该“命题”、“问题”优先级设为 never,永不调度(到验证器、solve)。命题、问题 如果证明/证伪/解法列表非空且含有正确概率非0的证明/证伪/解法的话 ,则在验证时可优先验证“命题/问题+证明/证伪/解法”而不是“命题/问题”,在验证时优先调度其正确概率高的证明/证伪/解法进行验证。正确概率会动态调整。
|
|
6
|
-
|
|
7
|
-
3 .允许晋升为问题清单的“问题”的是“命题”,晋升原命题对象为“判断下述命题是否成立:...(原命题)”这样的问题对象,相应证明、证伪列表变为解法列表(并在各个证明证伪过程前面表明是要证明还是证伪),一些合理的、适配的属性也可以对应地转移过去。那些布尔概率为0、1的命题不会被晋升。
|
|
8
|
-
|
|
9
|
-
4 .那个Verified里需含有所有当前 被证明/证伪的命题和已解决的问题及它们的所有正确概率为1的证明/证伪/解法。
|
|
10
|
-
|
|
11
|
-
5 .按下述修改文件 实现方案.md 中的那个 q_sub 部分的逻辑(使得我的这个方案更严格化),然后按照下述来实现下述功能:q_sub 需为问题类对象,临时假设(记 p_{q-tmp} )为对 q_sub 的某种回答/答案的临时假设(是命题类对象)。把临时问题 q_sub 和“判断下述命题是否成立:p_{q-tmp}”交到问题清单(注意交到问题清单后的它们的概述、定义要完整,所涉及的一些依赖、对象等要给出完整定义,以避免断章取义),之后推理过程中发现的命题/结论中那些依赖这个假设的结论/命题在输出时需要把这个假设加上来作为命题/结论,如“若 p_{q-tmp} 成立,则...”(注意,概述、定义要完整,所依赖、基于的一些对象、环境、背景等要给出完整定义,不能断章取义)。
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
6 .是否有对代理通过提示词/上下文按照我实现方案中的定义来解释/告诉它各个参数的完整含义(如progress是指...)?
|
|
15
|
-
要完全对照我的实现方案,来对各提示词/上下文中可能导致代理理解不清楚的地方、对象、要求进行优化、完善、完整解释、举例解释等,优化提示词,使得能让ai/代理完全明确理解含义,使得符合我实现方案中的定义/含义/意思。一定要让代理完全明白、符合实现方案中的理解/要求来进行。
|
|
16
|
-
例如,一个命题、问题或其它各种对象及其各属性的完整定义(按照我实现方案.md中的来),只要1或0那么就视为绝对正确/错误,就可以被代理作为已知成立的结论来使用。
|
|
17
|
-
这样来保证它们在阅读文件时可以有正确的理解,以及保证它们可以明确到底要输出什么、对输出的内容有正确的理解、使得输出的内容符合要求(例如尤其是要求输出的那些对象一定要完整不要断章取义,要把环境/背景、一些依赖的对象/概念都完整补充、补充定义)。
|
|
18
|
-
当前是否有对它们介绍各个文件夹(里文件)的用处、各个文件夹里文件的格式及文件的各参数的含义、完整含义和用处(按我的实现方案来)。
|
|
19
|
-
|
|
20
|
-
7. 允许调控它们是否可以使用网络、写程序/脚本及其次数等。
|
|
21
|
-
|
|
1
|
+
判断当前 v2 的下述是否已实现、当前是否符合下述,若未实现则请实现,过程中有不明确或觉得不合理或有自己建议时请询问我来决策:
|
|
2
|
+
|
|
3
|
+
1 .当前是否按我说的只允许“命题、命题+证明/证伪、问题+解法”被调度到验证器?以及是否按我说的只允许“问题”被调度到求解器solve。
|
|
4
|
+
|
|
5
|
+
2 .当前是否实现:当“命题”、“问题”的“证明/证伪/解法”的正确概率为1或0,或“命题”的布尔估计(正确概率)为1或0 或 问题已被解决时,则该证明/证伪/解法永不会被调度到去验证。当 “命题”、“问题”的证明/证伪/解法列表中存在它的正确概率为1的解法/证明/证伪时,则该“命题”、“问题”优先级设为 never,永不调度(到验证器、solve)。命题、问题 如果证明/证伪/解法列表非空且含有正确概率非0的证明/证伪/解法的话 ,则在验证时可优先验证“命题/问题+证明/证伪/解法”而不是“命题/问题”,在验证时优先调度其正确概率高的证明/证伪/解法进行验证。正确概率会动态调整。
|
|
6
|
+
|
|
7
|
+
3 .允许晋升为问题清单的“问题”的是“命题”,晋升原命题对象为“判断下述命题是否成立:...(原命题)”这样的问题对象,相应证明、证伪列表变为解法列表(并在各个证明证伪过程前面表明是要证明还是证伪),一些合理的、适配的属性也可以对应地转移过去。那些布尔概率为0、1的命题不会被晋升。
|
|
8
|
+
|
|
9
|
+
4 .那个Verified里需含有所有当前 被证明/证伪的命题和已解决的问题及它们的所有正确概率为1的证明/证伪/解法。
|
|
10
|
+
|
|
11
|
+
5 .按下述修改文件 实现方案.md 中的那个 q_sub 部分的逻辑(使得我的这个方案更严格化),然后按照下述来实现下述功能:q_sub 需为问题类对象,临时假设(记 p_{q-tmp} )为对 q_sub 的某种回答/答案的临时假设(是命题类对象)。把临时问题 q_sub 和“判断下述命题是否成立:p_{q-tmp}”交到问题清单(注意交到问题清单后的它们的概述、定义要完整,所涉及的一些依赖、对象等要给出完整定义,以避免断章取义),之后推理过程中发现的命题/结论中那些依赖这个假设的结论/命题在输出时需要把这个假设加上来作为命题/结论,如“若 p_{q-tmp} 成立,则...”(注意,概述、定义要完整,所依赖、基于的一些对象、环境、背景等要给出完整定义,不能断章取义)。
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
6 .是否有对代理通过提示词/上下文按照我实现方案中的定义来解释/告诉它各个参数的完整含义(如progress是指...)?
|
|
15
|
+
要完全对照我的实现方案,来对各提示词/上下文中可能导致代理理解不清楚的地方、对象、要求进行优化、完善、完整解释、举例解释等,优化提示词,使得能让ai/代理完全明确理解含义,使得符合我实现方案中的定义/含义/意思。一定要让代理完全明白、符合实现方案中的理解/要求来进行。
|
|
16
|
+
例如,一个命题、问题或其它各种对象及其各属性的完整定义(按照我实现方案.md中的来),只要1或0那么就视为绝对正确/错误,就可以被代理作为已知成立的结论来使用。
|
|
17
|
+
这样来保证它们在阅读文件时可以有正确的理解,以及保证它们可以明确到底要输出什么、对输出的内容有正确的理解、使得输出的内容符合要求(例如尤其是要求输出的那些对象一定要完整不要断章取义,要把环境/背景、一些依赖的对象/概念都完整补充、补充定义)。
|
|
18
|
+
当前是否有对它们介绍各个文件夹(里文件)的用处、各个文件夹里文件的格式及文件的各参数的含义、完整含义和用处(按我的实现方案来)。
|
|
19
|
+
|
|
20
|
+
7. 允许调控它们是否可以使用网络、写程序/脚本及其次数等。
|
|
21
|
+
|
|
22
22
|
8. 让对代理的提示词/上下文的一些关键部分(局部提示词/上下文)也作为 setting 值可配置的参数(方便用户自行修改/调控一些关键部分的提示词/上下文)。
|