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 +43 -101
- package/cordis.patch.yml +5 -0
- package/lib/index.js +185 -73
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
# mcmp
|
|
1
|
+
# dsh-mcmp · 数学建模论文自动化流水线(v2)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](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 |
|
|
21
|
-
| S2 |
|
|
22
|
-
| S3 |
|
|
23
|
-
| S4 |
|
|
24
|
-
| S5 |
|
|
25
|
-
| S6 | 论文撰写 | 2 次(
|
|
26
|
-
| S7 |
|
|
27
|
-
| S8 |
|
|
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
|
-
>
|
|
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
|
-
#
|
|
63
|
+
# 一条命令完成:安装包 + 自动注册(通过 bundle 机制)
|
|
59
64
|
pnpm dsh plugin --profile web add dsh-mcmp
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
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
|
-
|
|
74
|
-
pnpm dsh
|
|
75
|
-
#
|
|
69
|
+
# 然后重启:
|
|
70
|
+
# 重启 pnpm dsh web → 刷新网页
|
|
71
|
+
# 验证:浏览器访问 /mcmp-api/state,返回 JSON 即成功
|
|
76
72
|
```
|
|
77
73
|
|
|
78
|
-
|
|
74
|
+
**升级**:
|
|
79
75
|
|
|
80
76
|
```powershell
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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_
|
|
91
|
+
├── 01_赛题分析.md ~ 08_论文定稿.md # 八大步骤完整成果(逐迭代追加,含质疑清单/问题跟踪表)
|
|
92
|
+
├── 论文定稿.md # ★ 最终交付:完整、干净的 Markdown 论文(无论成败都会生成)
|
|
141
93
|
├── 代码/ # 全部验证与求解代码(_vN 后缀保留历史版本,绝不删除)
|
|
142
|
-
|
|
143
|
-
├──
|
|
144
|
-
└──
|
|
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
|
-
├─
|
|
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
|
-
>
|
|
122
|
+
> 仓库根目录即插件包本体(发布到 npm 的内容 = `lib/` + `cordis.patch.yml` + `package.json`)。
|
|
169
123
|
|
|
170
124
|
| 文件 | 内容 |
|
|
171
125
|
| --- | --- |
|
|
172
|
-
| `package.json` | 插件包清单:`dsh.client` 声明、`exports` 入口(`.` → Host,`./client` → 面板 bundle) |
|
|
173
|
-
| `
|
|
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
|
|
package/cordis.patch.yml
ADDED
package/lib/index.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-mcmp · 数学建模论文自动化流水线 —— Host 半(持久化部署插件)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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: '
|
|
28
|
-
{ name: '
|
|
29
|
-
{ name: '
|
|
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: '
|
|
33
|
-
{ name: '模型建立', task: '
|
|
34
|
-
{ name: '模型求解', task: '
|
|
35
|
-
{ name: '
|
|
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: '
|
|
38
|
-
{ name: '核心代码', task: '
|
|
39
|
-
{ name: '
|
|
40
|
-
{ name: '
|
|
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: '
|
|
43
|
-
{ name: '
|
|
44
|
-
{ name: '
|
|
45
|
-
{ name: '精选定稿', task: '
|
|
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: '
|
|
48
|
-
{ name: '
|
|
49
|
-
{ name: '
|
|
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
|
|
54
|
-
{ name: '
|
|
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: '
|
|
57
|
-
{ name: '
|
|
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: '
|
|
60
|
-
{ name: '
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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,
|
|
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
|
|
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: '
|
|
478
|
-
+ (resumeCount > 0 ? '\n断点续跑:已跳过此前完成的 ' + resumeCount + ' 次迭代,从第 ' + (resumeCount + 1) + ' 次继续。' : '\n
|
|
479
|
-
+ '\n
|
|
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: '已发送中止请求,流水线将在当前子任务结束后停止(
|
|
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
|
|
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": "
|
|
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": [
|