dsh-math-modeling-agent 0.3.1 → 0.4.1
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.md +16 -1
- package/package.json +1 -1
- package/skills/math-modeling-agent/SKILL.md +16 -3
- package/skills/math-modeling-agent/references/original-project-parity.md +19 -0
- package/skills/math-modeling-agent/references/subagent-dispatch.md +3 -2
- package/skills/math-modeling-agent/references/tool-policy.md +11 -0
- package/skills/math-modeling-agent/references/workflow.md +10 -9
- package/skills/math-modeling-agent/scripts/computation/README.md +20 -0
- package/skills/math-modeling-agent/scripts/computation/backend-inventory.schema.json +78 -0
- package/skills/math-modeling-agent/scripts/computation/backend_inventory.ps1 +351 -0
- package/skills/math-modeling-agent/scripts/computation/backend_inventory.py +322 -0
- package/skills/math-modeling-agent/scripts/computation/computation_record.py +361 -0
- package/skills/math-modeling-agent/scripts/computation/probe_backends.ps1 +396 -0
- package/skills/math-modeling-agent/scripts/computation/probe_backends.py +230 -0
- package/skills/math-modeling-agent/scripts/distribution-parity.mjs +123 -0
package/README.md
CHANGED
|
@@ -71,6 +71,10 @@ NOT_CHECKED → DERIVED → EXECUTED → VERIFIED → INDEPENDENTLY_VERIFIED →
|
|
|
71
71
|
|
|
72
72
|
工具只是产生证据的方式,可以替换,工作流不变。
|
|
73
73
|
|
|
74
|
+
本插件保留原 GitHub 项目的核心行为:问题/附件探查、子问题顺序求解、Modeler → Analyzer → Correction 迭代、逐轮日志和可恢复运行;对应关系见 `skills/math-modeling-agent/references/original-project-parity.md`。
|
|
75
|
+
|
|
76
|
+
数学计算后端扩展位于 `skills/math-modeling-agent/scripts/computation/`:它只负责探测可调用后端、生成能力快照和校验计算记录,不捆绑 Mathematica/SageMath 等外部引擎。
|
|
77
|
+
|
|
74
78
|
- **Python** 可选(推荐):需要计算时在运行目录内创建隔离环境(数值计算、数据分析、优化、仿真、绘图、独立重算)
|
|
75
79
|
- **Lean** 可选:形式化验证;不自动安装;**形式命题被证明 ≠ 现实主张被证明**
|
|
76
80
|
- **Wolfram** 可选:符号计算、解析推导、恒等式验证
|
|
@@ -108,7 +112,7 @@ D3 方向/D4 裁决),`run-state.mjs gate` 在每次状态转移前强制校
|
|
|
108
112
|
安装:
|
|
109
113
|
|
|
110
114
|
```bash
|
|
111
|
-
dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.
|
|
115
|
+
dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.4.1
|
|
112
116
|
dsh --profile web --dump-config # 检查组合层(应看到 dsh-math-modeling-agent-skills 行)
|
|
113
117
|
dsh web # 重启以加载插件
|
|
114
118
|
```
|
|
@@ -118,6 +122,17 @@ dsh web # 重启以加载插件
|
|
|
118
122
|
不要用 `npm install` 代替,那会装到错误位置。如本机镜像源同步滞后,可显式走官方源:
|
|
119
123
|
`dsh plugin --profile web add dsh-math-modeling-agent --registry=https://registry.npmjs.org/`)
|
|
120
124
|
|
|
125
|
+
维护者在发布后应分别核对 GitHub 解包目录、npm tarball 解包目录和 profile
|
|
126
|
+
安装目录:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
node skills/math-modeling-agent/scripts/distribution-parity.mjs <source-root> <candidate-root>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
该命令比较 package.json `files` allowlist 内所有文件的 SHA-256;它不把 AI
|
|
133
|
+
解题中的偶发失败当作分发失败。`dsh --profile web --dump-config` 用于检查组合,
|
|
134
|
+
但新版 DSH 可能重写 profile 的空 `cordis.yml`,不是严格只读操作。
|
|
135
|
+
|
|
121
136
|
开始建模——直接描述任务即可。每个环节都是人与 LLM 的深度信息交换:LLM 给出带依据链的
|
|
122
137
|
完整分析(题面原句/数据证据/文献/显式判断标记),你纠正、补充背景或提供自己的参考文献,
|
|
123
138
|
LLM 更新并展示差异。建模前必须先做文献调研(AI 检索原理与方法文献,你可增删),每个候选
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: math-modeling-agent
|
|
3
|
-
description:
|
|
4
|
-
whenToUse:
|
|
3
|
+
description: 当用户要求分析数学建模题、读取题目附件、建立预测/优化/估计/仿真模型、比较建模方向、执行数值验证、进行鲁棒性分析或继续已有建模项目时使用。
|
|
4
|
+
whenToUse: 用于开放式数学建模、预测、优化、估计、仿真、机制分析、决策和多小问竞赛题;已有成果的独立审计改用 math-modeling-audit,不在此处静默改写被审计成果。
|
|
5
5
|
user-invocable: true
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -17,7 +17,7 @@ Accept problem text, attachments, or an existing run directory.
|
|
|
17
17
|
|
|
18
18
|
Interaction model (references/interaction-protocol.md): execution details stay autonomous (file names, tool adapters, state fields), but every model-shaping decision is a DEEP three-beat exchange — LLM presents a complete analysis with its evidence chain, the user responds with substance (corrections, background, their own references), the LLM updates the artifact and shows the delta. Decision points: D0 six-section restatement / D1 routing once per task, D-R literature research before any candidate is built, D2 assumptions and D3 direction per subproblem, D2' on mid-subproblem discoveries, D4 verdict per round. Show the runlog digest in the chat after every round (references/report-contract.md); announce one status line before every transition; the user may interrupt at any time.
|
|
19
19
|
|
|
20
|
-
Return a concise current status, supported claims, unresolved obligations, attempt delta, artifact path, and next action or final report.
|
|
20
|
+
Return a concise current status, supported claims, unresolved obligations, attempt delta, artifact path, and next action or final report. The legacy Modeler → Analyzer → Correction behavior mapping is recorded in `references/original-project-parity.md`.
|
|
21
21
|
|
|
22
22
|
## Default workflow
|
|
23
23
|
|
|
@@ -47,6 +47,19 @@ Return a concise current status, supported claims, unresolved obligations, attem
|
|
|
47
47
|
- Continue only when a round closes an obligation, adds reproducible evidence, removes a blocker, tightens uncertainty, or refutes a candidate.
|
|
48
48
|
- The run directory stays clean at every moment, and every failed path is preserved in its own folder (references/run-directory.md).
|
|
49
49
|
|
|
50
|
+
## Computation tool extension
|
|
51
|
+
|
|
52
|
+
When a computation is required, first call the bundled backend probe:
|
|
53
|
+
`python scripts/computation/probe_backends.py`
|
|
54
|
+
(or the `.ps1` entrypoint when Python is unavailable). Choose only a backend
|
|
55
|
+
reported callable; record the primary route and fallback. Use
|
|
56
|
+
`scripts/computation/backend_inventory.py` with a run-local `--state-file`
|
|
57
|
+
when a persistent capability snapshot is needed. For reproducible/file or
|
|
58
|
+
high-assurance work, initialize and validate
|
|
59
|
+
`scripts/computation/computation_record.py`. These scripts discover and record
|
|
60
|
+
external backends; they are not mathematical engines and their successful
|
|
61
|
+
execution is not proof.
|
|
62
|
+
|
|
50
63
|
## Tool degradation
|
|
51
64
|
|
|
52
65
|
Python is recommended but not required for installation. Lean and Wolfram are optional. If a required tool is unavailable, weaken the claim and report the missing obligation; never pretend execution or formal verification occurred.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Original GitHub workflow parity
|
|
2
|
+
|
|
3
|
+
The DSH bundle is a faithful workflow migration of the original
|
|
4
|
+
MathModelingAgent project, not a replacement of its useful behavior.
|
|
5
|
+
|
|
6
|
+
| Original GitHub behavior | DSH realization |
|
|
7
|
+
|---|---|
|
|
8
|
+
| PDF/problem extraction in `code/agent.py` | intake D0 and the subproblem DAG |
|
|
9
|
+
| Excel attachment profiling in `code/data_analyzer.py` | INPUT_PROFILED and the run input manifest |
|
|
10
|
+
| sequential subproblem solving | dependency-ordered SPi loop |
|
|
11
|
+
| Modeler → Analyzer → Correction loop in `code/prompts.py` | explorer/modeler → verifier/analyzer → corrector/revise |
|
|
12
|
+
| per-round thinking and run logs | attempts/<n>/report.md, events.jsonl, and chat runlog digest |
|
|
13
|
+
| acceptance/rejection and stagnation control | D4 verdict, progress test, REVISE/FORK, and resumable non-SOLVED states |
|
|
14
|
+
| hash-based cache and resume | run-local artifacts, evidence links, and state recovery |
|
|
15
|
+
|
|
16
|
+
The migration deliberately replaces private chain-of-thought storage with
|
|
17
|
+
human-readable reasoning, evidence, and reproducible artifacts. The roles and
|
|
18
|
+
loop semantics remain; only the execution substrate changes from the legacy
|
|
19
|
+
Python CLI to DSH skills and state files.
|
|
@@ -7,8 +7,9 @@ produce reports and evidence. Never let a subagent mutate run-state directly.
|
|
|
7
7
|
|
|
8
8
|
| Class | Role (lineage) | When | Count |
|
|
9
9
|
|---|---|---|---|
|
|
10
|
-
| explorer | parallel candidate exploration (
|
|
11
|
-
| verifier | blind re-verification (
|
|
10
|
+
| explorer | parallel candidate exploration (original Modeler lineage) | CANDIDATES_READY, >= 2 independent directions | 1 per direction |
|
|
11
|
+
| verifier | blind re-verification (original Analyzer lineage) | before terminal: key numeric/feasibility claims | Standard: 1 per task; High-Assurance: 1 per claim |
|
|
12
|
+
| corrector | apply accepted critique and produce the next attempt (original Correction lineage) | after a failed/conditional verdict | 1 per selected direction |
|
|
12
13
|
| auditor | artifact-only audit (math-modeling-audit skill) | before SOLVED | High-Assurance: mandatory; else optional |
|
|
13
14
|
|
|
14
15
|
## Brief template (self-contained; subagents see no parent context)
|
|
@@ -22,6 +22,17 @@ e.g. "本阶段用 Python+OSQP 做约束优化;Wolfram 对本题非必需,
|
|
|
22
22
|
The problem never silently depends on a single tool: every phase names its
|
|
23
23
|
backend, its fallback, and the degradation path when the backend fails.
|
|
24
24
|
|
|
25
|
+
## Computation backend extension
|
|
26
|
+
|
|
27
|
+
Before selecting an external computation backend, run
|
|
28
|
+
`scripts/computation/probe_backends.py` (or its PowerShell compatibility entry)
|
|
29
|
+
and use only the live capability result. Persist a backend snapshot with
|
|
30
|
+
`scripts/computation/backend_inventory.py` and an explicit run-local
|
|
31
|
+
`--state-file` when the phase needs a stable inventory. Use
|
|
32
|
+
`scripts/computation/computation_record.py` for reproducible/file or
|
|
33
|
+
high-assurance computation. A probe, inventory, or record validation does not
|
|
34
|
+
prove a mathematical claim.
|
|
35
|
+
|
|
25
36
|
## Python
|
|
26
37
|
|
|
27
38
|
Use `scripts/python-environment.mjs`. Prefer uv, then an existing Python runtime plus venv. Keep the environment under the run directory. Install only required normalized PyPI package names; VCS URLs, local wheels, or arbitrary indexes require explicit user approval. Record lock, seed, command, workdir, timeout, stdout/stderr, exit code, and generated files.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## States
|
|
10
10
|
|
|
11
|
-
TRIAGE → SCOPE_FROZEN → INPUT_PROFILED → CLAIMS_REGISTERED → CANDIDATES_READY → ATTEMPT → EXECUTE → VERIFY.
|
|
11
|
+
TRIAGE → RESEARCH → SCOPE_FROZEN → INPUT_PROFILED → CLAIMS_REGISTERED → CANDIDATES_READY → ATTEMPT → EXECUTE → VERIFY.
|
|
12
12
|
|
|
13
13
|
VERIFY may move to REVISE, RESEARCH, FORK, an independent audit, or one terminal status:
|
|
14
14
|
SOLVED, PARTIAL, CONDITIONAL, INCONCLUSIVE, REFUTED, INFEASIBLE, UNIDENTIFIABLE, BLOCKED, CANCELLED.
|
|
@@ -19,16 +19,17 @@ SOLVED, PARTIAL, CONDITIONAL, INCONCLUSIVE, REFUTED, INFEASIBLE, UNIDENTIFIABLE,
|
|
|
19
19
|
- **逐问层**(SPi 循环):子问题分析 → 子问题假设 Si(D2-SPi)→ ATTEMPT 建模求解 → VERIFY 该问验证 → 该问鲁棒性(扰动计划→交互→执行)→ D4 裁决 → REVISE(本问修订)或进入 SPi+1(reason: "SPi 完成,进入 SPi+1")。
|
|
20
20
|
- **收束层**:跨问联动检查(终态 gate 校验全部子问题 DONE + 依赖满足)→ 全题鲁棒性汇总 → 终态硬门 → SOLVED + final-report(整体叙事,非拼凑)。
|
|
21
21
|
|
|
22
|
-
## Pre-modeling literature research (
|
|
22
|
+
## Pre-modeling literature research (mandatory)
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
TRIAGE → RESEARCH → D-R → SCOPE_FROZEN. After the initial task triage and
|
|
25
|
+
before D0 restatement is frozen, run the D-R exchange
|
|
26
|
+
(interaction-protocol.md): search principles, engineering background, and
|
|
27
|
+
methods by direction; write research/sources.jsonl; attach a literature
|
|
28
|
+
column to the candidate matrix. Every direction must be traceable to at least
|
|
29
|
+
one source. A literature-free direction must be explicitly marked
|
|
30
30
|
"原创方向" with its derivation basis. Never search for solutions to the
|
|
31
|
-
specific contest problem.
|
|
31
|
+
specific contest problem. Mid-solve evidence gaps use a recorded
|
|
32
|
+
RESEARCH backtrack, not an implicit bypass of the initial research phase.
|
|
32
33
|
|
|
33
34
|
## Interaction gates (v2)
|
|
34
35
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# DSH computation tool extension
|
|
2
|
+
|
|
3
|
+
These entrypoints are adapted from the user-provided
|
|
4
|
+
`math-science-computation` toolkit. They are computation-support tools, not
|
|
5
|
+
mathematical engines: they discover callable external backends, persist a
|
|
6
|
+
local capability snapshot, and validate reproducible computation records.
|
|
7
|
+
|
|
8
|
+
## Entrypoints
|
|
9
|
+
|
|
10
|
+
- `probe_backends.py` / `probe_backends.ps1`: read-only discovery of
|
|
11
|
+
Wolfram/Mathematica, primecount, SageMath, Python, and Python library
|
|
12
|
+
availability. External probes are bounded and return structured failures.
|
|
13
|
+
- `backend_inventory.py` / `backend_inventory.ps1`: read or refresh a
|
|
14
|
+
capability snapshot. Pass a run-local `--state-file`/`-StateFile`; do not
|
|
15
|
+
use an implicit shared machine-wide path.
|
|
16
|
+
- `computation_record.py`: initialize or validate a reproducibility record
|
|
17
|
+
for every probeable backend, including primecount.
|
|
18
|
+
|
|
19
|
+
The external backend remains responsible for the actual mathematical
|
|
20
|
+
calculation. A successful probe or record validation is not a proof.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://local.codex.invalid/math-science-computation/backend-inventory.schema.json",
|
|
4
|
+
"title": "Math and science computation local backend inventory",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": [
|
|
7
|
+
"inventory_schema_version",
|
|
8
|
+
"created_at_utc",
|
|
9
|
+
"updated_at_utc",
|
|
10
|
+
"local",
|
|
11
|
+
"mcp",
|
|
12
|
+
"invalidations"
|
|
13
|
+
],
|
|
14
|
+
"properties": {
|
|
15
|
+
"inventory_schema_version": { "const": "1.0" },
|
|
16
|
+
"created_at_utc": { "type": "string", "format": "date-time" },
|
|
17
|
+
"updated_at_utc": { "type": "string", "format": "date-time" },
|
|
18
|
+
"local": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"required": ["schema_version", "probed_at_utc", "host", "mathematica", "primecount", "sagemath", "python"],
|
|
21
|
+
"properties": {
|
|
22
|
+
"schema_version": { "const": "1.0" },
|
|
23
|
+
"probed_at_utc": { "type": "string", "format": "date-time" },
|
|
24
|
+
"host": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"required": ["system", "architecture"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"system": { "enum": ["Windows", "Darwin", "Linux"] },
|
|
29
|
+
"architecture": { "type": "string", "minLength": 1 }
|
|
30
|
+
},
|
|
31
|
+
"additionalProperties": true
|
|
32
|
+
},
|
|
33
|
+
"mathematica": { "type": "object" },
|
|
34
|
+
"primecount": { "type": "object" },
|
|
35
|
+
"sagemath": { "type": "object" },
|
|
36
|
+
"python": { "type": "object" }
|
|
37
|
+
},
|
|
38
|
+
"additionalProperties": true
|
|
39
|
+
},
|
|
40
|
+
"mcp": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"required": ["authority", "persisted_status", "required_action"],
|
|
43
|
+
"properties": {
|
|
44
|
+
"authority": { "const": "current_session_tool_discovery_and_call" },
|
|
45
|
+
"persisted_status": { "const": "historical_only" },
|
|
46
|
+
"required_action": { "type": "string" },
|
|
47
|
+
"mathematica": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"required": ["server_name", "protocol_version", "server_version", "wolfram_language_version", "observed_at_utc", "evidence"],
|
|
50
|
+
"properties": {
|
|
51
|
+
"server_name": { "type": "string", "minLength": 1 },
|
|
52
|
+
"protocol_version": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
|
|
53
|
+
"server_version": { "type": "string", "minLength": 1 },
|
|
54
|
+
"wolfram_language_version": { "type": "string", "minLength": 1 },
|
|
55
|
+
"observed_at_utc": { "type": "string", "format": "date-time" },
|
|
56
|
+
"evidence": { "const": "initialize_handshake_and_evaluator" }
|
|
57
|
+
},
|
|
58
|
+
"additionalProperties": false
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"additionalProperties": false
|
|
62
|
+
},
|
|
63
|
+
"invalidations": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"required": ["backend", "reason", "recorded_at_utc"],
|
|
68
|
+
"properties": {
|
|
69
|
+
"backend": { "enum": ["mathematica", "primecount", "sagemath", "python"] },
|
|
70
|
+
"reason": { "enum": ["user_requested", "path_missing", "execution_failed", "version_mismatch"] },
|
|
71
|
+
"recorded_at_utc": { "type": "string", "format": "date-time" }
|
|
72
|
+
},
|
|
73
|
+
"additionalProperties": false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"additionalProperties": false
|
|
78
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
[CmdletBinding()]
|
|
2
|
+
param(
|
|
3
|
+
[ValidateSet('ReadOrCreate', 'Refresh', 'Invalidate', 'RecordMcp')]
|
|
4
|
+
[string]$Mode = 'ReadOrCreate',
|
|
5
|
+
[string]$StateFile = '',
|
|
6
|
+
[ValidateSet('all', 'mathematica', 'primecount', 'sagemath', 'python')]
|
|
7
|
+
[string[]]$Backend = @('all'),
|
|
8
|
+
[string]$ReasonCode = '',
|
|
9
|
+
[int]$MaxAgeHours = 168,
|
|
10
|
+
[string]$ProbeScript = (Join-Path $PSScriptRoot 'probe_backends.ps1'),
|
|
11
|
+
[string]$ProbeJsonFile = '',
|
|
12
|
+
[string]$PythonCommand = 'python',
|
|
13
|
+
[string]$SageCommand = '',
|
|
14
|
+
[string]$WslDistro = '',
|
|
15
|
+
[string]$WslSageCommand = 'sage',
|
|
16
|
+
[string]$PrimecountCommand = '',
|
|
17
|
+
[string]$McpServerName = '',
|
|
18
|
+
[string]$McpProtocolVersion = '',
|
|
19
|
+
[string]$McpServerVersion = '',
|
|
20
|
+
[string]$McpWolframLanguageVersion = '',
|
|
21
|
+
[string]$McpObservedAtUtc = ''
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
$ErrorActionPreference = 'Stop'
|
|
25
|
+
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
|
26
|
+
if ($Mode -eq 'Invalidate' -and $ReasonCode -notmatch '^[a-z][a-z0-9_]{0,31}$') {
|
|
27
|
+
throw "Invalidate mode requires a bounded lowercase reason code."
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Get-DefaultStateFile {
|
|
31
|
+
if ($env:MATH_SCIENCE_BACKEND_INVENTORY) {
|
|
32
|
+
return $env:MATH_SCIENCE_BACKEND_INVENTORY
|
|
33
|
+
}
|
|
34
|
+
return (Join-Path (Join-Path (Join-Path ([IO.Path]::GetTempPath()) 'DSH') 'math-science-computation') 'backend-inventory.json')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function Get-HostIdentity {
|
|
38
|
+
$system = if ([Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([Runtime.InteropServices.OSPlatform]::Windows)) {
|
|
39
|
+
'Windows'
|
|
40
|
+
}
|
|
41
|
+
elseif ([Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([Runtime.InteropServices.OSPlatform]::OSX)) {
|
|
42
|
+
'Darwin'
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
'Linux'
|
|
46
|
+
}
|
|
47
|
+
$rawArchitecture = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()
|
|
48
|
+
$architecture = switch ($rawArchitecture) {
|
|
49
|
+
'x64' { 'x86_64' }
|
|
50
|
+
'amd64' { 'x86_64' }
|
|
51
|
+
'arm64' { 'arm64' }
|
|
52
|
+
'x86' { 'x86' }
|
|
53
|
+
default { $rawArchitecture }
|
|
54
|
+
}
|
|
55
|
+
return [ordered]@{ system = $system; architecture = $architecture; powershell_edition = $PSVersionTable.PSEdition }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function Read-Inventory {
|
|
59
|
+
param([Parameter(Mandatory = $true)][string]$Path)
|
|
60
|
+
|
|
61
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
62
|
+
return $null
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
$inventory = Get-Content -Raw -LiteralPath $Path | ConvertFrom-Json -AsHashtable
|
|
66
|
+
if ($inventory.inventory_schema_version -ne '1.0' -or $inventory.local.schema_version -ne '1.0') {
|
|
67
|
+
return $null
|
|
68
|
+
}
|
|
69
|
+
return $inventory
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return $null
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function Invoke-LocalProbe {
|
|
77
|
+
if ($ProbeJsonFile) {
|
|
78
|
+
return (Get-Content -Raw -LiteralPath $ProbeJsonFile | ConvertFrom-Json -AsHashtable)
|
|
79
|
+
}
|
|
80
|
+
if (-not (Test-Path -LiteralPath $ProbeScript -PathType Leaf)) {
|
|
81
|
+
throw "Backend probe script is unavailable."
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
$probeArguments = @{
|
|
85
|
+
PythonCommand = $PythonCommand
|
|
86
|
+
SageCommand = $SageCommand
|
|
87
|
+
WslDistro = $WslDistro
|
|
88
|
+
WslSageCommand = $WslSageCommand
|
|
89
|
+
PrimecountCommand = $PrimecountCommand
|
|
90
|
+
}
|
|
91
|
+
$raw = & $ProbeScript @probeArguments
|
|
92
|
+
if ($LASTEXITCODE -notin @($null, 0)) {
|
|
93
|
+
throw "Backend probe failed with a nonzero exit code."
|
|
94
|
+
}
|
|
95
|
+
return (($raw -join "`n") | ConvertFrom-Json -AsHashtable)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function New-Inventory {
|
|
99
|
+
param([Parameter(Mandatory = $true)][hashtable]$Local)
|
|
100
|
+
|
|
101
|
+
$now = [DateTime]::UtcNow.ToString('o')
|
|
102
|
+
return [ordered]@{
|
|
103
|
+
inventory_schema_version = '1.0'
|
|
104
|
+
created_at_utc = $now
|
|
105
|
+
updated_at_utc = $now
|
|
106
|
+
local = $Local
|
|
107
|
+
mcp = [ordered]@{
|
|
108
|
+
authority = 'current_session_tool_discovery_and_call'
|
|
109
|
+
persisted_status = 'historical_only'
|
|
110
|
+
required_action = 'Build a current-session overlay and live-check only the selected MCP backend.'
|
|
111
|
+
}
|
|
112
|
+
invalidations = @()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function New-McpObservation {
|
|
117
|
+
$required = [ordered]@{
|
|
118
|
+
server_name = $McpServerName
|
|
119
|
+
protocol_version = $McpProtocolVersion
|
|
120
|
+
server_version = $McpServerVersion
|
|
121
|
+
wolfram_language_version = $McpWolframLanguageVersion
|
|
122
|
+
}
|
|
123
|
+
$missing = @($required.GetEnumerator() | Where-Object { -not $_.Value.Trim() } | ForEach-Object Key)
|
|
124
|
+
if ($missing.Count -gt 0) {
|
|
125
|
+
throw ('RecordMcp requires: ' + ($missing -join ', '))
|
|
126
|
+
}
|
|
127
|
+
if ($McpProtocolVersion -notmatch '^\d{4}-\d{2}-\d{2}$') {
|
|
128
|
+
throw 'MCP protocol version must use the negotiated YYYY-MM-DD form.'
|
|
129
|
+
}
|
|
130
|
+
$observedAt = if ($McpObservedAtUtc) { $McpObservedAtUtc } else { [DateTime]::UtcNow.ToString('o') }
|
|
131
|
+
try { [void][DateTimeOffset]::Parse($observedAt) }
|
|
132
|
+
catch { throw 'MCP observation time must be an ISO-8601 timestamp.' }
|
|
133
|
+
return [ordered]@{
|
|
134
|
+
server_name = $McpServerName
|
|
135
|
+
protocol_version = $McpProtocolVersion
|
|
136
|
+
server_version = $McpServerVersion
|
|
137
|
+
wolfram_language_version = $McpWolframLanguageVersion
|
|
138
|
+
observed_at_utc = $observedAt
|
|
139
|
+
evidence = 'initialize_handshake_and_evaluator'
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function Write-InventoryAtomic {
|
|
144
|
+
param(
|
|
145
|
+
[Parameter(Mandatory = $true)][hashtable]$Inventory,
|
|
146
|
+
[Parameter(Mandatory = $true)][string]$Path
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
$parent = Split-Path -Parent $Path
|
|
150
|
+
if (-not $parent) {
|
|
151
|
+
$parent = (Get-Location).Path
|
|
152
|
+
$Path = Join-Path $parent $Path
|
|
153
|
+
}
|
|
154
|
+
[IO.Directory]::CreateDirectory($parent) | Out-Null
|
|
155
|
+
$temporary = Join-Path $parent ('.backend-inventory-' + [Guid]::NewGuid().ToString('N') + '.tmp')
|
|
156
|
+
try {
|
|
157
|
+
$json = $Inventory | ConvertTo-Json -Depth 12
|
|
158
|
+
[IO.File]::WriteAllText($temporary, $json, [Text.UTF8Encoding]::new($false))
|
|
159
|
+
[IO.File]::Move($temporary, $Path, $true)
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
if (Test-Path -LiteralPath $temporary -PathType Leaf) {
|
|
163
|
+
Remove-Item -LiteralPath $temporary -Force -ErrorAction SilentlyContinue
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function Get-MissingBackendPaths {
|
|
169
|
+
param([Parameter(Mandatory = $true)][hashtable]$Inventory)
|
|
170
|
+
|
|
171
|
+
$missing = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
|
|
172
|
+
foreach ($installation in @($Inventory.local.mathematica.installations)) {
|
|
173
|
+
if ($installation.executable -and -not (Test-Path -LiteralPath $installation.executable -PathType Leaf)) {
|
|
174
|
+
[void]$missing.Add('mathematica')
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
$checks = @(
|
|
178
|
+
@('mathematica', $Inventory.local.mathematica.wolframscript.path),
|
|
179
|
+
@('primecount', $Inventory.local.primecount.path),
|
|
180
|
+
@('sagemath', $Inventory.local.sagemath.native.path),
|
|
181
|
+
@('python', $Inventory.local.python.path)
|
|
182
|
+
)
|
|
183
|
+
foreach ($check in $checks) {
|
|
184
|
+
if ($check[1] -and -not (Test-Path -LiteralPath $check[1] -PathType Leaf)) {
|
|
185
|
+
[void]$missing.Add([string]$check[0])
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return @($missing)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function Test-InventoryExpired {
|
|
192
|
+
param([Parameter(Mandatory = $true)][hashtable]$Inventory)
|
|
193
|
+
|
|
194
|
+
if ($MaxAgeHours -le 0) {
|
|
195
|
+
return $false
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
$updated = [DateTimeOffset]::Parse([string]$Inventory.updated_at_utc)
|
|
199
|
+
return ([DateTimeOffset]::UtcNow - $updated).TotalHours -ge $MaxAgeHours
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return $true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function Test-InventoryHostMismatch {
|
|
207
|
+
param([Parameter(Mandatory = $true)][hashtable]$Inventory)
|
|
208
|
+
|
|
209
|
+
$current = Get-HostIdentity
|
|
210
|
+
$stored = $Inventory.local.host
|
|
211
|
+
if (-not $stored) {
|
|
212
|
+
return $true
|
|
213
|
+
}
|
|
214
|
+
return ($stored.system -ne $current.system -or $stored.architecture -ne $current.architecture)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function Merge-Backends {
|
|
218
|
+
param(
|
|
219
|
+
[Parameter(Mandatory = $true)][hashtable]$Inventory,
|
|
220
|
+
[Parameter(Mandatory = $true)][hashtable]$FreshLocal,
|
|
221
|
+
[Parameter(Mandatory = $true)][string[]]$Names
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
$selected = if ($Names -contains 'all') {
|
|
225
|
+
@('mathematica', 'primecount', 'sagemath', 'python')
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
@($Names | Select-Object -Unique)
|
|
229
|
+
}
|
|
230
|
+
foreach ($name in $selected) {
|
|
231
|
+
$Inventory.local[$name] = $FreshLocal[$name]
|
|
232
|
+
}
|
|
233
|
+
$Inventory.local.probed_at_utc = $FreshLocal.probed_at_utc
|
|
234
|
+
$Inventory.local.host = $FreshLocal.host
|
|
235
|
+
$Inventory.updated_at_utc = [DateTime]::UtcNow.ToString('o')
|
|
236
|
+
return $Inventory
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function Write-Result {
|
|
240
|
+
param(
|
|
241
|
+
[Parameter(Mandatory = $true)][hashtable]$Inventory,
|
|
242
|
+
[Parameter(Mandatory = $true)][string]$CacheStatus,
|
|
243
|
+
[string[]]$RefreshedBackends = @(),
|
|
244
|
+
[string[]]$InvalidPaths = @(),
|
|
245
|
+
[bool]$BackendStarted = $false,
|
|
246
|
+
[string]$WriteError = ''
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
$stopwatch.Stop()
|
|
250
|
+
$output = [ordered]@{
|
|
251
|
+
inventory_schema_version = $Inventory.inventory_schema_version
|
|
252
|
+
snapshot_updated_at_utc = $Inventory.updated_at_utc
|
|
253
|
+
cache = [ordered]@{
|
|
254
|
+
status = $CacheStatus
|
|
255
|
+
state_file = $StateFile
|
|
256
|
+
elapsed_ms = $stopwatch.ElapsedMilliseconds
|
|
257
|
+
backend_started = $BackendStarted
|
|
258
|
+
refreshed_backends = @($RefreshedBackends)
|
|
259
|
+
invalid_path_backends = @($InvalidPaths)
|
|
260
|
+
write_error = $WriteError
|
|
261
|
+
}
|
|
262
|
+
local = $Inventory.local
|
|
263
|
+
mcp = [ordered]@{
|
|
264
|
+
status = 'session_probe_required'
|
|
265
|
+
authority = 'current_session_tool_discovery_and_call'
|
|
266
|
+
note = 'The persisted snapshot is not evidence that an MCP tool is callable in this session.'
|
|
267
|
+
recorded_mathematica_observation = $Inventory.mcp.mathematica
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
$output | ConvertTo-Json -Depth 12 -Compress
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (-not $StateFile) {
|
|
274
|
+
$StateFile = Get-DefaultStateFile
|
|
275
|
+
}
|
|
276
|
+
$StateFile = [IO.Path]::GetFullPath($StateFile)
|
|
277
|
+
$inventory = Read-Inventory -Path $StateFile
|
|
278
|
+
$missingBackends = @()
|
|
279
|
+
|
|
280
|
+
if ($Mode -eq 'RecordMcp') {
|
|
281
|
+
$observation = New-McpObservation
|
|
282
|
+
$backendStarted = $false
|
|
283
|
+
if (-not $inventory) {
|
|
284
|
+
$freshLocal = Invoke-LocalProbe
|
|
285
|
+
if ($freshLocal.schema_version -ne '1.0') { throw 'Unsupported backend probe schema.' }
|
|
286
|
+
if ($freshLocal.mathematica -is [Collections.IDictionary] -and $freshLocal.mathematica.Contains('mcp')) {
|
|
287
|
+
[void]$freshLocal.mathematica.Remove('mcp')
|
|
288
|
+
}
|
|
289
|
+
$inventory = New-Inventory -Local $freshLocal
|
|
290
|
+
$backendStarted = $true
|
|
291
|
+
}
|
|
292
|
+
$inventory.mcp.mathematica = $observation
|
|
293
|
+
$inventory.updated_at_utc = [DateTime]::UtcNow.ToString('o')
|
|
294
|
+
Write-InventoryAtomic -Inventory $inventory -Path $StateFile
|
|
295
|
+
Write-Result -Inventory $inventory -CacheStatus 'mcp_recorded' -BackendStarted $backendStarted
|
|
296
|
+
exit 0
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if ($Mode -eq 'ReadOrCreate' -and $inventory) {
|
|
300
|
+
$missingBackends = @(Get-MissingBackendPaths -Inventory $inventory)
|
|
301
|
+
if ($missingBackends.Count -eq 0 -and -not (Test-InventoryExpired -Inventory $inventory) -and -not (Test-InventoryHostMismatch -Inventory $inventory)) {
|
|
302
|
+
Write-Result -Inventory $inventory -CacheStatus 'hit' -BackendStarted $false
|
|
303
|
+
exit 0
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
$freshLocal = Invoke-LocalProbe
|
|
308
|
+
if ($freshLocal.schema_version -ne '1.0') {
|
|
309
|
+
throw "Unsupported backend probe schema."
|
|
310
|
+
}
|
|
311
|
+
if ($freshLocal.mathematica -is [Collections.IDictionary] -and $freshLocal.mathematica.Contains('mcp')) {
|
|
312
|
+
[void]$freshLocal.mathematica.Remove('mcp')
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
$refreshed = @('mathematica', 'primecount', 'sagemath', 'python')
|
|
316
|
+
$cacheStatus = 'created'
|
|
317
|
+
if (-not $inventory) {
|
|
318
|
+
$inventory = New-Inventory -Local $freshLocal
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
$cacheStatus = 'refreshed'
|
|
322
|
+
if ($Mode -eq 'Invalidate') {
|
|
323
|
+
$targets = if ($Backend -contains 'all') { @('mathematica', 'primecount', 'sagemath', 'python') } else { @($Backend) }
|
|
324
|
+
$inventory.invalidations = @($inventory.invalidations) + @($targets | ForEach-Object {
|
|
325
|
+
[ordered]@{ backend = $_; reason = $ReasonCode; recorded_at_utc = [DateTime]::UtcNow.ToString('o') }
|
|
326
|
+
})
|
|
327
|
+
if ($inventory.invalidations.Count -gt 20) {
|
|
328
|
+
$inventory.invalidations = @($inventory.invalidations | Select-Object -Last 20)
|
|
329
|
+
}
|
|
330
|
+
$refreshed = $targets
|
|
331
|
+
}
|
|
332
|
+
elseif ($Mode -eq 'Refresh') {
|
|
333
|
+
$refreshed = if ($Backend -contains 'all') { @('mathematica', 'primecount', 'sagemath', 'python') } else { @($Backend) }
|
|
334
|
+
}
|
|
335
|
+
elseif ($missingBackends.Count -gt 0) {
|
|
336
|
+
$refreshed = $missingBackends
|
|
337
|
+
}
|
|
338
|
+
$inventory = Merge-Backends -Inventory $inventory -FreshLocal $freshLocal -Names $refreshed
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
$writeError = ''
|
|
342
|
+
try {
|
|
343
|
+
Write-InventoryAtomic -Inventory $inventory -Path $StateFile
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
$writeError = $_.Exception.GetType().Name
|
|
347
|
+
$cacheStatus = 'write_failed'
|
|
348
|
+
}
|
|
349
|
+
Write-Result -Inventory $inventory -CacheStatus $cacheStatus -RefreshedBackends $refreshed -InvalidPaths $missingBackends -BackendStarted $true -WriteError $writeError
|
|
350
|
+
if ($writeError) { exit 1 }
|
|
351
|
+
exit 0
|