helixlife-v5-cli 1.2.5 → 1.2.7
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/lib/cli.js +1 -60
- package/package.json +1 -1
- package/references/vip.helixlife.cn.site-ops.md +24 -125
- package/scripts/workbench-jova-outline-regenerate-node.js +173 -154
- package/scripts/workbench-jova-proofread-section.js +262 -262
- package/scripts/workbench-jova-writing-tool.js +308 -310
- package/scripts/workbench-jova-dispatch.js +0 -370
package/lib/cli.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const fs = require('node:fs');
|
|
4
3
|
const path = require('node:path');
|
|
5
4
|
const { spawnSync } = require('node:child_process');
|
|
6
5
|
|
|
@@ -200,67 +199,11 @@ function helixDoctor(options) {
|
|
|
200
199
|
return helixHome(options);
|
|
201
200
|
}
|
|
202
201
|
|
|
203
|
-
/** npm 包内 scripts/ 目录(WorkBuddy 等工作区无 scripts/ 时回退) */
|
|
204
|
-
function packageScriptsDir() {
|
|
205
|
-
return path.join(__dirname, '..', 'scripts');
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* run-code --filename=scripts/foo.js:cwd 下不存在时,回退到 helixlife-v5-cli 包内 scripts/
|
|
210
|
-
* @param {string[]} tokens
|
|
211
|
-
* @returns {string[]}
|
|
212
|
-
*/
|
|
213
|
-
function resolveRunCodeFilename(tokens) {
|
|
214
|
-
const runIdx = tokens.indexOf('run-code');
|
|
215
|
-
if (runIdx === -1) {
|
|
216
|
-
return tokens;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const out = [...tokens];
|
|
220
|
-
for (let i = runIdx + 1; i < out.length; i += 1) {
|
|
221
|
-
let filePath = null;
|
|
222
|
-
let mode = null;
|
|
223
|
-
const t = out[i];
|
|
224
|
-
if (t.startsWith('--filename=')) {
|
|
225
|
-
filePath = t.slice('--filename='.length).replace(/^["']|["']$/g, '');
|
|
226
|
-
mode = 'eq';
|
|
227
|
-
} else if (t === '--filename' && out[i + 1]) {
|
|
228
|
-
filePath = out[i + 1].replace(/^["']|["']$/g, '');
|
|
229
|
-
mode = 'next';
|
|
230
|
-
}
|
|
231
|
-
if (!filePath) {
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const cwdResolved = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
236
|
-
if (fs.existsSync(cwdResolved)) {
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const base = path.basename(filePath.replace(/\\/g, '/'));
|
|
241
|
-
const pkgScript = path.join(packageScriptsDir(), base);
|
|
242
|
-
if (!fs.existsSync(pkgScript)) {
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (mode === 'eq') {
|
|
247
|
-
out[i] = `--filename=${pkgScript}`;
|
|
248
|
-
} else {
|
|
249
|
-
out[i + 1] = pkgScript;
|
|
250
|
-
}
|
|
251
|
-
if (process.env.HELIX_DEBUG_SCRIPT_RESOLVE) {
|
|
252
|
-
console.error(`[${PACKAGE_JSON.name}] run-code: ${filePath} → ${pkgScript}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return out;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
202
|
async function runPassthroughWithVisual(tokens) {
|
|
259
|
-
const resolved = resolveRunCodeFilename(tokens);
|
|
260
203
|
let visual;
|
|
261
204
|
let stripped;
|
|
262
205
|
try {
|
|
263
|
-
({ tokens: stripped, visual } = parseVisualFlags(
|
|
206
|
+
({ tokens: stripped, visual } = parseVisualFlags(tokens));
|
|
264
207
|
} catch (e) {
|
|
265
208
|
console.error(`[${PACKAGE_JSON.name}] ${e.message}`);
|
|
266
209
|
return 3;
|
|
@@ -333,6 +276,4 @@ module.exports = {
|
|
|
333
276
|
buildChildEnv,
|
|
334
277
|
DEFAULT_OUTPUT_DIR_NAME,
|
|
335
278
|
runPassthroughWithVisual,
|
|
336
|
-
resolveRunCodeFilename,
|
|
337
|
-
packageScriptsDir,
|
|
338
279
|
};
|
package/package.json
CHANGED
|
@@ -24,59 +24,6 @@
|
|
|
24
24
|
- **对用户回复的门禁**:在 `await fr.getByLabel('页码').inputValue()` 严格等于 `String(N)` 之前,禁止向用户输出「已跳到 / 已切换到 / 已完成第 N 页」等成功表述。验收未通过时如实说明未完成,给出读到的 `inputValue()` 或脚本抛错摘要、已执行步骤与下一步建议。
|
|
25
25
|
- **禁止误导性归因**:不得以「跨域 iframe / 同源策略导致父页脚本无法访问子 frame」作为**无法自动化跳页**的结论。父页 JS 同源限制不等于 Playwright 无法在 `fr` 上操作阅读器工具栏。若仍失败应报告**未找到 `fr`、定位超时、语言包下 `aria-label` 变更**等可核验原因,并指向 S-25 与 `scripts/chrome-pdf-enumerate-a11y.js`。
|
|
26
26
|
|
|
27
|
-
#### 工作台 · 「扩写前言」等章节 AI · Agent 硬门禁(操作前必审 · 与 PDF 跳页同级)
|
|
28
|
-
|
|
29
|
-
**触发词**(任一命中即适用本门禁):用户说 **「扩写 / 精简 / 润色 / 改写 / 重写 / 优化 + 章节名」**(如「扩写前言」「润色 1. 乳腺癌…」),或 **「校对 + 章节」**,或 **writing 页工具 + 章节**(如「翻译 前言」「降重 摘要」)。
|
|
30
|
-
|
|
31
|
-
**语义(最高优先级)**
|
|
32
|
-
|
|
33
|
-
| 用户说法 | 含义 | **绝不是** |
|
|
34
|
-
| --- | --- | --- |
|
|
35
|
-
| 扩写前言 | AI **重写**「前言」节点的**正文内容** | 提纲树 **添加子级** / **添加同级** |
|
|
36
|
-
| 润色 / 精简 / 改写 + 章节 | 同上,改该节文字 | 在树下 **新增条目** |
|
|
37
|
-
|
|
38
|
-
`snapshot` 里出现 `添加子级` 按钮 **不代表** 用户要添加子级;该按钮**仅**在用户明确说「加子标题 / 加一条同级提纲 / 添加子级」时使用。
|
|
39
|
-
|
|
40
|
-
**禁止(违规则视为任务失败,勿向用户报「已完成扩写」)**
|
|
41
|
-
|
|
42
|
-
- `click` → `getByRole('button', { name: '添加子级' })`
|
|
43
|
-
- `click` → `getByRole('button', { name: '添加同级' })`
|
|
44
|
-
- 在 **未** `run-code` 脚本前,对 outline 提纲树手写 `hover` + `click` 猜图标
|
|
45
|
-
- 因 snapshot 里有「添加子级」文案而推断用户要「扩写」
|
|
46
|
-
|
|
47
|
-
**必须(唯一合法第一步)**
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
# ① 定页
|
|
51
|
-
helixlife-v5-cli --raw eval "location.pathname"
|
|
52
|
-
|
|
53
|
-
# ② 统一入口(WorkBuddy 默认只跑这一条;用环境变量传 userIntent,勿改脚本文件)
|
|
54
|
-
# PowerShell: $env:HELIX_JOVA_INTENT='扩写前言'
|
|
55
|
-
# bash: HELIX_JOVA_INTENT='扩写前言' helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
56
|
-
helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
工作区无 `scripts/` 时 CLI **自动回退**到 `node_modules/helixlife-v5-cli/scripts/` 同名文件;**禁止**复制脚本到 WorkBuddy 目录再改 `userIntent`。
|
|
60
|
-
|
|
61
|
-
脚本内按 `pathname` 自动路由;**outline / writing 页**在 dispatch 内 **一次 run-code 跑完全流程**(含替换原文),**绝不**点「添加子级」。proofread 仍返回子脚本名。
|
|
62
|
-
|
|
63
|
-
**WorkBuddy · 执行约束(与 PDF 跳页同级)**
|
|
64
|
-
|
|
65
|
-
1. **只执行一条命令**:上表 `run-code` + `HELIX_JOVA_INTENT`;**禁止**在成功后追加 `hover` / `click e*` / `fill` / `sleep` / `snapshot` 手工重放。
|
|
66
|
-
2. **验收**:读 `Result` JSON 末尾 `"ok": true` → 向用户报完成;`"ok": false` → 只报 `reason`,**禁止**改用手动逐步操作代替。
|
|
67
|
-
3. **禁止**:将脚本复制到 `C:/Users/.../WorkBuddy/.../scripts/` 再编辑;禁止用绝对路径指向 WorkBuddy 副本。
|
|
68
|
-
4. **禁止**:`run-code` 返回 `ok:true` 后仍「深度思考 + snapshot + 逐步 click」——这是导致数分钟慢路径的根因。
|
|
69
|
-
|
|
70
|
-
**子脚本(仅 proofread 等 dispatch 未内联时)**
|
|
71
|
-
|
|
72
|
-
| `pathname` | 示例 userIntent | 子脚本 |
|
|
73
|
-
| --- | --- | --- |
|
|
74
|
-
| `…/jova/outline` | `扩写前言` | (dispatch 内直接执行) |
|
|
75
|
-
| `…/jova/writing` | `扩写前言` / `翻译 前言` | (dispatch 内直接执行;扩写类自动替换原文) |
|
|
76
|
-
| `…/jova/proofread` | `校对 前言` | `workbench-jova-proofread-section.js` |
|
|
77
|
-
|
|
78
|
-
细则与按钮对照表:下文 **WB-1 · 用户说法与脚本路由**。
|
|
79
|
-
|
|
80
27
|
#### 用户说法映射
|
|
81
28
|
|
|
82
29
|
- **深浅色**:用户说「切换颜色 / 换颜色 / 深色浅色 / 外观主题」等,已登录壳下**一律视为切换深浅色模式**(深色 ⇄ 浅色),见 S-16。
|
|
@@ -97,7 +44,6 @@ helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
|
97
44
|
- **定位与多命中**:推荐 `page.locator('main').getByText('部分章节匹配', { exact: true })`;多卡并列时**必须先缩小到目标课程卡片**,避免 strict 或误触(`scripts/hover-partial-chapter-match.js` 的 `cardTitle` 参数可直接用于此场景)。
|
|
98
45
|
- 自测(2026-05-11):`/edu/search?keywords=…` 课程结果首条 `hover` 后,可见 tooltip 浮层(`helixlife-v5-cli run-code --filename=scripts/hover-partial-chapter-match.js` 返回 `ok: true`,即 `tooltipLikeAfter > tooltipLikeBefore`)。
|
|
99
46
|
- **数据分析 · 详情页 · 上传文件**:用户说「上传文件 / 上传数据 / 换数据文件 / 导入 csv xlsx」等,且 `pathname` 为 `/analysis/{toolId}` 时,**一律走 D-2 三步弹窗流程**(打开弹窗 → 弹窗内选文件 → 弹窗内「确认」),**禁止**把左侧参数卡片底部「确认」(D-6 生成结果)当成上传确认。细则见下文「上传文件 · Agent 硬门禁」。
|
|
100
|
-
- **工作台 · 扩写/润色/精简/改写 + 章节**(如「扩写前言」):**一律**先 `run-code --filename=scripts/workbench-jova-dispatch.js`(改 `userIntent`),**禁止**点提纲树「添加子级 / 添加同级」。语义是 AI 重写该节正文(outline 上为 **编辑**→重新生成),不是新增子条目。细则见上文「工作台 · 扩写前言等 · Agent 硬门禁」与 WB-1。
|
|
101
47
|
|
|
102
48
|
#### 任务范围与跨域
|
|
103
49
|
|
|
@@ -209,7 +155,7 @@ helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
|
209
155
|
| 对话 · 新建会话 | S-09 |
|
|
210
156
|
| 工作站 · 新建任务弹窗 | S-10 |
|
|
211
157
|
| 工作台 · 进入子工作台 | S-11 |
|
|
212
|
-
| 工作台 ·
|
|
158
|
+
| 工作台 · 综述全流程(拟定主题→精修) | WB-1 |
|
|
213
159
|
| 工作台 · SCI 论著全流程 | WB-2 |
|
|
214
160
|
| 工作台 · 国自然基金全流程 | WB-3 |
|
|
215
161
|
| 工作台 · 临床申请书全流程 | WB-4 |
|
|
@@ -448,7 +394,7 @@ div.side-menu--item[to='/edu/courses']
|
|
|
448
394
|
| 步骤 | `pathname` | 主要表单项 / 区块 | 底栏主按钮(`getByRole('button', …)`) |
|
|
449
395
|
| --- | --- | --- | --- |
|
|
450
396
|
| 拟定主题 | `/workbench/jova/topic` | **生成模式**(三选一,`click` 文案):`短篇`(6000-10000字)、`中篇`(10000-15000字)、`长篇`(>15000字);**示例主题**列表 + `getByText('换一组', { exact: true })`;**综述主题** `getByRole('textbox', { name: '请输入综述主题概要内容' })`(上限 2000 字,界面显示 `0 / 2000`) | `继续编辑本地文档`;`生成提纲` |
|
|
451
|
-
| 确认提纲 | `/workbench/jova/outline` |
|
|
397
|
+
| 确认提纲 | `/workbench/jova/outline` | 左侧「论文提纲」树;节点行内为**无文案图标按钮**(仅 `编辑`/`添加同级`/`添加子级`/`删除` 四个**结构**操作,`hover` 后才出现,需时用 `generate-locator` 固化,勿记 `e*`)。**AI 重写(扩写/精简/润色等)不在这些按钮里**,见下方 WB-1 关键动作 5 | `返回主题`;`下载提纲`;`生成全文` |
|
|
452
398
|
| 初稿校对 | `/workbench/jova/proofread` | 左目录 + 右正文;段落带「请校对本段内容」提示 | `返回提纲`;`下载文档`;`完成校对` |
|
|
453
399
|
| 初稿精修 | `/workbench/jova/writing` | 左目录 + 中央 `textbox` 富文本;顶栏 **修订模式**(含接受/拒绝修订) | `返回提纲`;`修订模式` 切换钮(文案含 `接受修订` / `拒绝修订`);`新建论文`;`下载文档` |
|
|
454
400
|
|
|
@@ -458,86 +404,40 @@ div.side-menu--item[to='/edu/courses']
|
|
|
458
404
|
2. **生成提纲**:填主题后 `click` → `getByRole('button', { name: '生成提纲' })` → 验收 `/workbench/jova/outline`;等待「提纲内容生成中…」结束。
|
|
459
405
|
3. **生成全文(含弹窗)**:`click` → `getByRole('button', { name: '生成全文' })` → `dialog "是否已完成提纲修改,继续生成全文"` 内:`getByText('中文'|'English', { exact: true })`;综述另须 `getByText('是'|'否', { exact: true })`(「需要中文文献」);`click` → `getByRole('dialog', { name: '是否已完成提纲修改,继续生成全文' }).getByRole('button', { name: '确 认' })`(注意「确」「认」间有空格)。验收:`/workbench/jova/proofread`。
|
|
460
406
|
4. **完成校对 → 精修**:`click` → `getByRole('button', { name: '完成校对' })` → 二次 `dialog` 文案「是否确认完成所有段落内容校对,进入全文编辑模式」→ `getByRole('button', { name: '确认' })`(无空格)。验收:`/workbench/jova/writing`。
|
|
407
|
+
5. **节点 AI 重写(扩写 / 精简 / 润色 / 改写 / 重写 / 优化)**:用户说「扩写前言」「**扩写摘要**」「精简结论」「润色 1. xxx」等时,**严禁**点 `添加子级` / `添加同级`(那只会插入空节点)。正确流程:`hover` 目标提纲节点 → 点该节点行内 `编辑`(铅笔图标)→ 右侧出现「重新生成」面板 → 把**动词**(扩写/精简/…)填入 `getByRole('textbox', { name: '用户需求' })` → `getByRole('button', { name: '一键生成' })` → 等待 `替换原文` 可点 → `click` `替换原文` → Popconfirm「是否将本次生成结果替换原文?」`getByRole('button', { name: '确 定' })` → 左侧内联编辑器 `getByRole('button', { name: '保 存' })`。**意图拆分**:「<动词><节点标题>」(动词与标题间可有可无空格)→ `节点标题` 用于定位节点、`动词` 填入「用户需求」框;动词集合 = {扩写, 精简, 改写, 润色, 重写, 优化}。**Agent 硬门禁 · 提纲节点 AI 重写**(细则见下节):**一律** `run-code --filename=scripts/workbench-jova-outline-regenerate-node.js`,将用户**原话**传入 `HELIX_INTENT`(如 `扩写摘要`);**禁止**为不同节点标题新建脚本或手点逐步操作。
|
|
461
408
|
|
|
462
409
|
**取消全文生成弹窗**:同 dialog 内 `getByRole('button', { name: '取 消' })` 或 `Close`。
|
|
463
410
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
> 用户说「扩写前言」却被做成「添加子级」的根因:**同一动词在不同步骤页含义不同**,且 outline 树按钮默认隐藏(须 hover)。**必须先** `eval location.pathname`,**必须优先** `run-code` 下表脚本,禁止从 snapshot 猜「添加子级」。
|
|
467
|
-
|
|
468
|
-
**0 · 定页(第一步,不可跳过)**
|
|
411
|
+
###### 提纲节点 AI 重写 · Agent 硬门禁(WB-1 / outline · 必读)
|
|
469
412
|
|
|
470
|
-
>
|
|
413
|
+
> **触发**:`/workbench/jova/outline`(或结构相同的 `sci`/`nsfc-v2`/`clinical` outline 步)且用户指令含 {扩写, 精简, 改写, 润色, 重写, 优化} + 提纲节点名(前言、摘要、结论、1. xxx …)。
|
|
471
414
|
|
|
472
|
-
|
|
473
|
-
helixlife-v5-cli --raw eval "location.pathname"
|
|
474
|
-
# PowerShell: $env:HELIX_JOVA_INTENT='扩写前言'
|
|
475
|
-
helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
(用 `HELIX_JOVA_INTENT` 传用户说法;outline/writing 在 dispatch 内直接完成,禁止点「添加子级」。工作区无 `scripts/` 时 CLI 自动用 npm 包内脚本。)
|
|
479
|
-
|
|
480
|
-
**1 · 说法 → 脚本(jova 综述)**
|
|
481
|
-
|
|
482
|
-
| 当前 `pathname` | 用户说法(示例) | 脚本(工作区 `scripts/`) | Agent 改动的变量 |
|
|
483
|
-
| --- | --- | --- | --- |
|
|
484
|
-
| `…/jova/outline` | `扩写前言`、`扩写 前言`、`精简结论`、`润色 1. 乳腺癌…` | **`workbench-jova-dispatch.js`** | `HELIX_JOVA_INTENT` |
|
|
485
|
-
| `…/jova/writing` | `扩写前言`、`翻译 前言`、`润色 摘要`、`降重`、`文献列表` | **`workbench-jova-dispatch.js`** | `HELIX_JOVA_INTENT` |
|
|
486
|
-
| `…/jova/proofread` | `校对 前言`、`确认校对`、`AI 改写`、`替换原文` | dispatch 返回 → `workbench-jova-proofread-section.js` | `HELIX_JOVA_INTENT` + `HELIX_JOVA_ACTION` |
|
|
487
|
-
|
|
488
|
-
执行:`HELIX_JOVA_INTENT='扩写前言' helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js`(**唯一首选**;**禁止**成功后手工 hover/click/fill)
|
|
489
|
-
|
|
490
|
-
**2 · outline 页:扩写 ≠ 添加子级(反模式)**
|
|
491
|
-
|
|
492
|
-
确认提纲页左侧为**提纲树**(非正文)。节点行 `aria-label` 含「操作区」;**移入前**行内按钮不可见。移入后出现:
|
|
493
|
-
|
|
494
|
-
| 按钮 | 作用 | 用户何时才会要 |
|
|
415
|
+
| 用户说法示例 | `HELIX_INTENT` | 解析结果 |
|
|
495
416
|
| --- | --- | --- |
|
|
496
|
-
|
|
|
497
|
-
|
|
|
498
|
-
|
|
|
499
|
-
|
|
|
500
|
-
|
|
501
|
-
**禁止**:将「扩写 / 精简 / 润色 / 改写 + 章节名」映射为「添加子级」或「添加同级」。前者是 **AI 重写该节点正文**(`编辑` 路径),后者只改提纲层级。
|
|
502
|
-
|
|
503
|
-
`outline-regenerate-node.js` 已实现:定位 `[aria-label*="操作区"]` 且首列文案匹配章节 → hover → **编辑**(不点添加子级)。
|
|
417
|
+
| 扩写前言 | `扩写前言` | 节点=前言,用户需求=扩写 |
|
|
418
|
+
| 扩写摘要 | `扩写摘要` | 节点=摘要,用户需求=扩写 |
|
|
419
|
+
| 精简结论 | `精简结论` | 节点=结论,用户需求=精简 |
|
|
420
|
+
| 润色 1. 乳腺癌… | `润色 1. 乳腺癌…` | 节点=1. 乳腺癌…,用户需求=润色 |
|
|
504
421
|
|
|
505
|
-
|
|
422
|
+
**执行(必用同一脚本,禁止复制改写)**
|
|
506
423
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
| 文风 | `getByRole('textbox', { name: '参考文风' })` **必填** |
|
|
512
|
-
| 文献列表 | 仅打开列表,无一键生成 |
|
|
513
|
-
| 其余 7 项 | 原文内容 + `getByRole('button', { name: '一键生成' })` + 生成结果 + `替换原文` |
|
|
514
|
-
|
|
515
|
-
**4 · 生成后门禁**
|
|
516
|
-
|
|
517
|
-
- 脚本返回 `needsReplaceChoice` 或用户未说「替换原文」:**禁止**自动点「替换原文」。
|
|
518
|
-
- proofread 脚本:用户仅说「校对 xxx」时 **禁止**自动「确认校对」。
|
|
519
|
-
- writing · 文风:`参考文风` 为必填;`writing-tool.js` 会用 `referenceStyle` 或选中段落兜底。
|
|
520
|
-
|
|
521
|
-
**5 · 禁止的低效路径(导致 Agent 数分钟仍失败)**
|
|
522
|
-
|
|
523
|
-
1. 在 **outline** 页对用户「扩写/润色/精简 + 章节」点「添加子级」或「添加同级」——**永远不对**。
|
|
524
|
-
2. 不 `eval location.pathname` 就在 **proofread / writing** 页找提纲树「编辑」或「添加子级」。
|
|
525
|
-
3. `run-code` 已返回 `"ok": true` 仍用手动 `snapshot` + `hover` + `click e*` + `fill` + `sleep` 重放(WorkBuddy 慢路径根因)。
|
|
526
|
-
4. 将 `workbench-jova-dispatch.js` **复制到 WorkBuddy 工作区**再改 `userIntent`(应设 `HELIX_JOVA_INTENT`,CLI 自动回退 npm 包内脚本)。
|
|
527
|
-
5. 同一轮任务内反复 `snapshot` 猜 `e*` ref 定位提纲节点(须脚本内 `aria-label*="操作区"` + 章节文案匹配)。
|
|
528
|
-
|
|
529
|
-
**6 · 执行命令模板**
|
|
424
|
+
```powershell
|
|
425
|
+
# PowerShell — 将引号内换成用户原话
|
|
426
|
+
$env:HELIX_INTENT='扩写摘要'; helixlife-v5-cli run-code --filename=scripts/workbench-jova-outline-regenerate-node.js
|
|
427
|
+
```
|
|
530
428
|
|
|
531
429
|
```bash
|
|
532
|
-
#
|
|
533
|
-
helixlife-v5-cli --
|
|
534
|
-
|
|
535
|
-
# ② 一条命令完成(示例 PowerShell)
|
|
536
|
-
$env:HELIX_JOVA_INTENT='扩写前言'
|
|
537
|
-
helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
430
|
+
# Bash / macOS
|
|
431
|
+
HELIX_INTENT='扩写摘要' helixlife-v5-cli run-code --filename=scripts/workbench-jova-outline-regenerate-node.js
|
|
538
432
|
```
|
|
539
433
|
|
|
540
|
-
|
|
434
|
+
**反模式(常见导致误操作「前言」或重复造脚本)**
|
|
435
|
+
|
|
436
|
+
- 直接 `run-code` 而不设 `HELIX_INTENT` → 脚本会报错并拒绝执行(勿改脚本内默认值绕过)。
|
|
437
|
+
- 用户说「扩写摘要」却新建 `workbench-jova-outline-regenerate-abstract.js` 等副本 → **禁止**;节点标题差异仅体现在 `HELIX_INTENT`。
|
|
438
|
+
- 用 `添加子级` / `添加同级` 代替 AI 重写 → 只会插入空节点。
|
|
439
|
+
|
|
440
|
+
**显式拆分(可选)**:`HELIX_NODE_TITLE` + `HELIX_USER_REQUIREMENT` 同时设置时优先于 `HELIX_INTENT`。
|
|
541
441
|
|
|
542
442
|
---
|
|
543
443
|
|
|
@@ -619,7 +519,6 @@ helixlife-v5-cli run-code --filename=scripts/workbench-jova-dispatch.js
|
|
|
619
519
|
**用户说法映射**
|
|
620
520
|
|
|
621
521
|
- 「写综述 / 综述工作台」→ WB-1,`getByText('综述工作台')`。
|
|
622
|
-
- **「扩写前言 / 扩写 前言 / 润色摘要 / 精简结论 / 改写 xxx」**→ **禁止**提纲树「添加子级」;**必须** `run-code --filename=scripts/workbench-jova-dispatch.js`(改 `userIntent`)。见上文硬门禁与 WB-1 §2。
|
|
623
522
|
- 「SCI / 英文论文 / 论著工作台」→ WB-2,`getByText('SCI论著工作台')`。
|
|
624
523
|
- 「国自然 / 基金 / 标书」→ WB-3,`getByText('基金工作台')`(**不是**旧称「基金申请书工作台」)。
|
|
625
524
|
- 「临床申请书 / PICOS / 临床工作台」→ WB-4;面包屑为「临床申请书工作台」。
|
|
@@ -1,154 +1,173 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 综述工作台(jova)·
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* intent
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const intent = '
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
for (const verb of
|
|
35
|
-
if (
|
|
36
|
-
const title =
|
|
37
|
-
if (title) return { nodeTitle: title, userRequirement: verb };
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return { nodeTitle: '前言', userRequirement:
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
await
|
|
125
|
-
|
|
126
|
-
await
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
await
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 综述工作台(jova)· 确认提纲页 · 节点 AI 重新生成
|
|
3
|
+
*
|
|
4
|
+
* 典型场景:用户说「扩写前言」「扩写摘要」「精简结论」「润色 1. xxx」等。
|
|
5
|
+
*
|
|
6
|
+
* 流程:
|
|
7
|
+
* 悬停提纲节点 → 编辑 → 填写「用户需求」→「一键生成」
|
|
8
|
+
* → 等待「生成结果」→「替换原文」→ Popconfirm「确 定」→「保 存」
|
|
9
|
+
*
|
|
10
|
+
* Agent 调用约定(严格遵守):
|
|
11
|
+
* 1) 无论节点标题是「前言」「摘要」「结论」或任意提纲节名,一律复用本脚本,禁止新建同类脚本。
|
|
12
|
+
* 2) 将用户原话传入 HELIX_INTENT 后执行 run-code(勿依赖脚本内默认 intent):
|
|
13
|
+
* PowerShell: $env:HELIX_INTENT='扩写摘要'; helixlife-v5-cli run-code --filename=scripts/workbench-jova-outline-regenerate-node.js
|
|
14
|
+
* Bash: HELIX_INTENT='扩写摘要' helixlife-v5-cli run-code --filename=scripts/workbench-jova-outline-regenerate-node.js
|
|
15
|
+
* 3) 也可显式拆分:HELIX_NODE_TITLE + HELIX_USER_REQUIREMENT(同时设置时优先于 HELIX_INTENT)
|
|
16
|
+
*
|
|
17
|
+
* intent / HELIX_INTENT 解析规则(<动词><节点标题>,动词与标题间可有可无空格):
|
|
18
|
+
* 「扩写前言」 → nodeTitle=前言, userRequirement=扩写
|
|
19
|
+
* 「扩写摘要」 → nodeTitle=摘要, userRequirement=扩写
|
|
20
|
+
* 「精简结论」 → nodeTitle=结论, userRequirement=精简
|
|
21
|
+
* 「润色 1. xxx」 → nodeTitle=1. xxx, userRequirement=润色
|
|
22
|
+
* 无法解析时整句作为 userRequirement,nodeTitle 回退为「前言」
|
|
23
|
+
*/
|
|
24
|
+
async page => {
|
|
25
|
+
const intent = (process.env.HELIX_INTENT || '').trim();
|
|
26
|
+
|
|
27
|
+
const nodeTitleOverride = process.env.HELIX_NODE_TITLE?.trim() || null;
|
|
28
|
+
const userRequirementOverride = process.env.HELIX_USER_REQUIREMENT?.trim() || null;
|
|
29
|
+
|
|
30
|
+
const VERBS = ['扩写', '精简', '改写', '润色', '重写', '优化'];
|
|
31
|
+
|
|
32
|
+
function parseIntent(raw) {
|
|
33
|
+
const normalized = raw.replace(/[::]\s*$/, '').trim();
|
|
34
|
+
for (const verb of VERBS) {
|
|
35
|
+
if (normalized.startsWith(verb)) {
|
|
36
|
+
const title = normalized.slice(verb.length).trim();
|
|
37
|
+
if (title) return { nodeTitle: title, userRequirement: verb };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { nodeTitle: '前言', userRequirement: normalized || raw };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!intent && !nodeTitleOverride) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
reason:
|
|
47
|
+
'缺少 HELIX_INTENT(用户原话,如「扩写摘要」)或 HELIX_NODE_TITLE。禁止直接 run-code 而不传意图——否则会误操作默认节点。',
|
|
48
|
+
hint: "PowerShell: $env:HELIX_INTENT='扩写摘要'; helixlife-v5-cli run-code --filename=scripts/workbench-jova-outline-regenerate-node.js",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const parsed = intent ? parseIntent(intent) : { nodeTitle: '', userRequirement: '' };
|
|
53
|
+
const nodeTitle = nodeTitleOverride ?? parsed.nodeTitle;
|
|
54
|
+
const userRequirement = userRequirementOverride ?? parsed.userRequirement;
|
|
55
|
+
|
|
56
|
+
if (!nodeTitle || !userRequirement) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
reason: '无法解析节点或动词。请传 HELIX_INTENT(如「扩写摘要」)或同时传 HELIX_NODE_TITLE + HELIX_USER_REQUIREMENT。',
|
|
60
|
+
intent,
|
|
61
|
+
nodeTitle,
|
|
62
|
+
userRequirement,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const main = page.locator('main');
|
|
67
|
+
|
|
68
|
+
if (!page.url().includes('/workbench/jova/outline')) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
reason: '当前不在综述工作台确认提纲页 /workbench/jova/outline',
|
|
72
|
+
intent,
|
|
73
|
+
nodeTitle,
|
|
74
|
+
userRequirement,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function resetEditingState() {
|
|
79
|
+
const closeIcons = main.getByRole('img', { name: 'close' });
|
|
80
|
+
for (let i = 0; i < (await closeIcons.count()); i++) {
|
|
81
|
+
const icon = closeIcons.nth(i);
|
|
82
|
+
if (await icon.isVisible()) {
|
|
83
|
+
await icon.click();
|
|
84
|
+
await page.waitForTimeout(400);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const cancelReplace = main.getByRole('button', { name: '取 消' });
|
|
88
|
+
if ((await cancelReplace.count()) > 0 && (await cancelReplace.first().isVisible())) {
|
|
89
|
+
await cancelReplace.first().click();
|
|
90
|
+
await page.waitForTimeout(400);
|
|
91
|
+
}
|
|
92
|
+
const saveBtn = main.getByRole('button', { name: '保 存' });
|
|
93
|
+
if ((await saveBtn.count()) > 0 && (await saveBtn.first().isVisible())) {
|
|
94
|
+
await saveBtn.first().click();
|
|
95
|
+
await page.waitForTimeout(400);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function outlineNode(title) {
|
|
100
|
+
const base = title.replace(/[::]\s*$/, '');
|
|
101
|
+
const titleRe = new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[::]?$`);
|
|
102
|
+
return main
|
|
103
|
+
.locator('[aria-label*="操作区"]')
|
|
104
|
+
.filter({
|
|
105
|
+
has: page.locator(':scope > *').first().filter({ hasText: titleRe }),
|
|
106
|
+
})
|
|
107
|
+
.first();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await resetEditingState();
|
|
111
|
+
|
|
112
|
+
const opArea = outlineNode(nodeTitle);
|
|
113
|
+
if ((await opArea.count()) === 0) {
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
reason: `未找到提纲节点「${nodeTitle}」`,
|
|
117
|
+
intent,
|
|
118
|
+
nodeTitle,
|
|
119
|
+
userRequirement,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 1. 悬停 → 编辑
|
|
124
|
+
await opArea.hover();
|
|
125
|
+
await page.waitForTimeout(400);
|
|
126
|
+
await opArea.getByRole('button', { name: '编辑' }).click();
|
|
127
|
+
|
|
128
|
+
// 2. 右侧「重新生成」面板
|
|
129
|
+
const regenTitle = main.getByText('重新生成', { exact: true });
|
|
130
|
+
await regenTitle.waitFor({ state: 'visible', timeout: 10000 });
|
|
131
|
+
|
|
132
|
+
const userReqBox = main.getByRole('textbox', { name: '用户需求' });
|
|
133
|
+
await userReqBox.waitFor({ state: 'visible', timeout: 5000 });
|
|
134
|
+
await userReqBox.fill(userRequirement);
|
|
135
|
+
|
|
136
|
+
// 3. 一键生成 → 等待「替换原文」可用
|
|
137
|
+
await main.getByRole('button', { name: '一键生成' }).click();
|
|
138
|
+
const replaceBtn = main.getByRole('button', { name: '替换原文' });
|
|
139
|
+
await replaceBtn.waitFor({ state: 'visible', timeout: 120000 });
|
|
140
|
+
await page.waitForTimeout(800);
|
|
141
|
+
|
|
142
|
+
// 4. 替换原文 → Popconfirm「是否将本次生成结果替换原文?」→ 确 定
|
|
143
|
+
await replaceBtn.click();
|
|
144
|
+
const confirmPrompt = main.getByText('是否将本次生成结果替换原文?', { exact: true });
|
|
145
|
+
await confirmPrompt.waitFor({ state: 'visible', timeout: 10000 });
|
|
146
|
+
await main.getByRole('button', { name: '确 定' }).click();
|
|
147
|
+
await page.waitForTimeout(800);
|
|
148
|
+
|
|
149
|
+
// 5. 左侧内联编辑器「保 存」
|
|
150
|
+
const saveBtn = main.getByRole('button', { name: '保 存' });
|
|
151
|
+
await saveBtn.waitFor({ state: 'visible', timeout: 10000 });
|
|
152
|
+
const inlineEditor = saveBtn.locator('xpath=ancestor::li[1]//textarea | ancestor::li[1]//input[@type="text"] | ancestor::li[1]//*[@role="textbox"]').first();
|
|
153
|
+
const editorText = (await inlineEditor.inputValue().catch(() => '')) || (await inlineEditor.textContent().catch(() => '')) || '';
|
|
154
|
+
|
|
155
|
+
await saveBtn.click();
|
|
156
|
+
await page.waitForTimeout(1000);
|
|
157
|
+
|
|
158
|
+
const panelClosed = !(await regenTitle.isVisible().catch(() => false));
|
|
159
|
+
const nodePresent = (await outlineNode(nodeTitle).count()) > 0;
|
|
160
|
+
const contentApplied = editorText.includes(nodeTitle) || editorText.length > 0;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
ok: nodePresent && panelClosed && contentApplied,
|
|
164
|
+
intent,
|
|
165
|
+
nodeTitle,
|
|
166
|
+
userRequirement,
|
|
167
|
+
panelClosed,
|
|
168
|
+
nodePresent,
|
|
169
|
+
contentApplied,
|
|
170
|
+
editorTextLength: editorText.length,
|
|
171
|
+
url: page.url(),
|
|
172
|
+
};
|
|
173
|
+
}
|