dsh-mcmp 1.0.0 → 2.0.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/README.md CHANGED
@@ -1,15 +1,21 @@
1
- # mcmp-1 · 数学建模论文自动化流水线
1
+ # dsh-mcmp · 数学建模论文自动化流水线(v2)
2
2
 
3
- DeepSeek Harness **动态 Cordis 插件**:面向全国大学生数学建模竞赛论文撰写的全自动流水线。
3
+ [![npm](https://img.shields.io/badge/npm-dsh--mcmp-blue)](https://www.npmjs.com/package/dsh-mcmp)
4
+
5
+ DeepSeek Harness **持久化 Cordis 插件**:面向全国大学生数学建模竞赛论文撰写的全自动流水线。
4
6
  粘贴赛题 → 发送一条以 `/loopbegin` 开头的消息 → 插件按 8 大步骤、每步多次迭代自动完成论文撰写,
5
7
  全程由专家子智能体「先质疑、再迭代」驱动,成果全部落盘保存,右下角浮动面板实时显示进度百分比。
8
+ **无论流水线正常完成、出错还是被中止,最终都会生成一版完整、干净的 Markdown 论文(`论文定稿.md`)。**
6
9
 
7
10
  ## 功能特性
8
11
 
9
12
  - **消息触发**:输入文本以 `/loopbegin` 开头即自动运行(兼容斜杠命令路径)
10
13
  - **8 大步骤 × 多迭代**:每轮 19 次迭代,外循环轮数可配置(`--round=N`,默认 1 轮)
11
14
  - **质疑驱动**:每个子智能体必须先对上一版成果提出 ≥3 条质疑,再开始本轮工作
12
- - **成果全落盘**:每步迭代完整追加保存(含质疑清单),旧内容绝不覆盖;代码保留全部历史版本
15
+ - **图表视觉自检与修复**:S5 步骤自动探测识图插件——检测到识图服务/工具(如 vision_describe、read_image)即自动调用逐张检查定稿图;没有识图能力则让子智能体编写脚本做文本包围盒重叠/越界/乱码分析,发现问题必须**修复并复检**(A 级问题清零),而不是只写报告
16
+ - **评审问题修复闭环**:S7 合并"合规检查 + 评审",建立《问题跟踪表》,每个 A/B 级问题必须在论文/图表/代码中**实际修复**并经复核验证,禁止"只写问题不修复"
17
+ - **论文兜底定稿**:S8 组装完整、可直接提交的 `论文定稿.md`;即使流水线提前出错或中止,也会追加一次兜底定稿
18
+ - **成果全落盘**:每步迭代完整追加保存(含质疑清单),旧内容绝不覆盖;代码保留全部历史版本;全新运行时旧目录自动归档为 `数学建模流水线_历史归档_*`
13
19
  - **实时进度面板**:独立浮动窗口(可拖拽/最小化/关闭),显示百分比进度条、当前轮/步骤/迭代、8 步骤打点、日志、产出文件,支持一键中止
14
20
  - **原生工作流卡片**:聊天区实时显示每个子智能体的运行状态(按阶段分组)
15
21
 
@@ -17,14 +23,14 @@ DeepSeek Harness **动态 Cordis 插件**:面向全国大学生数学建模竞
17
23
 
18
24
  | 步骤 | 内容 | 迭代次数 |
19
25
  |------|------|----------|
20
- | S1 | 赛题分析 | 3 次(初步拆解→深度分析→最终定调) |
21
- | S2 | 模型求解 | 3 次(模型建立→模型求解→模型验证) |
22
- | S3 | 编程实现 | 3 次(核心代码→完善调试→优化封装) |
23
- | S4 | 图表生成 | 3 次(基础图表→美化优化→精选定稿) |
24
- | S5 | 流程与架构图 | 3 次(结构设计→细节完善→美化定稿) |
25
- | S6 | 论文撰写 | 2 次(初稿→深化润色) |
26
- | S7 | 编译与合规检查 | 1 次 |
27
- | S8 | 评审改进循环 | 1 次 |
26
+ | S1 | 赛题分析与建模方案 | 2 次(赛题精读与拆解→建模方案定稿) |
27
+ | S2 | 模型建立与求解 | 3 次(模型建立→模型求解→模型验证设计) |
28
+ | S3 | 编程实现与数值验证 | 3 次(核心代码→调试与数值验证→封装与自测) |
29
+ | S4 | 图表与流程图生成 | 3 次(基础图表与结构图→美化统一→精选定稿) |
30
+ | S5 | 图表视觉自检与修复 | 2 次(视觉自检与问题清单→修复与复检) |
31
+ | S6 | 论文撰写 | 2 次(初稿→润色终稿) |
32
+ | S7 | 评审与修复落实 | 3(合规检查与评审→修复落实→修复复核) |
33
+ | S8 | 论文定稿与提交包 | 1 次(必产出完整 `论文定稿.md`) |
28
34
 
29
35
  ## 使用方法
30
36
 
@@ -48,100 +54,46 @@ DeepSeek Harness **动态 Cordis 插件**:面向全国大学生数学建模竞
48
54
 
49
55
  ## 安装方式
50
56
 
51
- > 本仓库根目录**就是插件包本体**(`package.json` + `lib/`),与部署目录同构。
52
-
53
- ### 方式一:`dsh plugin add` 安装(推荐,需插件已发布到 npm)
57
+ > 插件包已发布到 npm([dsh-mcmp](https://www.npmjs.com/package/dsh-mcmp)),并声明了 `dsh.bundle`
58
+ > 补丁:安装后**自动注册配置行**,无需任何手动步骤。
54
59
 
55
- 与官方插件相同的安装体验,一条命令安装、注册:
60
+ ### 安装(唯一方式)
56
61
 
57
62
  ```powershell
58
- # 1. 安装插件包(等价于在 profile 目录执行 pnpm add,链接进 profile 的 node_modules)
63
+ # 一条命令完成:安装包 + 自动注册(通过 bundle 机制)
59
64
  pnpm dsh plugin --profile web add dsh-mcmp
60
- # 其他等价的调用形式:
61
- # dsh plugin --profile web add dsh-mcmp
62
- # npx @deepseek-ai/dsh plugin --profile web add dsh-mcmp
63
-
64
- # 2. 注册配置行(在本仓库目录执行)
65
- .\deploy.ps1 -RowOnly
66
-
67
- # 3. 重启并刷新
68
- # 重启 pnpm dsh web → 刷新网页 → 访问 /mcmp-api/state 返回 JSON 即成功
69
- ```
70
-
71
- **卸载**:
65
+ # 等价调用形式:
66
+ # dsh plugin --profile web add dsh-mcmp
67
+ # npx @deepseek-ai/dsh plugin --profile web add dsh-mcmp
72
68
 
73
- ```powershell
74
- pnpm dsh plugin --profile web remove dsh-mcmp # 移除包
75
- # 并删除 cordis.patch.yml 中的插件行(或恢复 cordis.patch.yml.bak)
69
+ # 然后重启:
70
+ # 重启 pnpm dsh web 刷新网页
71
+ # 验证:浏览器访问 /mcmp-api/state,返回 JSON 即成功
76
72
  ```
77
73
 
78
- ### 方式二:本地一键安装(插件未发布到 npm 时)
74
+ **升级**:
79
75
 
80
76
  ```powershell
81
- .\deploy.ps1
82
- ```
83
-
84
- 脚本自动完成:复制插件文件 → 建立 node_modules junction → 注册配置行,然后重启 `dsh web` 并刷新网页即可。手动步骤如下:
85
-
86
- **1. 放置插件包**
87
-
88
- 把仓库根目录的 `package.json` 与 `lib/` 复制到配置层目录:
89
-
90
- ```
91
- $DSH_HOME/profiles/web/plugins/dsh-mcmp/
92
- ├── package.json # dsh.client 声明 + 包入口
93
- └── lib/
94
- ├── index.js # Host 半:命令、编排、断点续跑、/mcmp-api 路由
95
- └── client.js # Client 半:浮动进度面板(__ModuleLoader__ bundle)
77
+ pnpm dsh plugin --profile web update dsh-mcmp
96
78
  ```
97
79
 
98
- **2. 建立包解析链接**(Windows 无需管理员权限)
99
-
100
- ```cmd
101
- mklink /J "%USERPROFILE%\.dsh\profiles\node_modules\dsh-mcmp" "%USERPROFILE%\.dsh\profiles\web\plugins\dsh-mcmp"
102
- ```
103
-
104
- **3. 在配置层注册插件行**
105
-
106
- 编辑 `$DSH_HOME/profiles/web/cordis.patch.yml`,追加(注意必须用 `insert:` 列表语法):
107
-
108
- ```yaml
109
- - insert:
110
- - id: mcmp
111
- name: dsh-mcmp
112
- ```
113
-
114
- **4. 重启并验证**
115
-
116
- - 重启 `dsh web`,刷新网页(插件随启动加载,面板 bundle 注入启动清单);
117
- - 验证接口:浏览器访问 `http://127.0.0.1:3080/mcmp-api/state`,返回 JSON 即安装成功;
118
- - 之后任何重启都无需再安装,`/loopbegin` 永久可用。
119
-
120
- ### 发布到 npm(维护者)
80
+ **卸载**:
121
81
 
122
82
  ```powershell
123
- npm login # 首次需要登录(注册表账号)
124
- npm publish # 发布后即可用「方式一」安装
83
+ pnpm dsh plugin --profile web remove dsh-mcmp
125
84
  ```
126
85
 
127
- ### 方式三:动态插件安装(临时,仅当前进程)
128
-
129
- 适合试用与开发调试;DSH 重启后自动消失,需重新安装:
130
-
131
- 1. 把 `legacy/dynamic-host.js` 内容作为 `code.host`、`legacy/dynamic-client.js` 内容作为 `code.client`,通过 `cordis_define` 定义插件;
132
- 2. `cordis_run` 激活(客户端包需在界面批准);
133
- 3. 发送以 `/loopbegin` 开头的消息即可使用。
134
-
135
86
  ## 输出目录(工作区 `数学建模流水线/`)
136
87
 
137
88
  ```
138
89
  数学建模流水线/
139
90
  ├── 00_赛题原文.md # 启动时自动保存的赛题
140
- ├── 01_赛题分析.md ~ 08_评审改进.md # 八大步骤完整成果(逐迭代追加,含质疑清单)
91
+ ├── 01_赛题分析.md ~ 08_论文定稿.md # 八大步骤完整成果(逐迭代追加,含质疑清单/问题跟踪表)
92
+ ├── 论文定稿.md # ★ 最终交付:完整、干净的 Markdown 论文(无论成败都会生成)
141
93
  ├── 代码/ # 全部验证与求解代码(_vN 后缀保留历史版本,绝不删除)
142
- ├── 图表/ # 全部图表(含归档)
143
- ├── 论文/ # LaTeX 源码(若环境支持)
144
- └── 流水线说明.md # 目录说明
94
+ │ └── results/ # 权威结构化结果(CSV/JSON + 结果索引)
95
+ ├── 图表/ # 全部图表(定稿/ 为论文插图,归档/ 为冗余图)
96
+ └── 数学建模流水线_历史归档_*/ # 全新运行时,上一次运行成果自动归档(改名,不删除)
145
97
  ```
146
98
 
147
99
  ## 架构
@@ -152,9 +104,11 @@ npm publish # 发布后即可用「方式一」安装
152
104
 
153
105
  Host 插件(dsh-mcmp,HOST 组合)
154
106
  ├─ 提取赛题(对话文本或 --from 文件)
155
- ├─ 断点续跑:扫描会话日志中 tool-workflow/* 记录,跳过已完成迭代
107
+ ├─ 识图能力探测(宿主服务/工具注册表扫描;S5 子智能体再自检,以自检为准)
108
+ ├─ 断点续跑:扫描会话日志中 tool-workflow/* v2 记录,跳过已完成迭代
156
109
  ├─ 编排器:按 8 步骤 × 19 迭代 × N 轮逐个启动专家子智能体(subagents.spawn)
157
110
  │ 子智能体:读必读文件 → 质疑先行 → 完成任务 → 追加落盘 → 返回总结
111
+ ├─ 兜底定稿:若 S8 未完成(出错/中止),追加一次子智能体组装完整 论文定稿.md
158
112
  ├─ 向会话追加 tool-workflow/* 事件 → 聊天区原生工作流卡片
159
113
  └─ 面板 API:webServer 路由 /mcmp-api/{state,abort,reset}
160
114
 
@@ -165,26 +119,14 @@ Client 插件(dsh-mcmp,标准 __ModuleLoader__ bundle)
165
119
 
166
120
  ## 文件说明
167
121
 
168
- > 仓库根目录即插件包本体,与部署目录 `$DSH_HOME/profiles/web/plugins/dsh-mcmp/` 同构。
122
+ > 仓库根目录即插件包本体(发布到 npm 的内容 = `lib/` + `cordis.patch.yml` + `package.json`)。
169
123
 
170
124
  | 文件 | 内容 |
171
125
  | --- | --- |
172
- | `package.json` | 插件包清单:`dsh.client` 声明、`exports` 入口(`.` → Host,`./client` → 面板 bundle) |
173
- | `lib/index.js` | **Host 半**:命令注册、断点续跑、Host 侧子智能体编排、`/mcmp-api` 面板路由 |
126
+ | `package.json` | 插件包清单:`dsh.bundle` 补丁声明、`dsh.client` 声明、`exports` 入口(`.` → Host,`./client` → 面板 bundle) |
127
+ | `cordis.patch.yml` | **bundle 补丁**:安装时自动注册插件行(`insert: mcmp`),无需手动编辑配置 |
128
+ | `lib/index.js` | **Host 半**:命令注册、识图能力探测、断点续跑、Host 侧子智能体编排(8 步骤 + 兜底定稿)、`/mcmp-api` 面板路由 |
174
129
  | `lib/client.js` | **Client 半**:浮动进度面板(标准 `__ModuleLoader__` bundle,`fetch` 轮询) |
175
- | `deploy.ps1` | 一键部署脚本:复制插件文件 → 建 junction → 注册插件行 |
176
- | `legacy/` | 历史动态插件源码(`dynamic-host.js` / `dynamic-client.js`),仅当前进程临时安装时参考 |
177
-
178
- ## 版本历史
179
-
180
- - **v1(pkg-1)**:斜杠命令触发(`/loopbegin`),8 步骤流水线、进度面板、成果落盘
181
- - **v2(pkg-2)**:新增消息内容触发(输入文本以 `/loopbegin` 开头即运行),注册模型提示避免重复执行
182
- - **v3(pkg-3)**:修复「工作流引擎不可用」——引擎从 Host 上下文三级回退到 Agent 上下文获取;工作流事件改为全局监听;`--from` 支持含空格/中文文件名;赛题随工作流参数传递、由 S1 首迭代落盘
183
- - **v4(pkg-4)**:重构为 **Host 侧直接编排子智能体**(`subagents` 服务),彻底摆脱工作流引擎的 Agent 上下文依赖——关闭/刷新聊天窗口不再中断流水线,后台继续执行;启动逻辑全部同步化,命令通道永不阻塞
184
- - **v5(pkg-5)**:移除 `maxDepth: 0` 参数(该语义会拒绝所有子智能体创建),改用部署默认深度限制
185
- - **v6(pkg-6)**:①**断点续跑**——启动时扫描会话日志中落盘的工作流记录,自动跳过已完成的迭代,中断/重启后不再从头开始;新增 `--fresh` 强制重跑;②修复浮动面板 ×/— 按钮被拖拽指针捕获吞掉点击的问题(拖拽仅绑定标题文字);③提升状态徽章对比度与按钮可点击区域
186
- - **v7(pkg-7)**:**提示词优化**——明确"产出可提交论文级内容"的目标;新增五条工作纪律(质疑先行并逐条给出处理决定、只改有据之处、禁止编造数据、符号与术语一致、论文级表达);明确回复结构(质疑清单→工作成果→文件清单总结)
187
- - **v8(固化版)**:**持久化部署插件**——以正式插件包(`dsh-mcmp`)写入用户配置层 `$DSH_HOME/profiles/web/cordis.patch.yml`,DSH 重启后自动加载,不再依赖动态安装。声明 `inject` 硬依赖等待服务就绪,面板 RPC 改为 `webServer` 路由(`/mcmp-api/*`),客户端为标准 `__ModuleLoader__` bundle + fetch。源码见 `persistent/` 目录
188
130
 
189
131
  ## License
190
132
 
@@ -0,0 +1,5 @@
1
+ # dsh-mcmp bundle 补丁:作为 profile bundle 层自动注册插件行
2
+ # 通过 dsh.plugin add/update 安装本包后,启动时自动应用本补丁,无需手动编辑配置
3
+ - insert:
4
+ - id: mcmp
5
+ name: dsh-mcmp
package/lib/index.js CHANGED
@@ -1,12 +1,18 @@
1
1
  /**
2
2
  * dsh-mcmp · 数学建模论文自动化流水线 —— Host 半(持久化部署插件)
3
3
  *
4
- * 挂载于用户配置层($DSH_HOME/profiles/web/cordis.patch.yml),重启不丢。
5
- * 与动态版本的区别:
6
- * - 面板 RPC 由 harness.handle 改为 webServer 路由(/mcmp-api/*)
7
- * - 命令/提示注册全部防御化(与动态插件共存期间冲突时静默降级)
8
- * 其余编排逻辑与动态版 v6 完全一致:断点续跑、Host 侧子智能体编排、
9
- * 质疑驱动、成果落盘、tool-workflow/* 会话记录(原生工作流卡片)。
4
+ * v2:按上一轮实战复盘重构流程:
5
+ * 1) 新增 S5「图表视觉自检与修复」:探测到识图插件(服务/工具)则自动调用识图;
6
+ * 没有识图能力则引导子智能体编写脚本做文本包围盒重叠/越界/乱码分析并修复。
7
+ * 2) 新增 S8「论文定稿与提交包」:无论流水线正常完成、出错还是被中止,都会兜底
8
+ * 生成一版完整、干净的 Markdown 论文(论文定稿.md)。
9
+ * 3) S7 合并旧「编译合规」+「评审改进」:检查 → 修复落实(直接在论文/图表/代码中
10
+ * 修改,禁止只写处理意见)→ 复核验证,配《问题跟踪表》闭环。
11
+ * 4) 步骤顺序理顺:赛题分析→模型建立与求解→编程实现→图表与流程图→图表视觉
12
+ * 自检→论文撰写→评审与修复落实→论文定稿(S4 合并旧图表+流程图两步)。
13
+ * 流程名改为「数学建模论文流水线v2」,断点续跑只识别 v2 记录,避免与 v1 旧记录混淆。
14
+ * 其余机制不变:质疑驱动、成果落盘、tool-workflow 会话记录(原生工作流卡片)、
15
+ * 浮动面板 RPC(/mcmp-api/*)、消息以 /loopbegin 开头自动触发。
10
16
  */
11
17
 
12
18
  export const name = 'mcmp'
@@ -22,42 +28,68 @@ export function apply(ctx) {
22
28
  const webServer = ctx.get('webServer')
23
29
  console.log('dsh-mcmp: services available -> commands=' + !!commands + ' fs=' + !!fs + ' agents=' + !!agents + ' subagents=' + !!subagents + ' systemPrompt=' + !!systemPrompt + ' webServer=' + !!webServer)
24
30
 
25
- // ---------- 步骤元数据(含每个迭代的任务提示词) ----------
31
+ const FLOW_NAME = '数学建模论文流水线v2'
32
+
33
+ // ---------- 识图能力探测(宿主侧,仅供参考;以子智能体自检为准) ----------
34
+ function detectVision() {
35
+ // 1) 宿主服务:常见的识图服务键
36
+ const keys = ['visionRouter', 'vision', 'imageVision', 'visionService', 'imageAnalysis', 'imageRecognition']
37
+ for (const k of keys) {
38
+ try { if (ctx.get(k) !== undefined) return { source: 'service', key: k } } catch (err) { /* ignore */ }
39
+ }
40
+ // 2) 工具注册表:扫描识图类工具名
41
+ try {
42
+ const tools = ctx.get('tools')
43
+ if (tools && typeof tools.schemas === 'function') {
44
+ let schemas = null
45
+ try { schemas = tools.schemas() } catch (err) { /* 某些实现要求 scope,忽略 */ }
46
+ if (Array.isArray(schemas)) {
47
+ const names = schemas.map((s) => (s && s.name) || '').filter(Boolean)
48
+ const hits = ['vision_describe', 'vision_bootstrap', 'vision_ocr', 'vision_ground', 'vision_crop', 'vision_detect', 'read_image']
49
+ .filter((n) => names.indexOf(n) >= 0)
50
+ if (hits.length > 0) return { source: 'tools', tools: hits }
51
+ }
52
+ }
53
+ } catch (err) { /* ignore */ }
54
+ return null
55
+ }
56
+
57
+ // ---------- 步骤元数据(8 步骤 × 19 迭代/轮) ----------
26
58
  const STEPS = [
27
- { key: 'S1', name: '赛题分析', phase: 'S1 赛题分析', file: '01_赛题分析.md', iters: [
28
- { name: '初步拆解', task: '通读赛题,完成初步拆解:(1) 确认所选题目( A/B/C )与全部子问题;(2) 列出问题背景、已知条件、数据(附件)说明、待求目标;(3) 明确提交要求(论文页数、程序、结果表等);(4) 判断问题类型(预测、优化、评价、机理、决策等);(5) 圈出关键词与易误解之处,给出你的理解与疑问。' },
29
- { name: '深度分析', task: '对每个子问题深入分析:(1) 明确评价指标或目标函数;(2) 列出约束条件;(3) 分析数据特征(缺失、异常、量纲、规模),给出预处理方案;(4) 提出合理假设,逐条编号并说明理由;(5) 列出 2-3 种候选建模思路,逐一分析优劣与可行性;(6) 指出主要难点与风险。' },
30
- { name: '最终定调', task: '在前两轮成果基础上质疑并定稿:(1) 给出总体建模框架(问题→数据→假设→模型→求解→验证→结论);(2) 为每个子问题敲定选用的模型与方法,并说明选择理由;(3) 列出所需的编程实现清单与图表清单;(4) 给出论文结构大纲;(5) 输出《建模方案定稿》,作为后续所有步骤的权威依据。' },
59
+ { key: 'S1', name: '赛题分析与建模方案', phase: 'S1 赛题分析与建模方案', file: '01_赛题分析.md', iters: [
60
+ { name: '赛题精读与拆解', task: '(1) 环境清理:若工作区根目录已存在且含有 00_赛题原文.md 或 01_赛题分析.md(属于上一次运行),先用 shell 命令把整个「数学建模流水线」目录改名为 数学建模流水线_历史归档_YYYYMMDD_HHMMSS(只改名、绝不删除,完整保留历史成果),再重建空目录结构;(2) 通读赛题全文(含附件、表格、图),确认所选题目与全部子问题;(3) 列出问题背景、已知条件、数据说明、待求目标与提交要求(页数、格式、支撑材料);(4) 判断每个子问题的类型(预测/优化/评价/机理/决策等);(5) 圈出关键词与易误解之处(尤其题目附录中的条件定义),给出你的理解与疑问;(6) 将赛题原文完整保存为 00_赛题原文.md;(7) 建立目录结构:代码/、代码/results/、图表/。' },
61
+ { name: '建模方案定稿', task: '(1) 数据特征分析(缺失、异常、量纲、规模)与预处理方案;(2) 逐条编号的合理假设(初版)并说明理由;(3) 每个子问题列出 2-3 种候选建模思路,逐一分析优劣与可行性;(4) 为每个子问题敲定选用的模型与方法并说明理由;(5) 给出总体建模框架(问题→数据→假设→模型→求解→验证→结论);(6) 输出《建模方案定稿》,作为后续所有步骤的权威依据;(7) 列出编程实现清单、图表清单(含技术路线图/架构图)与论文结构大纲。' },
31
62
  ] },
32
- { key: 'S2', name: '模型求解', phase: 'S2 模型求解', file: '02_模型求解.md', iters: [
33
- { name: '模型建立', task: '依据 01_赛题分析.md 的《建模方案定稿》:(1) 建立统一的符号体系(符号表,含含义与单位);(2) 逐条给出模型假设(编号);(3) 对每个子问题建立数学模型:目标函数、约束条件或方程,并给出推导过程;(4) 说明模型与赛题要求的对应关系;(5) 指出模型的适用范围。' },
34
- { name: '模型求解', task: '对每个已建立的模型给出求解:(1) 能解析求解的给出完整推导;(2) 需要数值求解的给出算法原理、步骤与伪代码(如最优化、微分方程数值解、统计方法、启发式算法);(3) 说明复杂度、收敛性与可行性;(4) 明确哪些结果将在步骤3中用程序实现。' },
35
- { name: '模型验证', task: '验证模型的正确性与可靠性:(1) 用特殊情形、退化情形或手算小例子验证解析结果;(2) 若数据可用,做数据驱动的验证(拟合优度、误差分析、残差检验);(3) 设计灵敏度分析方案(参数扰动范围与观察指标);(4) 总结模型的优点、不足与假设的合理性;(5) 明确哪些假设可以放宽、放宽后模型如何修正。' },
63
+ { key: 'S2', name: '模型建立与求解', phase: 'S2 模型建立与求解', file: '02_模型建立与求解.md', iters: [
64
+ { name: '模型建立', task: '(1) 建立全题统一的符号体系(符号表:含义+单位),后续步骤必须沿用;(2) 逐条编号模型假设(在 S1 基础上完善);(3) 对每个子问题建立数学模型:决策变量/目标函数/约束或方程,给出推导过程;(4) 说明模型与赛题要求的对应关系(尤其回应赛题附录中的条件定义);(5) 指出模型适用范围与前提。' },
65
+ { name: '模型求解', task: '(1) 可解析求解的给出完整推导与闭式解;(2) 需数值求解的给出算法原理、步骤与伪代码(最优化/微分方程/统计/启发式),说明复杂度、收敛性与可行性;(3) 对关键公式用手算小例子验证;(4) 明确哪些结果将在 S3 用程序实现,给出期望输出的数值口径(精度、单位)。' },
66
+ { name: '模型验证设计', task: '(1) 设计验证方案:特殊/退化情形、手算对照、交叉验证、独立仿真等;(2) 设计灵敏度分析方案(扰动参数、观察指标、扰动范围);(3) 检查模型假设的合理性,列出可放宽的假设及放宽后的修正方式;(4) 总结模型优点、不足与风险;(5) 把本轮形成的符号、公式、结论固化为 S3 的权威依据;若与 S1 定稿冲突,显式裁决并记录。' },
36
67
  ] },
37
- { key: 'S3', name: '编程实现', phase: 'S3 编程实现', file: '03_编程实现.md', iters: [
38
- { name: '核心代码', task: '依据 0102 两步成果,用 Python 实现每个模型的核心算法:(1) 代码写入 代码/ 目录,文件名体现步骤与版本(如 s2_model_v1.py),配中文注释;(2) 处理数据读取与预处理;(3) 确保代码可直接运行,运行并把关键输出(数值结果、评价指标)记录到 03_编程实现.md;(4) 若环境缺少依赖,先安装(pip)再运行;(5) 旧代码一律保留。' },
39
- { name: '完善调试', task: '对已有代码严格调试:(1) 用手算小例子与极端参数做对照测试,验证正确性;(2) 修复所有 bug,处理数据缺失与异常;(3) 补充边界情形测试;(4) 补充中文注释与文档字符串;(5) 重新运行并与 02_模型求解.md 的推导核对一致;(6) 新版本用 _v2_v3 后缀另存,绝不删除旧版本。' },
40
- { name: '优化封装', task: '将代码工程化:(1) 整理为函数/类,参数化关键量;(2) 统一输出:数值结果写入 代码/results/ 下的 CSV/JSON;(3) 提供 main 入口脚本一键复现全部结果;(4) 规范命名与注释;(5) 在 03_编程实现.md 记录运行环境(语言版本、依赖库及版本)与复现步骤;(6) 最终完整运行一次并保存全部输出。' },
68
+ { key: 'S3', name: '编程实现与数值验证', phase: 'S3 编程实现与数值验证', file: '03_编程实现.md', iters: [
69
+ { name: '核心代码', task: '(1) 01/02 权威成果用 Python 实现每个子问题的核心算法,代码写入 代码/ 目录,文件名含步骤与版本(如 s3_model_v1.py),配中文注释;(2) 数据读取与预处理;(3) 确保可直接运行;运行并把关键输出记录到 03_编程实现.md;(4) 若缺依赖先 pip 安装(设 3 分钟时限,失败则改用纯标准库实现);(5) 旧代码一律保留。' },
70
+ { name: '调试与数值验证', task: '(1) 用手算小例子与极端参数对照测试;(2) 修复所有 bug,处理数据缺失/异常;(3) 边界情形测试;(4) 与 02 公式逐项核对,数值不一致必须定位原因并裁决(以程序实测为准,并把裁决回写文档);(5) 数值结果落盘到 代码/results/ 的 CSV/JSON;(6) 新版本用 _v2/_v3 后缀另存,绝不删除旧版。' },
71
+ { name: '封装与自测', task: '(1) 整理为函数/类,参数化关键量;(2) 统一输出 代码/results/ 结构化数据(CSV/JSON + 结果索引);(3) 提供 main 入口一键复现全部结果;(4) 编写自测套件(断言:数值口径、概率范围、收支非负、退化情形),全部通过并保留报告;(5) 记录运行环境(语言版本、依赖及版本)与复现步骤;(6) 输出《数值验证报告》:权威数值清单,供图表与论文引用。' },
41
72
  ] },
42
- { key: 'S4', name: '图表生成', phase: 'S4 图表生成', file: '04_图表生成.md', iters: [
43
- { name: '基础图表', task: '依据 03_编程实现.md 的结果数据生成论文所需的基础图表:(1) 至少包括:结果曲线、对比柱状图、误差或收敛图、数据散点与拟合对比等(按实际需要);(2) 图片保存为 PNG 图表/ 目录,文件名语义化;(3) 每张图配中文标题、轴标签、图例与单位;(4) 04_图表生成.md 记录每张图的含义、数据来源与生成代码位置;(5) 绘图代码保存到 代码/ 下。' },
44
- { name: '美化优化', task: '统一美化全部图表:(1) 统一字体大小、配色、线型风格;(2) 修复中文乱码(配置中文字体,如 SimHei Microsoft YaHei);(3) 分辨率不低于 300dpi;(4) 补充关键标注(重要数值、结论性说明);(5) 确保每张图脱离正文也能看懂;(6) 更新 04_图表生成.md 中的说明。' },
45
- { name: '精选定稿', task: '对图表做最终取舍:(1) 从全部图表中精选论文真正需要的图,宁缺毋滥;(2) 确定编号(图1、图2…)与插入位置;(3) 冗余图移入 图表/归档/ 保留,不物理删除;(4) 导出最终版本;(5) 04_图表生成.md 输出《图表清单》:编号、文件路径、插入章节、一句话说明。' },
73
+ { key: 'S4', name: '图表与流程图生成', phase: 'S4 图表与流程图生成', file: '04_图表生成.md', iters: [
74
+ { name: '基础图表与结构图', task: '(1) 依据 03 权威 results/ 生成论文所需全部图:结果曲线、对比柱状图、散点+拟合、成本/误差分解图等;(2) 同时生成总体技术路线图与系统/模型架构图(graphviz/mermaid/matplotlib/networkx 或自建引擎,源文件保存 代码/,图片输出 图表/);(3) 优先尝试 pip 安装 matplotlib/PIL(3 分钟时限,失败则沿用/自建纯标准库 PNG 引擎);(4) 每张图:中文标题、轴标签、图例、单位,分辨率不低于 300dpi(或 1800×1200 + pHYs);(5) 布局防重叠:图例自动避让、长标签截断或换行、结论框/副标题预留空间、文本框之间留足间隙;(6) 绘图数据与 results/ 交叉核对(脚本断言),不符即退出;(7) 绘图代码保存 代码/。' },
75
+ { name: '美化统一', task: '(1) 统一字号、配色、线型风格(集中式 STYLE 常量);(2) 修复中文乱码(字体字形探测/回退),禁用易乱码字符(如下标数字、组合音标),统一 U+2212 "-";(3) 每张图加副标题(参数/口径说明)、结论框(关键数值+结论)、数据来源行;(4) 补充关键标注(重要数值、结论性说明);(5) 图内符号与论文符号体系一致;(6) 保证每张图脱离正文也能看懂。' },
76
+ { name: '精选定稿', task: '(1) 从全部图中精选论文真正需要的图(结果图+技术路线图+架构图),宁缺毋滥;(2) 确定编号(图1、图2…)与插入章节;(3) 冗余图移入 图表/归档/,不物理删除;(4) 定稿图统一导出到 图表/定稿/;(5) 输出《图表清单》(编号/路径/插入章节/一句话说明)与《程序化自检报告》(尺寸/墨量/数据断言);(6) S5 视觉自检做好准备:列出每张定稿图对应的绘图代码与重渲染命令。' },
46
77
  ] },
47
- { key: 'S5', name: '流程与架构图', phase: 'S5 流程与架构图', file: '05_流程与架构图.md', iters: [
48
- { name: '结构设计', task: '设计论文所需的两类图:(1) 总体技术路线/流程图:问题分析→数据预处理→模型建立→求解→验证→结果→论文,体现完整流程;(2) 系统/模型架构图:模块划分与数据流。要求:节点、层次、连线语义明确。可用 graphviz(dot)、matplotlib/networkx mermaid 生成;源文件保存到 代码/,图片输出到 图表/。' },
49
- { name: '细节完善', task: '检查并完善流程图与架构图:(1) 逻辑完整:每个节点有输入输出、无孤立节点、方向一致;(2) 命名与论文符号统一;(3) 补充关键公式或参数标注;(4) 调整布局避免重叠;(5) 生成修订版,并在 05_流程与架构图.md 说明每张图的结构与用途。' },
50
- { name: '美化定稿', task: '最终美化:(1) 统一配色与字体(支持中文);(2) 导出高清 PNG 或 SVG;(3) 确定图编号(如“图0 技术路线”)与插入位置;(4) 在 05_流程与架构图.md 输出最终文件清单;(5) 中间版本归档保留。' },
78
+ { key: 'S5', name: '图表视觉自检与修复', phase: 'S5 图表视觉自检与修复', file: '05_图表自检.md', iters: [
79
+ { name: '视觉自检与问题清单', vision: true, task: '(1) 逐张检查 图表/定稿/ 的全部图片,重点:①文字重叠/相互遮挡;②乱码、豆腐块、缺字;③文字被裁切/越出画布;④标题、轴标签、图例缺失或不完整;⑤标注数值与 代码/results/ 权威数据不一致;⑥曲线/柱体被文字遮挡。若你具备识图能力(视觉能力:可用):用识图工具逐张查看(必要时裁剪放大局部),列出每张图的问题清单(问题描述+图内位置+严重级:A=必须修复/B=应当修复)。若不具备识图能力(视觉能力:不可用):编写并运行 Python 分析脚本——在绘图代码中记录每个文本的包围盒(x,y,w,h)并计算两两交叠面积、文本框越界检测、字形回退(豆腐块)探测、墨量/空白检测,输出每图问题 JSON 报告。(2) 汇总为《图表视觉自检报告》写入 05_图表自检.md:每图问题清单+分级+修复建议;(3) 分析脚本保存 代码/。' },
80
+ { name: '修复与复检', task: '(1) 对《图表视觉自检报告》中全部 A/B 级问题逐条实际修复:修改绘图代码(调整布局/字号/标签/图例位置/增加间距/换行/去重),重渲染出新版 PNG(定稿目录覆盖同名或 _v2 另存,旧版移入 归档/);(2) 每条修复登记到《修复验证表》:问题→修复方式(代码改动描述)→复检证据(视觉复核结论或脚本输出:重叠数=0);(3) 修复后再次自检(识图工具或脚本),A 级问题必须清零;若仍保留 B 级,给出取舍理由;(4) 更新 04_图表生成.md 的《图表清单》,注明每张图已通过视觉自检;(5) 禁止只记录不修复:每个问题必须有"修复+复检证据"或"显式弃修理由"。' },
51
81
  ] },
52
82
  { key: 'S6', name: '论文撰写', phase: 'S6 论文撰写', file: '06_论文.md', iters: [
53
- { name: '初稿', task: '依据前 5 步全部成果撰写论文初稿,写入 06_论文.md(如有 LaTeX 环境,同时输出 tex 源文件到 论文/ 目录)。标准结构:(1) 摘要(单独成页、含关键词,突出方法、结果与创新);(2) 一、问题重述与分析;(3) 二、模型假设与符号说明;(4) 三、模型建立与求解(分问题撰写、公式编号);(5) 四、模型验证与灵敏度分析;(6) 五、模型评价与推广;(7) 六、参考文献;(8) 附录(程序清单)。要求:正文所有结论必须引用前序成果(图表、代码输出),不得凭空捏造数据;语言规范、逻辑严密、图表引用齐全。' },
54
- { name: '深化润色', task: '以评审专家视角逐节质疑并润色初稿:(1) 摘要是否突出方法、结果与创新,是否控制在一页;(2) 公式与符号是否前后一致、编号连续;(3) 图表引用是否齐全、编号是否正确;(4) 表述是否严谨,删除“显然”“容易得到”等无依据表述;(5) 补充灵敏度分析的量化结论;(6) 检查参考文献格式;(7) 逐段润色语言,输出终稿(在 06_论文.md 中新增“终稿”小节,保留初稿)。' },
83
+ { name: '初稿', task: '(1) 依据前 5 步权威成果撰写论文初稿,写入 06_论文.md 新的一节;(2) 标准结构:摘要(单独成页、含关键词,突出方法/结果/创新)、一 问题重述与分析、二 模型假设与符号说明、三 模型建立与求解(分问题、公式编号)、四 模型验证与灵敏度分析、五 模型评价与推广、六 参考文献、附录(程序清单);(3) 正文全部结论必须引用前序成果(图表、代码输出),不得凭空捏造数据;(4) 图表用相对路径引用 图表/定稿/ 的图,编号与《图表清单》一致;(5) 严禁流水线内部痕迹:不得出现"01_赛题分析.md""02 迭代3""03 步骤""流水线"等字样,证据一律表述为"支撑材料/附录";(6) 公式编号连续、符号与 02 一致。' },
84
+ { name: '润色终稿', task: '(1) 以评审视角逐节质疑:摘要是否一页内?公式与符号前后一致、编号连续?图表引用齐全、编号正确?有无"显然/易得"式跳步?灵敏度结论是否量化?参考文献格式规范?(2) 修订后重写一份完整、连续的终稿,写入新的一节(标题注明「终稿」),全文完整、可直接提交——不允许用"修订段落清单"代替完整终稿;(3) 保留初稿内容不删;(4) 全文检索流水线痕迹与内部文档名并清零;(5) 结尾附《终稿质检清单》(逐项 ✓/✗)。' },
55
85
  ] },
56
- { key: 'S7', name: '编译与合规检查', phase: 'S7 编译与合规检查', file: '07_编译与合规检查.md', iters: [
57
- { name: '编译与合规检查', task: '按国赛要求逐项检查并输出报告:(1) 摘要单独成页、含关键词;(2) 论文不含任何学校、队员、指导教师信息;(3) 正文页数符合当年要求(以赛题要求为准);(4) 图表编号与正文引用一一对应;(5) 公式编号连续;(6) 参考文献格式规范;(7) 附录含程序清单;(8) 结果表格完整、数值与程序输出一致;(9) 提交文件命名符合赛方要求(如 论文.pdf、支撑材料.zip)。若环境存在 pdflatex/xelatex,尝试编译论文为 PDF 并记录编译日志;若无 LaTeX 环境,明确说明并给出“提交前手工检查清单”。全部结论写入 07_编译与合规检查.md,并输出《合规检查报告》与《待修改清单》。' },
86
+ { key: 'S7', name: '评审与修复落实', phase: 'S7 评审与修复落实', file: '07_评审修复.md', iters: [
87
+ { name: '合规检查与评审', task: '(1) 按国赛要求逐项检查 06 终稿:摘要单独成页含关键词、无学校/队员/指导教师信息、页数符合要求、图表编号与引用一一对应、公式编号连续、参考文献格式、附录程序清单、结果表数值与 results/ 一致、提交文件命名;(2) 以竞赛评审专家身份按国赛评审标准(摘要约10%、假设合理性、建模科学性、求解正确性、验证充分性、写作规范性、创新性)打分并写评语;(3) 汇总输出《合规检查报告》+《评审报告》+《问题清单》(每项含:级别 A=必须修复/B=应当修复/C=建议、位置、证据、修复建议);(4) 建立《问题跟踪表》(编号/描述/级别/状态=待修复),写入 07_评审修复.md' },
88
+ { name: '修复落实', task: '(1) 对《问题清单》中全部 A/B 级问题逐一实际修复,而不是只写处理意见:论文问题 → 直接用编辑工具修改 06_论文.md 终稿正文(修改后终稿仍是完整连续的全文,禁止再追加"修订段落");图表问题 → 修改绘图代码并重渲染、复检;数值问题 → 修改代码重跑,与 results/ 复核一致后再更新论文;(2) 每条修复在《问题跟踪表》中登记:修复方式(文件+改动描述)、证据(命令/复检输出/断言结果)、状态(已修复);无法修复的写明阻塞原因与后续建议(状态:受阻);(3) 修复不得破坏既有正确内容;(4) 同步更新论文中受影响的图表引用与编号。' },
89
+ { name: '修复复核', task: '(1) 重新逐项核验《问题跟踪表》:每项重新检查(重跑自测/断言/视觉复检/文本检索),确认修复真实生效,状态更新为"已验证";(2) 对未闭环项继续修复;(3) 输出《修复验证报告》:问题→状态→验证证据,给出闭环率(A 级必须 100%);(4) 更新评审打分(修复后预估);(5) 为 S8 定稿列出移交清单:论文终稿路径、图表清单、代码入口、复现命令。' },
58
90
  ] },
59
- { key: 'S8', name: '评审改进循环', phase: 'S8 评审改进循环', file: '08_评审改进.md', iters: [
60
- { name: '评审改进', task: '以竞赛评审专家身份,按国赛评审标准(摘要约10%、假设合理性、建模科学性、求解正确性、验证充分性、写作规范性、创新性) 06_论文.md 打分并写评语:(1) 列出必须修改的高优先级问题与建议改进项;(2) 针对高优先级问题,直接在 06_论文.md 中给出修改后的关键段落(全文修订亦可),确保论文达到可提交水准;(3) 输出《评审报告》到 08_评审改进.md;(4) 最后给出整条流水线的总结:方法、主要结果、亮点与遗留问题。' },
91
+ { key: 'S8', name: '论文定稿与提交包', phase: 'S8 论文定稿与提交包', file: '08_论文定稿.md', iters: [
92
+ { name: '论文定稿与提交包', task: '(1) 无论前序步骤是否全部成功,本步骤必须产出一版完整论文:检查 06_论文.md 是否有干净完整的终稿;若有,以其为底稿做最终校对后写入 论文定稿.md;若终稿缺失/混乱/不完整,则依据 00_赛题原文.md、01~07 全部成果与 代码/results/、图表/定稿/ 从头撰写完整论文,写入 论文定稿.md;(2) 论文定稿.md 必须是可直接提交的完整 Markdown:标题、摘要(含关键词)、问题重述与分析、模型假设与符号说明、模型建立与求解(公式编号)、模型验证与灵敏度分析、模型评价与推广、参考文献、附录,图表用相对路径引用;(3) 全文检索并清除流水线内部痕迹与参赛者信息;(4) 数值只采用 results/ 权威数据或前序已验证结论,缺失时明确标注"待补充"并给出推导,不得编造;(5) 输出《提交材料清单》(论文定稿.md、图表、代码、results/、复现说明)与《最后校对报告》;(6) 本步骤工作记录写入 08_论文定稿.md,最终论文全文写入 工作区根目录的 论文定稿.md。' },
61
93
  ] },
62
94
  ]
63
95
  const PER_ROUND = 19
@@ -70,6 +102,7 @@ export function apply(ctx) {
70
102
  status: 'idle', runId: '', rounds: 0, round: 0, total: 0, done: 0,
71
103
  phase: '', agentLabel: '', logs: [], files: [], error: '',
72
104
  title: '', wsDir: '', problemPath: '', startedAt: 0, endedAt: 0,
105
+ vision: null, visionLive: '',
73
106
  }
74
107
  }
75
108
  let state = freshState()
@@ -122,7 +155,7 @@ export function apply(ctx) {
122
155
  return undefined
123
156
  }
124
157
 
125
- // ---------- 断点续跑:从会话日志(落盘)恢复已完成迭代数 ----------
158
+ // ---------- 断点续跑:从会话日志(落盘)恢复已完成迭代数(仅识别 v2 流程名) ----------
126
159
  function completedIterations(session) {
127
160
  if (!session || !Array.isArray(session.events)) return 0
128
161
  const runs = []
@@ -131,7 +164,7 @@ export function apply(ctx) {
131
164
  try {
132
165
  if (ev.type === 'tool-workflow/run-start') {
133
166
  const d = ev.data || {}
134
- if (d.name === '数学建模论文流水线') {
167
+ if (d.name === FLOW_NAME) {
135
168
  current = { started: [], ended: [] }
136
169
  runs.push(current)
137
170
  } else {
@@ -192,6 +225,7 @@ export function apply(ctx) {
192
225
  logs: state.logs.slice(-12), files: state.files.slice(0, 80),
193
226
  error: state.error, title: state.title, wsDir: state.wsDir, problemPath: state.problemPath,
194
227
  startedAt: state.startedAt, endedAt: state.endedAt,
228
+ vision: state.vision, visionLive: state.visionLive,
195
229
  }
196
230
  }
197
231
 
@@ -255,7 +289,6 @@ export function apply(ctx) {
255
289
  } else {
256
290
  p.push('【赛题原文(已完整嵌入,请逐字精读)】')
257
291
  p.push(args.problem.text)
258
- if (si === 0 && ii === 0) p.push('【先存档】请先把嵌入的赛题原文完整保存为 ' + args.problemPath + ' 文件,供后续所有步骤读取。')
259
292
  }
260
293
  p.push('')
261
294
  p.push('【必读文件(先全部读完再动手,禁止跳过)】')
@@ -269,6 +302,15 @@ export function apply(ctx) {
269
302
  } else {
270
303
  p.push('若文件中缺失上一迭代(「第' + round + '轮·迭代' + ii + ' ' + s.iters[ii - 1].name + '」)成果,说明其失败:先补齐该迭代成果,再完成本次迭代。')
271
304
  }
305
+ if (it.vision) {
306
+ p.push('')
307
+ p.push('【识图能力自检(强制,回复第一行)】检查你自己的工具列表:若含有 vision_describe / read_image / vision_ocr / vision_ground / vision_crop / vision_detect 等识图工具,回复第一行写「视觉能力:可用(工具:xxx)」并优先用识图工具检查图片;若没有任何识图工具,回复第一行写「视觉能力:不可用」,改走脚本分析路径。')
308
+ if (args.vision) {
309
+ p.push('【宿主侧探测(仅供参考)】系统检测到识图插件: ' + (args.vision.source === 'service' ? '服务 ' + args.vision.key : '工具 ' + (args.vision.tools || []).join(', ')) + '。若你的工具列表中确有其对应的调用方式,请直接调用它完成图片检查。')
310
+ } else {
311
+ p.push('【宿主侧探测(仅供参考)】未检测到独立识图插件;如果你装了识图插件但此处未检出,以你自己的工具列表为准,仍然按"视觉能力:可用"路径执行。')
312
+ }
313
+ }
272
314
  p.push('')
273
315
  p.push('【工作纪律】')
274
316
  p.push('1. 质疑先行:开工前对既有成果提出至少 3 条具体质疑(假设是否成立、是否偏离赛题、推导有无漏洞、数据与结论是否一致、格式是否规范),并逐条给出处理决定(采纳并改进 / 否决并说明理由)。质疑清单必须放在回复最前面。')
@@ -276,6 +318,8 @@ export function apply(ctx) {
276
318
  p.push('3. 禁止编造:所有数值与结论必须来自赛题、实际计算或前序成果;信息不足时明确写出你的假设并说明理由,不得假装已知。')
277
319
  p.push('4. 符号与术语一致:沿用前序步骤的符号体系,新增符号必须先定义。')
278
320
  p.push('5. 论文级表达:推导完整、步骤清晰、结论可复现,杜绝"显然""易得"式跳步。')
321
+ p.push('6. 修复闭环:发现的每个问题要么在本迭代内真正修复并给出证据(修改后的文件/复检输出/断言结果),要么明确记录阻塞原因与后续建议;禁止"只把问题写进文档而不修复"。')
322
+ p.push('7. 无流水线痕迹:论文类文件(06_论文.md、08_论文定稿.md、论文定稿.md)不得出现内部文档引用("01_赛题分析.md""02 迭代3""03 步骤""流水线"等),证据一律表述为"支撑材料/附录"。')
279
323
  p.push('')
280
324
  p.push('【本轮任务】' + it.task)
281
325
  p.push('')
@@ -287,6 +331,34 @@ export function apply(ctx) {
287
331
  return p.join('\n')
288
332
  }
289
333
 
334
+ function buildFinalizePrompt(args, reason) {
335
+ const p = []
336
+ p.push('你正在参与「全国大学生数学建模竞赛论文」自动化流水线的**兜底定稿**环节。此前流水线提前结束(原因:' + reason + '),但交付要求是"不论最终结果如何,都必须产出一版完整的论文"。现在由你仅凭现有文件完成这个承诺。')
337
+ p.push('')
338
+ p.push('【工作区(绝对路径)】' + args.wsDir + ' —— 所有读写都在该目录内。')
339
+ if (args.problem.kind === 'file') {
340
+ p.push('【赛题原文文件】' + args.problem.path + ' —— 先用文件工具读取全文。')
341
+ } else {
342
+ p.push('【赛题原文(已完整嵌入)】')
343
+ p.push(args.problem.text)
344
+ }
345
+ p.push('')
346
+ p.push('【现有材料(全部先读,能用的必须用)】')
347
+ p.push('- 00_赛题原文.md、01_赛题分析.md、02_模型建立与求解.md、03_编程实现.md、04_图表生成.md、05_图表自检.md、06_论文.md、07_评审修复.md、08_论文定稿.md(存在即读)')
348
+ p.push('- 代码/ 与 代码/results/ 下的全部代码与结构化结果(权威数值只从这里取)')
349
+ p.push('- 图表/定稿/ 下的全部图片(论文插图用相对路径引用)')
350
+ p.push('')
351
+ p.push('【任务(必须完成)】')
352
+ p.push('1. 判断 06_论文.md 中是否已有干净完整的终稿:有 → 以其为底稿做最终校对后写入 论文定稿.md;缺失/混乱/不完整 → 依据上述全部材料从头撰写完整论文,写入 论文定稿.md。')
353
+ p.push('2. 论文定稿.md 必须是可直接提交的完整 Markdown:标题、摘要(单独成页、含关键词)、问题重述与分析、模型假设与符号说明、模型建立与求解(公式编号)、模型验证与灵敏度分析、模型评价与推广、参考文献、附录(程序清单);图表用相对路径引用。')
354
+ p.push('3. 全文不得出现流水线内部痕迹与参赛者信息;数值只采用 results/ 权威数据或前序已验证结论,材料不足处明确标注"待补充"并给出推导,严禁编造。')
355
+ p.push('4. 在 08_论文定稿.md 追加一节「## 兜底定稿」,记录:定稿来源(06 终稿 / 重写)、完整度自评、遗留问题清单。')
356
+ p.push('5. 输出《提交材料清单》与《最后校对报告》(写入 08_论文定稿.md 该节)。')
357
+ p.push('')
358
+ p.push('【工作纪律】质疑先行、禁止编造、无流水线痕迹、论文级表达。回复结尾附不超过 200 字总结。')
359
+ return p.join('\n')
360
+ }
361
+
290
362
  function appendRec(runId, session, type, data) {
291
363
  const rec = recording.get(runId)
292
364
  if (!rec || !rec.ok) return false
@@ -311,7 +383,7 @@ export function apply(ctx) {
311
383
  state.status = stopReason
312
384
  state.endedAt = Date.now()
313
385
  if (stopReason === 'error') state.error = errorMsg || '流水线执行出错'
314
- if (stopReason === 'cancelled') state.error = '已被用户中止'
386
+ if (stopReason === 'cancelled') state.error = '已被用户中止(兜底定稿已尝试生成)'
315
387
  state.agentLabel = ''
316
388
  }
317
389
  refreshFiles()
@@ -330,10 +402,35 @@ export function apply(ctx) {
330
402
  return { ok: true }
331
403
  }
332
404
 
405
+ // 启动一个子智能体并返回 { ok, text, outcome }
406
+ async function runChild(provider, session, agent, args, controller, label, promptText) {
407
+ let child = null
408
+ try {
409
+ const parent = (agents && agents.get(session.id)) || agent
410
+ child = await subagents.start(provider, {
411
+ label,
412
+ prompt: [{ type: 'text', text: promptText }],
413
+ parent,
414
+ signal: controller.signal,
415
+ })
416
+ const res = await child.result
417
+ const text = (Array.isArray(res.output) ? res.output.filter((b) => b && b.type === 'text').map((b) => b.text).join('') : '')
418
+ const outcome = res.stopReason === 'completed' ? 'completed' : res.stopReason === 'cancelled' ? 'cancelled' : 'failed'
419
+ return { ok: true, started: true, outcome, text: text || '' }
420
+ } catch (err) {
421
+ return { ok: false, started: false, outcome: controller.signal.aborted ? 'cancelled' : 'failed', text: '', err }
422
+ } finally {
423
+ if (child) { try { await child.dispose() } catch (err) { /* ignore */ } }
424
+ }
425
+ }
426
+
333
427
  // ---------- Host 侧编排器:从断点继续(不依赖聊天 Agent 存活) ----------
334
428
  async function runPipeline(runId, session, agent, args, controller) {
335
429
  let failStreak = 0
336
430
  let cancelled = false
431
+ let finalDone = false // S8(定稿)是否已成功完成
432
+ let stopReason = 'completed'
433
+ let errorMsg = undefined
337
434
  const provider = pickProvider()
338
435
  if (!provider) {
339
436
  finishRun(runId, 'error', '没有可用的子智能体提供方(当前部署未注册 subagent provider)')
@@ -352,50 +449,60 @@ export function apply(ctx) {
352
449
  state.phase = s.phase
353
450
  state.agentLabel = label
354
451
  pushLog('开始: ' + label)
355
- let child = null
356
452
  let started = false
357
453
  let outcome = 'failed'
358
454
  let note = ''
359
- try {
360
- // 每次启动子任务前重新解析父 Agent(关闭聊天后 Agent 可能被重建)
361
- const parent = (agents && agents.get(session.id)) || agent
362
- child = await subagents.start(provider, {
363
- label,
364
- prompt: [{ type: 'text', text: buildPrompt(r, f.s, f.i, s, it, args) }],
365
- parent,
366
- signal: controller.signal,
367
- })
368
- appendRec(runId, session, 'tool-workflow/agent-start', { runId, seq: g + 1, label, phase: s.phase, childId: String(child.id) })
369
- started = true
370
- const res = await child.result
371
- const text = (Array.isArray(res.output) ? res.output.filter((b) => b && b.type === 'text').map((b) => b.text).join('') : '')
372
- outcome = res.stopReason === 'completed' ? 'completed' : res.stopReason === 'cancelled' ? 'cancelled' : 'failed'
373
- note = (text || '').trim().slice(0, 300)
374
- } catch (err) {
375
- outcome = controller.signal.aborted ? 'cancelled' : 'failed'
376
- note = '执行异常: ' + String((err && err.message) || err).slice(0, 200)
377
- } finally {
378
- if (child) { try { await child.dispose() } catch (err) { /* ignore */ } }
379
- }
380
- if (started) appendRec(runId, session, 'tool-workflow/agent-end', { runId, seq: g + 1, outcome })
455
+ const res = await runChild(provider, session, agent, args, controller, label, buildPrompt(r, f.s, f.i, s, it, args))
456
+ started = res.started
457
+ outcome = res.outcome
458
+ note = (res.text || '').trim().slice(0, 300)
459
+ if (res.err) note = '执行异常: ' + String((res.err && res.err.message) || res.err).slice(0, 200)
460
+ if (started) appendRec(runId, session, 'tool-workflow/agent-start', { runId, seq: g + 1, label, phase: s.phase, childId: '' })
381
461
  state.done = Math.min(g + 1, state.total)
382
462
  refreshFiles()
383
463
  if (outcome === 'completed') {
384
464
  failStreak = 0
385
465
  pushLog('完成: ' + label)
466
+ if (s.key === 'S8' && (g + 1) === total) finalDone = true
467
+ // 解析 S5 迭代1 的识图能力自报
468
+ if (s.key === 'S5' && f.i === 0) {
469
+ const m = /视觉能力[::]\s*(可用|不可用)/.exec(res.text || '')
470
+ if (m) {
471
+ state.visionLive = m[1] === '可用' ? 'vision' : 'script'
472
+ pushLog('图表自检路径: ' + (m[1] === '可用' ? '识图工具(视觉自检)' : '脚本分析(无识图工具)'))
473
+ }
474
+ }
386
475
  } else {
387
476
  failStreak += 1
388
477
  pushLog(outcome === 'cancelled' ? '中止: ' : '失败: ' + label + (note ? '(' + note.slice(0, 80) + ')' : ''))
389
478
  }
479
+ if (started) appendRec(runId, session, 'tool-workflow/agent-end', { runId, seq: g + 1, outcome })
390
480
  if ((g + 1) % PER_ROUND === 0 && !controller.signal.aborted) pushLog('【第 ' + r + '/' + args.rounds + ' 轮完成】')
391
481
  if (controller.signal.aborted) { cancelled = true; break }
392
482
  if (failStreak >= 3) {
393
- finishRun(runId, 'error', '连续 3 个子任务失败,流水线提前终止(已有成果全部保留,可重新 /loopbegin 续跑)')
394
- if (active && active.runId === runId) active = null
395
- return
483
+ stopReason = 'error'
484
+ errorMsg = '连续 3 个子任务失败,流水线提前终止(已有成果全部保留,可重新 /loopbegin 续跑)'
485
+ break
486
+ }
487
+ }
488
+ if (cancelled) { stopReason = 'cancelled'; errorMsg = '已被用户中止' }
489
+ // ---------- 兜底定稿:不论成败,只要 S8 未完成,就补一次论文定稿 ----------
490
+ if (!finalDone) {
491
+ pushLog('正在执行兜底定稿:生成 论文定稿.md(不论前序结果如何)')
492
+ state.phase = 'S8 兜底·论文定稿'
493
+ state.agentLabel = 'S8 兜底·论文定稿'
494
+ const fresh = makeController()
495
+ const reason = stopReason === 'cancelled' ? '用户中止' : stopReason === 'error' ? errorMsg : '正常流程已结束但定稿步骤未完成'
496
+ const fres = await runChild(provider, session, agent, args, fresh, '兜底·论文定稿(不论成败)', buildFinalizePrompt(args, reason))
497
+ if (fres.outcome === 'completed') {
498
+ finalDone = true
499
+ pushLog('兜底定稿完成: 论文定稿.md 已生成')
500
+ } else {
501
+ pushLog('兜底定稿未能完成: ' + (fres.text || '').trim().slice(0, 80) + ' —— 请查看工作区已有成果')
502
+ if (stopReason === 'completed') { stopReason = 'error'; errorMsg = '流水线迭代已完成,但兜底定稿生成失败' }
396
503
  }
397
504
  }
398
- finishRun(runId, cancelled ? 'cancelled' : 'completed', cancelled ? '已被用户中止' : undefined)
505
+ finishRun(runId, stopReason, errorMsg)
399
506
  if (active && active.runId === runId) active = null
400
507
  }
401
508
 
@@ -437,8 +544,9 @@ export function apply(ctx) {
437
544
  const wsDir = cwd.replace(/[\\/]+$/, '') + '/数学建模流水线'
438
545
  const problemPath = problem.kind === 'file' ? problem.path : wsDir + '/00_赛题原文.md'
439
546
  const title = problem.kind === 'text' ? problem.text.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0)[0] : problem.path
440
- const args = { rounds, wsDir, problemPath, problem, title: title ? title.slice(0, 40) : '赛题' }
441
- // 断点续跑:扫描本会话落盘的工作流记录,跳过已完成的迭代
547
+ const vision = detectVision()
548
+ const args = { rounds, wsDir, problemPath, problem, title: title ? title.slice(0, 40) : '赛题', vision }
549
+ // 断点续跑:扫描本会话落盘的 v2 工作流记录,跳过已完成的迭代
442
550
  const resumeCount = fresh ? 0 : completedIterations(session)
443
551
  args.startIndex = resumeCount
444
552
  if (resumeCount >= rounds * PER_ROUND) {
@@ -448,7 +556,7 @@ export function apply(ctx) {
448
556
  const rec = { session, ok: true }
449
557
  recording.set(runId, rec)
450
558
  try {
451
- session.append('tool-workflow/run-start', { runId, name: '数学建模论文流水线' })
559
+ session.append('tool-workflow/run-start', { runId, name: FLOW_NAME })
452
560
  } catch (err) {
453
561
  recording.delete(runId)
454
562
  }
@@ -463,9 +571,12 @@ export function apply(ctx) {
463
571
  state.wsDir = wsDir
464
572
  state.problemPath = problemPath
465
573
  state.title = args.title
574
+ state.vision = vision
466
575
  const controller = makeController()
467
576
  active = { runId, session, controller }
468
577
  pushLog('流水线已启动: ' + rounds + ' 轮 × 19 次迭代,输出目录 ' + wsDir + (resumeCount > 0 ? '(断点续跑:跳过已完成的 ' + resumeCount + ' 次迭代)' : '') + '(关闭本聊天窗口不影响后台执行)')
578
+ if (vision) pushLog('识图能力探测: ' + (vision.source === 'service' ? '检测到识图服务 ' + vision.key : '检测到识图工具 ' + (vision.tools || []).join(',')) + '(S5 将自动调用)')
579
+ else pushLog('识图能力探测:未检测到独立识图插件,S5 将让子智能体自检(无识图则走脚本分析)')
469
580
  refreshFiles()
470
581
  void runPipeline(runId, session, agent, args, controller).catch((err) => {
471
582
  pushLog('流水线异常: ' + String((err && err.message) || err))
@@ -474,9 +585,9 @@ export function apply(ctx) {
474
585
  })
475
586
  return {
476
587
  kind: 'success',
477
- text: '数学建模论文自动化流水线已启动:' + rounds + ' 轮 × 19 次迭代 = ' + (rounds * PER_ROUND) + ' 个子任务。\n输出目录: ' + wsDir
478
- + (resumeCount > 0 ? '\n断点续跑:已跳过此前完成的 ' + resumeCount + ' 次迭代,从第 ' + (resumeCount + 1) + ' 次继续。' : '\n全新运行。')
479
- + '\n每个子任务由专家子智能体执行,先对已有成果质疑、再迭代优化,并把完整成果追加保存到对应步骤文件(旧内容绝不删除)。\n进度请在右下角浮动面板实时查看(含百分比),聊天区也会出现流水线运行卡片。\n提示:中断或关闭聊天窗口都不会丢进度,再次发送 /loopbegin 即从断点续跑;如要重头开始请加 --fresh。',
588
+ text: '数学建模论文自动化流水线(v2)已启动:' + rounds + ' 轮 × 19 次迭代 = ' + (rounds * PER_ROUND) + ' 个子任务。\n输出目录: ' + wsDir
589
+ + (resumeCount > 0 ? '\n断点续跑:已跳过此前完成的 ' + resumeCount + ' 次迭代,从第 ' + (resumeCount + 1) + ' 次继续。' : '\n全新运行(若输出目录有旧成果,第一步会自动归档为 数学建模流水线_历史归档_*)。')
590
+ + '\n新流程:赛题分析 → 模型建立与求解 → 编程实现 → 图表与流程图 → 图表视觉自检与修复(有识图插件自动调用,无则脚本分析)→ 论文撰写 → 评审与修复落实(问题必须真正修复)→ 论文定稿。\n无论成败,最终都会生成完整论文 数学建模流水线/论文定稿.md。\n进度请在右下角浮动面板实时查看(含百分比),聊天区也会出现流水线运行卡片。\n提示:中断或关闭聊天窗口都不会丢进度,再次发送 /loopbegin 即从断点续跑;如要重头开始请加 --fresh。',
480
591
  }
481
592
  } catch (err) {
482
593
  return { kind: 'error', text: '启动失败: ' + String((err && err.message) || err) }
@@ -488,7 +599,7 @@ export function apply(ctx) {
488
599
  try {
489
600
  commands.register({
490
601
  name: 'loopbegin',
491
- description: '启动数学建模论文自动化流水线(8 步骤、每步多迭代、多轮外循环,自动质疑与优化,成果全部落盘)',
602
+ description: '启动数学建模论文自动化流水线 v2(8 步骤:含图表视觉自检、评审修复闭环、论文定稿;每轮 19 次迭代、多轮外循环,成果全部落盘)',
492
603
  input: { hint: '--round=N 外循环轮数(默认 1);可选 --from 题目文件 指定赛题文件;--fresh 从头重跑' },
493
604
  handler: (invocation) => startPipeline(invocation.agent, invocation.rawInput),
494
605
  })
@@ -500,7 +611,7 @@ export function apply(ctx) {
500
611
  handler: () => {
501
612
  if (!active) return { kind: 'error', text: '当前没有运行中的流水线。' }
502
613
  try { active.controller.abort(new Error('用户通过 /loopabort 中止')) } catch (err) { /* ignore */ }
503
- return { kind: 'success', text: '已发送中止请求,流水线将在当前子任务结束后停止(已产出的成果全部保留,重新 /loopbegin 可从断点续跑)。' }
614
+ return { kind: 'success', text: '已发送中止请求,流水线将在当前子任务结束后停止(已产出的成果全部保留,随后会自动兜底生成 论文定稿.md;重新 /loopbegin 可从断点续跑)。' }
504
615
  },
505
616
  })
506
617
  } catch (err) { console.warn('dsh-mcmp: loopabort 注册失败: ' + String((err && err.message) || err)) }
@@ -515,6 +626,7 @@ export function apply(ctx) {
515
626
  text += '\n进度: 第 ' + s.round + '/' + s.rounds + ' 轮,已完成 ' + s.done + '/' + s.total + ' 次迭代(' + s.pct + '%)'
516
627
  if (s.cur) text += '\n当前: ' + s.cur.stepKey + ' ' + s.cur.stepName + ' · 迭代 ' + (s.cur.iterIdx + 1) + '/' + s.cur.iterTotal + ' ' + s.cur.iterName
517
628
  if (s.agentLabel) text += '\n子任务: ' + s.agentLabel
629
+ text += '\n识图能力: ' + (s.visionLive === 'vision' ? '识图工具(视觉自检)' : s.visionLive === 'script' ? '脚本分析(无识图工具)' : s.vision ? '宿主侧已探测到' : '未探测到(由子智能体自检决定)')
518
630
  if (state.error) text += '\n错误: ' + state.error
519
631
  text += '\n输出目录: ' + (state.wsDir || '-')
520
632
  return { kind: 'success', text }
@@ -546,7 +658,7 @@ export function apply(ctx) {
546
658
  systemPrompt.section({
547
659
  name: 'mcmp-pipeline',
548
660
  order: 130,
549
- text: '数学建模流水线说明:当用户消息以 /loopbegin 开头(可带 --round=N、--from 文件或 --fresh 参数)时,插件已自动在后台启动「数学建模论文自动化流水线」(8 步骤、每轮 19 次迭代,由工作流子智能体执行)。此时你只需用一两句话确认已启动,并提示用户查看右下角进度面板与聊天区的工作流运行卡片;绝对不要自己去编写或执行该流水线的任何步骤,不要重复启动。若用户消息包含赛题但未以 /loopbegin 开头,说明尚未启动流水线,不要擅自运行。辅助命令 /loopstatus、/loopabort 由系统处理,你无需执行。',
661
+ text: '数学建模流水线说明:当用户消息以 /loopbegin 开头(可带 --round=N、--from 文件或 --fresh 参数)时,插件已自动在后台启动「数学建模论文自动化流水线」(8 步骤、每轮 19 次迭代,由工作流子智能体执行:赛题分析→模型建立与求解→编程实现→图表与流程图→图表视觉自检与修复→论文撰写→评审与修复落实→论文定稿)。此时你只需用一两句话确认已启动,并提示用户查看右下角进度面板与聊天区的工作流运行卡片;绝对不要自己去编写或执行该流水线的任何步骤,不要重复启动。流水线无论成败都会在 数学建模流水线/论文定稿.md 生成一版完整论文。若用户消息包含赛题但未以 /loopbegin 开头,说明尚未启动流水线,不要擅自运行。辅助命令 /loopstatus、/loopabort 由系统处理,你无需执行。',
550
662
  })
551
663
  } catch (err) { console.warn('dsh-mcmp: systemPrompt 注册失败: ' + String((err && err.message) || err)) }
552
664
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-mcmp",
3
- "version": "1.0.0",
4
- "description": "DeepSeek Harness 插件:数学建模论文自动化流水线(8 步骤 × 19 迭代 × N 轮、质疑驱动、断点续跑、进度面板)",
3
+ "version": "2.0.0",
4
+ "description": "DeepSeek Harness 插件:数学建模论文自动化流水线 v2(8 步骤 × 19 迭代 × N 轮:图表视觉自检与修复、评审问题修复闭环、论文兜底定稿、质疑驱动、断点续跑、进度面板)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -10,9 +10,13 @@
10
10
  "./package.json": "./package.json"
11
11
  },
12
12
  "files": [
13
- "lib"
13
+ "lib",
14
+ "cordis.patch.yml"
14
15
  ],
15
16
  "dsh": {
17
+ "bundle": {
18
+ "patch": "cordis.patch.yml"
19
+ },
16
20
  "client": {
17
21
  "platform": "web",
18
22
  "inject": [