dsh-data-cleaning-agent 0.8.0 → 0.8.2
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/CHANGELOG.md +27 -1
- package/README.en.md +15 -9
- package/README.md +15 -9
- package/docs/COMPATIBILITY.md +9 -1
- package/docs/RELEASE-0.8.1.md +81 -0
- package/docs/UI-WORKFLOW-V2.md +2 -2
- package/docs/USER-GUIDE.md +30 -5
- package/lib/client.js +77 -48
- package/lib/image-intake.js +210 -34
- package/lib/web.js +2 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录 `dsh-data-cleaning-agent` 的版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [0.8.2] - 2026-09-06
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- 单一会话所有权:入口改用 `sessions.create({ workspaceId, sessionId })` 显式创建带 `session-dsh-data-cleaning-agent-` 前缀的独立原生会话,不再复用空白会话(否则会与「招投标」「访前尽调」共享同一会话,导致标题与右侧工作台面板归属冲突)。
|
|
9
|
+
- 图片暂存与 OCR 连接检查解耦:尚未连接本地文档解析时仍显示缩略图,允许继续选规则、字段并生成完整说明;执行 OCR 时仍严格检查连接,不调用远端 URL 接口读取本地文件。
|
|
10
|
+
- 图片连接状态随轮询更新;15 分钟暂存期内补齐连接后可直接使用原图片任务。
|
|
11
|
+
- Composer 和向导图片粘贴同时兼容剪贴板 `files` 与 `items`,拖入共用相同取图逻辑。
|
|
12
|
+
|
|
13
|
+
## [0.8.1] - 2026-09-05
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- 图片企业名单识别切换为企查查智能文档解析:本地图片只通过官方 `qcc-document-mcp`
|
|
17
|
+
`parse_document(file_path)` 提交,并仅在异步处理中按返回的 `task_id` 查询 `get_parse_result`。
|
|
18
|
+
- 提示词向导不再要求先发送 OCR 专用说明;用户可一次完成图片、匹配规则、清洗动作和补全字段选择,
|
|
19
|
+
再向原生对话框回填一段完整、可读、可编辑的中文任务说明。
|
|
20
|
+
- 识别完成后名单直接进入同一 taskId 工作台人工核对,继续现有主体匹配、字段补全和导出流程。
|
|
21
|
+
|
|
22
|
+
### Security
|
|
23
|
+
- Host 仍以 0700 目录和 0600 文件临时保存图片,成功、失败、取消或 TTL 到期后删除;原图不进入
|
|
24
|
+
`storageDomain`、任务元数据、模型上下文或导出制品。
|
|
25
|
+
- 远端 `qcc-document` 只接受公网 `file_url`,不能读取本机临时图片;仅连接远端服务时明确 fail closed,
|
|
26
|
+
不自动上传图片、不暴露本地路径,也不回退到聊天模型视觉能力。
|
|
27
|
+
- 文档解析与后续企业补全均使用当前用户自己连接的企查查账号和额度;插件不读取、保存或分发凭据。
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- 文本模型(包括不支持图片的 DeepSeek 模型)不再收到原生图片附件,避免在执行清洗任务前被模型能力门拒绝。
|
|
31
|
+
- 统一解析企查查文档任务状态、Markdown 结果与认证失败,支持处理中轮询、超时和安全错误提示。
|
|
6
32
|
|
|
7
33
|
## [0.8.0] - 2026-09-05
|
|
8
34
|
|
package/README.en.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> A data cleaning & completion agent plugin for DeepSeek Harness: local CSV/XLSX/JSON engine plus optional Qichacha (QCC) MCP enterprise-data enrichment. Initiated and maintained by the Qichacha (QCC) team.
|
|
4
4
|
>
|
|
5
|
-
> Current source version / 当前源码版本: **0.8.
|
|
5
|
+
> Current source version / 当前源码版本: **0.8.2** (stable release)
|
|
6
6
|
|
|
7
7
|
[](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml)
|
|
8
8
|
[](https://www.npmjs.com/package/dsh-data-cleaning-agent)
|
|
@@ -49,14 +49,18 @@ task brief back to the native composer. Five workflow actions (upload, profile,
|
|
|
49
49
|
sit below the composer and open the five-step workbench (upload, rules, match, enrich, download)
|
|
50
50
|
on demand. Completed tasks expose four durable Host artifacts: result and exception-list CSV/XLSX.
|
|
51
51
|
|
|
52
|
-
Since 0.8.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
then
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
Since 0.8.1, users can paste, drop, or select a PNG/JPEG/WebP image, finish match rules and enrichment
|
|
53
|
+
field selection, and write one complete editable task brief to the native composer; the separate OCR-only
|
|
54
|
+
turn has been removed. Before send, the client releases the native image attachment so text-only models can
|
|
55
|
+
still dispatch the Agent-owned high-level tool. That tool calls the official local `qcc-document-mcp`
|
|
56
|
+
`parse_document(file_path)` exactly once and polls `get_parse_result` with the returned `task_id` only while
|
|
57
|
+
processing, then returns company names/credit codes to the workbench for human review.
|
|
58
|
+
|
|
59
|
+
Local image parsing requires the official `qcc-document-mcp` stdio server to be configured in the MCP
|
|
60
|
+
connector. The remote `qcc-document` connector accepts public `file_url` values only and cannot read Host
|
|
61
|
+
temporary files. The local server uploads the explicitly submitted file to the QCC document parsing gateway.
|
|
62
|
+
Both use the current customer's own QCC account and quota. Without the local connector,
|
|
63
|
+
image intake fails closed with actionable guidance; text and spreadsheet paths remain available.
|
|
60
64
|
|
|
61
65
|
Without the `dsh` CLI, use the install script:
|
|
62
66
|
|
|
@@ -131,6 +135,8 @@ See [the 0.7.0 release record](docs/RELEASE-0.7.0.md) for the first 40 and secon
|
|
|
131
135
|
one-company-one-row fields, deduplicated QCC tool dispatch, and real two-company acceptance.
|
|
132
136
|
See [the 0.8.0 release record](docs/RELEASE-0.8.0.md) for image paste/drop, native previews,
|
|
133
137
|
Agent-owned OCR, and the human-review handoff.
|
|
138
|
+
See [the 0.8.1 release record](docs/RELEASE-0.8.1.md) for QCC intelligent document parsing
|
|
139
|
+
and the single editable task brief.
|
|
134
140
|
See [the 0.6.3 release record](docs/RELEASE-0.6.3.md) for multiline entity-list parsing,
|
|
135
141
|
editable execution summaries, and workbench/composer layout fixes.
|
|
136
142
|
See [the 0.6.2 release record](docs/RELEASE-0.6.2.md) for Chinese export headers,
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 在 DeepSeek Harness 中清洗、补全、画像企业名单数据的智能体插件:本地 CSV/XLSX/JSON 引擎 + 可选企查查(Qichacha/QCC)MCP 企业数据补全,由企查查(Qichacha/QCC)团队发起并维护。
|
|
4
4
|
>
|
|
5
|
-
> 当前源码版本 / Current source version: **0.8.
|
|
5
|
+
> 当前源码版本 / Current source version: **0.8.2**(正式版本)
|
|
6
6
|
|
|
7
7
|
[](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml)
|
|
8
8
|
[](https://www.npmjs.com/package/dsh-data-cleaning-agent)
|
|
@@ -42,12 +42,17 @@ DSH 原生会话:首页标题、产品说明与工作流会替换通用探索
|
|
|
42
42
|
字段补全和任务历史五个入口位于输入框下方,右侧工作台承载“上传数据 → 规则确认 → 数据匹配 →
|
|
43
43
|
清洗补全 → 下载数据”五步闭环。完成后可下载结果与异常清单的 CSV/XLSX 四类 Host 耐久制品。
|
|
44
44
|
|
|
45
|
-
0.8.
|
|
46
|
-
拖入/选择 PNG、JPEG、WebP
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
0.8.1 起,在数据清洗补全会话的原生输入框粘贴图片,或在「提示词生成」的图片页
|
|
46
|
+
拖入/选择 PNG、JPEG、WebP,会显示可点击放大的原生缩略图。用户可继续完成匹配规则、清洗项与补全字段,
|
|
47
|
+
最后一次性把完整、可编辑的任务说明回填到对话框;不再先发送一轮 OCR 专用指令。发送前 Client 释放
|
|
48
|
+
Composer 图片附件,因此不支持视觉输入的文本模型也能调度 Agent-owned 高层工具。高层工具通过官方
|
|
49
|
+
`qcc-document-mcp` 的 `parse_document(file_path)` 提交本地图片,仅在异步处理中以同一 `task_id`
|
|
50
|
+
调用 `get_parse_result`,再将企业名称/信用代码交回工作台人工核对。
|
|
51
|
+
|
|
52
|
+
本地图片解析需在 MCP 连接器中另行配置官方 `qcc-document-mcp`;市场中的远端 `qcc-document`
|
|
53
|
+
只支持公网 `file_url`,不能读取本机图片。两者使用当前客户自己的企查查账号与额度,本插件不读取、保存
|
|
54
|
+
或分发凭据。本地服务会把用户明确提交的图片上传到企查查文档解析网关;未连接本地服务时功能会
|
|
55
|
+
fail closed,并明确提示配置要求,文本与 Excel 流程不受影响。
|
|
51
56
|
|
|
52
57
|
没有 `dsh` CLI 时,也可以用安装脚本:
|
|
53
58
|
|
|
@@ -73,7 +78,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
|
|
|
73
78
|
| 耐久制品 | Host 工作区 `.dsh-data-cleaning-artifacts/v1` | 结果/异常 CSV+XLSX,checksum 校验,跨 Host 重启下载 |
|
|
74
79
|
| 应用内入口 | 侧栏顶部「数据清洗补全」+ 输入框下方五能力按钮 | 中央业务首页与原生对话,右侧按需打开 Mockup 对齐工作台 |
|
|
75
80
|
| 提示词生成 | `conversation.input.overlay` | 文本 / Excel / 图片录入、清洗项与补全维度选择,生成后回填原生输入框供人工修改 |
|
|
76
|
-
| 图片名单 | `data_cleaning_extract_image_companies` +
|
|
81
|
+
| 图片名单 | `data_cleaning_extract_image_companies` + `qcc-document-mcp` | 原生缩略图/放大、粘贴/拖入;一次性完整任务说明;`parse_document` → 按需 `get_parse_result` → 人工核对 |
|
|
77
82
|
| 工具卡片 | `tool.call.toolview`(清洗/补全/画像/图片名单) | 对话内渲染四类工具结果卡,含运行/已完成/失败状态 |
|
|
78
83
|
| 任务进度 | 工作台头部任务 pill | 轮询 `/data-cleaning/api/mvp/jobs`,展示排队/运行中任务 |
|
|
79
84
|
| Skill | `data-cleaning` | 引导模型按工作流调度上述工具 |
|
|
@@ -123,7 +128,8 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
|
|
|
123
128
|
0.5.3 业务首页、提示词生成和输入框下方流程栏见 [docs/RELEASE-0.5.3.md](docs/RELEASE-0.5.3.md)。
|
|
124
129
|
0.7.0 第一批 40 字段、第二批 58 字段、调用编排和真实两企业验收见
|
|
125
130
|
[docs/RELEASE-0.7.0.md](docs/RELEASE-0.7.0.md)。0.8.0 图片粘贴、原生缩略图、Agent-owned OCR 和人工核对流程见
|
|
126
|
-
[docs/RELEASE-0.8.0.md](docs/RELEASE-0.8.0.md)。0.
|
|
131
|
+
[docs/RELEASE-0.8.0.md](docs/RELEASE-0.8.0.md)。0.8.1 企查查智能文档解析与单次任务说明见
|
|
132
|
+
[docs/RELEASE-0.8.1.md](docs/RELEASE-0.8.1.md)。0.6.3 多行名单解析、可编辑执行说明与工作台遮挡修复见
|
|
127
133
|
[docs/RELEASE-0.6.3.md](docs/RELEASE-0.6.3.md)。0.6.2 中文导出表头、完整字段选择传递与画像字段补全见
|
|
128
134
|
[docs/RELEASE-0.6.2.md](docs/RELEASE-0.6.2.md)。0.6.1 Agent-owned QCC 工作台、会话隔离、恢复统计及真实闭环验收见
|
|
129
135
|
[docs/RELEASE-0.6.1.md](docs/RELEASE-0.6.1.md)。0.6.0 五步 taskId 工作流、耐久制品、
|
package/docs/COMPATIBILITY.md
CHANGED
|
@@ -50,6 +50,13 @@ alpha.2 只作兼容探针。全程未触碰生产 43120,未调用 QCC。
|
|
|
50
50
|
Agent-owned 工具通过 `dci-*` 凭证读取 Host 临时副本。因此图片选择与预览沿用 DSH 原生 UI,实际识别轮仍是
|
|
51
51
|
文本模型可接受的纯文本工具调用。
|
|
52
52
|
|
|
53
|
+
0.8.1 将当前图片 Provider 收敛到企查查官方本地 stdio 服务 `qcc-document-mcp`。本地服务的
|
|
54
|
+
`parse_document` 接受 `file_path`,返回完成结果或 `task_id`;只有状态仍为处理中时才调用
|
|
55
|
+
`get_parse_result(task_id)`。市场远端 `qcc-document` 的 `parse_document` 只接受公网 `file_url`,
|
|
56
|
+
不能读取 Host 临时文件,因此只连接远端服务时返回
|
|
57
|
+
`DC_IMAGE_LOCAL_DOCUMENT_PROVIDER_REQUIRED`。该版本保留 0.8.0 的 DSH 原生附件 API 兼容层,
|
|
58
|
+
但不再依赖 Modlens,也不把图片交给当前聊天模型。
|
|
59
|
+
|
|
53
60
|
## 2. Node 运行时
|
|
54
61
|
|
|
55
62
|
- 本包 `engines.node` 声明 `>=20`。
|
|
@@ -70,7 +77,8 @@ Agent-owned 工具通过 `dci-*` 凭证读取 Host 临时副本。因此图片
|
|
|
70
77
|
| 同源守卫 | `isTrusted(req)` | `sec-fetch-site !== 'cross-site'` 且 origin 为 127.0.0.1/localhost |
|
|
71
78
|
| Agent-owned 动态工具调用 | `ctx.tools.register()` 高层工具 + `ctx.tools.get()` + 带 `parent/agent/rootCallId` 的 `ctx.tools.execute()` | rc.2 Code Mode 实测要求 nested execution;每次调用重新解析,不缓存动态 MCP 工具 |
|
|
72
79
|
| 原生图片附件 | `conversation.createDraftImages/releaseDraftImage(s)` + `input.shell().addImages/removeImage` | rc.2 已验证;Client 运行时探测,缺失时 fail closed |
|
|
73
|
-
|
|
|
80
|
+
| 本地图片文档解析 | `ctx.tools.get()` 探测 `qcc-document-mcp` 的 `parse_document/get_parse_result` + Agent-owned nested `ctx.tools.execute()` | 0.8.1 当前实现;要求 `parse_document` 支持 `file_path`,不是 npm 强依赖 |
|
|
81
|
+
| 远端图片文档解析 | `mcp__qcc-document__parse_document(file_url)` | 只支持公网 URL;不能用于 Host 本地临时图片,当前流程明确 fail closed |
|
|
74
82
|
|
|
75
83
|
## 4. 与企查查 MCP OAuth 插件的共存
|
|
76
84
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# dsh-data-cleaning-agent 0.8.1 发布记录
|
|
2
|
+
|
|
3
|
+
> 日期:2026-09-05
|
|
4
|
+
> 主题:企查查智能文档解析图片名单与单次完整任务说明
|
|
5
|
+
|
|
6
|
+
## 1. 产品结果
|
|
7
|
+
|
|
8
|
+
0.8.1 修正图片名单入口的执行路径:
|
|
9
|
+
|
|
10
|
+
1. 用户在提示词向导粘贴、拖入或选择 PNG/JPEG/WebP 图片,继续选择匹配规则、清洗动作与补全字段。
|
|
11
|
+
2. 向导一次生成完整、可读、可编辑的中文任务说明,不再要求先发送 OCR 专用指令、识别后再发送第二轮任务。
|
|
12
|
+
3. 回填前 Client 释放 Composer 原生图片附件;DeepSeek 等不支持图片的文本模型只接收纯文本任务说明。
|
|
13
|
+
4. Agent 只调用一次 `data_cleaning_extract_image_companies` 高层工具;Host 以父执行上下文调用用户连接的企查查智能文档解析。
|
|
14
|
+
5. Host 通过本地 `qcc-document-mcp` 准确提交一次 `parse_document(file_path)`,仅在任务仍处理中时使用同一 `task_id` 查询 `get_parse_result`。
|
|
15
|
+
6. 识别出的企业名称和统一社会信用代码进入同一 taskId 工作台人工核对,再继续主体匹配、字段补全和 CSV/XLSX 导出。
|
|
16
|
+
|
|
17
|
+
## 2. 连接要求
|
|
18
|
+
|
|
19
|
+
本地图片必须使用官方 `qcc-document-mcp` stdio 服务:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"qcc-document-mcp": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "qcc-document-mcp"],
|
|
27
|
+
"env": {
|
|
28
|
+
"QCC_DOCUMENT_AUTHORIZATION": "Bearer YOUR_API_KEY"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- Key 由用户配置在自己的 DSH/MCP 环境中,不进入插件代码、日志或发布包。
|
|
36
|
+
- `qcc-document-mcp` 会读取本地文件并上传到企查查文档解析网关;用户只应提交有权处理且允许上传的数据。
|
|
37
|
+
- 远端市场连接 `qcc-document` 的 `parse_document` 只接受公网 `file_url`,不能读取 Host 本机路径。
|
|
38
|
+
- 只连接远端服务时,本插件返回 `DC_IMAGE_LOCAL_DOCUMENT_PROVIDER_REQUIRED`,不会自动上传图片、暴露本地路径或回退到聊天模型视觉能力。
|
|
39
|
+
- 文档解析额度或费用由当前用户自己的企查查账号承担;后续企业补全继续遵守现有 BYO QCC 确认门。
|
|
40
|
+
|
|
41
|
+
## 3. 安全边界
|
|
42
|
+
|
|
43
|
+
- 图片限制为 PNG/JPEG/WebP、8 MiB,使用魔数而非文件扩展名校验。
|
|
44
|
+
- Host 临时目录权限 0700、文件权限 0600;成功、失败、移除或 15 分钟 TTL 到期后删除。
|
|
45
|
+
- 原图不写入 `storageDomain`、工作流任务元数据、模型上下文或导出制品。
|
|
46
|
+
- Agent 只能取得随机 `dci-*` commandId,看不到 Host 文件路径;Web handler 不直接派发动态 MCP 工具。
|
|
47
|
+
- 文档解析失败、认证失效、协议异常与超时均安全收敛,不重试提交或扩大调用范围。
|
|
48
|
+
|
|
49
|
+
## 4. 验收范围
|
|
50
|
+
|
|
51
|
+
- `npm run check`:语法、版本一致性、市场素材、发布白名单与全量自动化测试。
|
|
52
|
+
- 图片契约:QCC Markdown 结果提取、一次提交、处理中轮询、两企业抽取、临时文件清理、认证/协议/超时错误。
|
|
53
|
+
- Provider 门:本地服务未配置和仅有远端 `qcc-document` 时分别 fail closed,并返回可操作说明。
|
|
54
|
+
- Client 契约:原生缩略图、一次完整任务说明、发送前释放附件、识别结果进入同一 taskId 工作台。
|
|
55
|
+
- Web 契约:能力预检报告当前用户 QCC 文档连接计费边界,高层工具保持 Agent-owned nested execution。
|
|
56
|
+
|
|
57
|
+
真实图片 QCC E2E 需要维护者在隔离 DSH Profile 中配置自己的 `qcc-document-mcp` Key;发布包不包含测试凭据或真实解析响应。
|
|
58
|
+
|
|
59
|
+
## 5. 升级与回滚
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
dsh plugin --profile web add dsh-data-cleaning-agent@0.8.1
|
|
63
|
+
# 完全停止后重启
|
|
64
|
+
dsh web
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
0.8.1 未修改 `dc_workflows_v2`、制品 schema 或已完成任务,可直接回滚:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
dsh plugin --profile web add dsh-data-cleaning-agent@0.8.0
|
|
71
|
+
# 完全停止后重启
|
|
72
|
+
dsh web
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
回滚会恢复 0.8.0 的 Modlens 两轮图片流程;文本、Excel、主体匹配、字段补全和历史制品不受影响。
|
|
76
|
+
|
|
77
|
+
## 6. 发布结果
|
|
78
|
+
|
|
79
|
+
- 发布提交 `4d71308620115acaf78b215552565a1db4199168` 通过 Release workflow run `33977279908` 的标签校验、完整 `npm run check` 门禁、npm 发布和 GitHub Release 创建,全部成功。
|
|
80
|
+
- npm 通过 GitHub Actions OIDC Trusted Publishing 发布并签署 provenance,未使用长期 npm Token;registry 回读确认 `latest` 为 `0.8.1`。
|
|
81
|
+
- GitHub Release:<https://github.com/duhu2000/dsh-data-cleaning-agent/releases/tag/v0.8.1>。
|
package/docs/UI-WORKFLOW-V2.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
业务主流程与企查查专业版“数据清洗补全”一致,固定为五步:
|
|
10
10
|
|
|
11
|
-
1. **上传数据**:文本、CSV、XLSX、JSON;0.8.
|
|
11
|
+
1. **上传数据**:文本、CSV、XLSX、JSON;0.8.1 支持 PNG/JPEG/WebP 企业名单图片,经 DSH 原生附件缩略图、Host 临时文件、企查查智能文档解析和人工核对后进入同一 taskId 流程。
|
|
12
12
|
2. **规则确认**:字段映射、清洗目标、匹配规则、补全字段选择。
|
|
13
13
|
3. **数据匹配**:以企业名称、统一社会信用代码或注册号作为主体锚点;精确、候选、已确认、未匹配、失败分流。
|
|
14
14
|
4. **清洗补全**:本地确定性清洗优先;需要 QCC 数据时,先估算调用,再由当前用户确认使用自己的 QCC 账号额度。
|
|
@@ -171,7 +171,7 @@ draft → uploaded → rules_confirmed → diagnosed(可选)
|
|
|
171
171
|
## 8. 当前实现与发布顺序
|
|
172
172
|
|
|
173
173
|
1. T3(完成):上传解析、字段映射、任务设置和规则确认接入 v2 taskId API。
|
|
174
|
-
2. T4(完成):提示词生成器四步向导接入 taskId 工作流,支持文本/本地文件;0.8.
|
|
174
|
+
2. T4(完成):提示词生成器四步向导接入 taskId 工作流,支持文本/本地文件;0.8.1 支持图片粘贴/拖入、缩略图、企查查智能文档解析、名单回传和人工核对,并将图片识别与后续规则/字段要求合并为一次可编辑任务说明。
|
|
175
175
|
3. T5(完成):中央业务首页、五能力入口、右侧工作台、基础企业匹配/补全和任务历史统一到 taskId。
|
|
176
176
|
4. T6(完成):Host 耐久下载制品、XLSX 与异常清单、双基线、视觉回归、迁移与回滚。
|
|
177
177
|
5. T7(完成):候选人工核验、部分失败重试、匹配与补全状态闭环。
|
package/docs/USER-GUIDE.md
CHANGED
|
@@ -43,12 +43,37 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
|
|
|
43
43
|
- **上传图片**:支持 PNG/JPEG/WebP(单张不超过 8 MiB);可在向导中粘贴、拖入或选择,也可直接在数据清洗补全会话的原生输入框粘贴。输入框使用 DSH 原生图片附件,向导同时显示 64px 缩略图,点击可放大。
|
|
44
44
|
|
|
45
45
|
随后可选择名称规范、信用代码校验、去重、模糊候选复核等清洗动作,以及工商字段或已支持的维度组。
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
1.
|
|
49
|
-
2. Agent 只调用一次 `data_cleaning_extract_image_companies
|
|
46
|
+
0.8.1 起,图片流程只需发送一次完整任务说明:
|
|
47
|
+
|
|
48
|
+
1. 选择图片后继续完成匹配规则、清洗动作与补全字段选择;最后一步生成一段包含图片识别要求和后续任务要求的可读中文说明,可在原生对话框中继续修改。
|
|
49
|
+
2. 回填前 Client 释放 Composer 图片附件,因此不支持视觉输入的聊天模型也不会收到图片;Agent 只调用一次 `data_cleaning_extract_image_companies` 高层工具。
|
|
50
|
+
3. Host 通过本地 `qcc-document-mcp` 准确调用一次 `parse_document(file_path)`;仅当任务仍在处理中时,才使用同一 `task_id` 查询 `get_parse_result`。
|
|
51
|
+
4. 识别到的企业名称/统一社会信用代码进入同一 taskId 工作台供人工核对,然后继续主体匹配、字段补全和导出。
|
|
52
|
+
|
|
53
|
+
本地图片解析需在 DSH 的 MCP 连接器中配置官方 `qcc-document-mcp`。示例(Key 仅保存在用户自己的环境中):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"qcc-document-mcp": {
|
|
59
|
+
"command": "npx",
|
|
60
|
+
"args": ["-y", "qcc-document-mcp"],
|
|
61
|
+
"env": {
|
|
62
|
+
"QCC_DOCUMENT_AUTHORIZATION": "Bearer YOUR_API_KEY"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
50
68
|
|
|
51
|
-
|
|
69
|
+
市场中的远端 `qcc-document` 只接受公网 `file_url`,不能读取 Host 本地临时文件;只连接远端服务时,
|
|
70
|
+
图片仍可载入并显示缩略图、继续选择规则和字段,页面会单独提示配置本地服务;真正执行 OCR 时才
|
|
71
|
+
阻断不可用的连接,绝不会向远端接口传本地路径。15 分钟暂存期内补齐连接后无需重新选图,超时或 Host
|
|
72
|
+
重启后需重新选择。选图本身不会执行 OCR,回填并发送完整任务说明后才开始识别。
|
|
73
|
+
图片仅作为 0600 权限的 Host 临时文件,成功、解析失败、取消或
|
|
74
|
+
15 分钟超时后删除,不进入任务 KV、模型上下文或导出制品。`qcc-document-mcp` 会读取该临时文件并
|
|
75
|
+
上传到企查查文档解析网关;用户应只提交有权处理且允许上传的数据。文档解析与后续企业补全均使用
|
|
76
|
+
当前用户自己连接的企查查账号和额度;本插件不读取或保存凭据。
|
|
52
77
|
|
|
53
78
|
模型在对话中调用 `data_clean_rows` / `data_complete_rows` / `data_profile` 时,
|
|
54
79
|
对话内会渲染对应的工具结果卡片(含运行中 / 已完成 / 失败状态);工作台头部用任务 pill
|
package/lib/client.js
CHANGED
|
@@ -1194,7 +1194,9 @@ window.__ModuleLoader__.load({
|
|
|
1194
1194
|
: [];
|
|
1195
1195
|
const entryCount = Number.isFinite(config.entryCount) ? config.entryCount : entries.length;
|
|
1196
1196
|
const source = mode === 'image'
|
|
1197
|
-
?
|
|
1197
|
+
? config.imageState === 'completed'
|
|
1198
|
+
? `图片${config.fileName ? `「${config.fileName}」` : ''},已由企查查智能文档解析识别并人工核验 ${entryCount} 条主体标识`
|
|
1199
|
+
: `图片${config.fileName ? `「${config.fileName}」` : ''},已安全暂存在本机 Host,等待企查查智能文档解析识别`
|
|
1198
1200
|
: mode === 'excel'
|
|
1199
1201
|
? `本地表格${config.fileName ? `「${config.fileName}」` : ''},已解析 ${entryCount} 条主体标识,完整数据已载入数据清洗补全工作台`
|
|
1200
1202
|
: `手工录入,共 ${entryCount} 条主体标识`;
|
|
@@ -1202,6 +1204,10 @@ window.__ModuleLoader__.load({
|
|
|
1202
1204
|
'请执行一项企业名单数据清洗补全任务。',
|
|
1203
1205
|
`输入来源:${source}。`,
|
|
1204
1206
|
];
|
|
1207
|
+
if (mode === 'image' && config.imageCommandId && config.imageState !== 'completed') {
|
|
1208
|
+
lines.push('请先通过企查查智能文档解析提取图片中的企业全称、18 位统一社会信用代码或注册号;模糊字符不得猜测。识别结果回传数据清洗补全工作台供我核验,然后沿用本任务已经选择的匹配规则、清洗要求和补全字段。');
|
|
1209
|
+
lines.push(`安全图片凭证:${config.imageCommandId}。请只调用一次 data_cleaning_extract_image_companies,参数只传 commandId;该高层工具会在 Host 内调用 qcc-document-mcp 的 parse_document / get_parse_result,不要直接调用底层 mcp__qcc-document* 工具。`);
|
|
1210
|
+
}
|
|
1205
1211
|
if (entries.length) {
|
|
1206
1212
|
lines.push(`${mode === 'excel' ? '主体预览' : '待处理主体'}:\n${entries.map((entry, index) => `${index + 1}. ${entry}`).join('\n')}`);
|
|
1207
1213
|
}
|
|
@@ -1559,6 +1565,15 @@ window.__ModuleLoader__.load({
|
|
|
1559
1565
|
return mount;
|
|
1560
1566
|
}
|
|
1561
1567
|
|
|
1568
|
+
const CLEANING_SESSION_ID_PREFIX = 'session-dsh-data-cleaning-agent-';
|
|
1569
|
+
const SESSION_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
1570
|
+
function createCleaningSessionId() {
|
|
1571
|
+
if (typeof globalThis.crypto?.randomUUID !== 'function') {
|
|
1572
|
+
throw new Error('当前浏览器不支持安全会话标识生成,请使用最新版浏览器');
|
|
1573
|
+
}
|
|
1574
|
+
return `${CLEANING_SESSION_ID_PREFIX}${globalThis.crypto.randomUUID()}`;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1562
1577
|
/** 使用 DSH 原生工作区、会话和输入机打开中央对话,不构造第二套聊天界面。 */
|
|
1563
1578
|
async function startCleaningSession(ctx) {
|
|
1564
1579
|
const workspace = ctx.workspaces.list.getSnapshot();
|
|
@@ -1571,21 +1586,17 @@ window.__ModuleLoader__.load({
|
|
|
1571
1586
|
if (targetWorkspaceId === undefined) {
|
|
1572
1587
|
throw new Error('请先选择一个工作空间,再打开数据清洗补全智能体');
|
|
1573
1588
|
}
|
|
1574
|
-
//
|
|
1575
|
-
//
|
|
1576
|
-
//
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
: typeof ctx.sessions.create === 'function'
|
|
1586
|
-
? await ctx.sessions.create({ workspaceId: targetWorkspaceId })
|
|
1587
|
-
: null;
|
|
1588
|
-
if (!sessionId) throw new Error('当前 DSH 版本没有可用的工作区会话创建能力');
|
|
1589
|
+
// 单一会话所有权:与招投标入口一致,显式创建带前缀的独立原生会话,绝不复用空白会话。
|
|
1590
|
+
// connectWorkspace 会把「招投标」尚为空白的工作台会话复用为清洗会话,
|
|
1591
|
+
// 使同一会话同时被招投标与清洗两个插件认领,进而争夺 Hero 标题与右侧工作台面板。
|
|
1592
|
+
if (typeof ctx.sessions.create !== 'function') {
|
|
1593
|
+
throw new Error('当前 DSH 版本没有可用的独立会话创建能力');
|
|
1594
|
+
}
|
|
1595
|
+
const requestedId = createCleaningSessionId();
|
|
1596
|
+
const sessionId = await ctx.sessions.create({ workspaceId: targetWorkspaceId, sessionId: requestedId });
|
|
1597
|
+
if (sessionId !== requestedId) {
|
|
1598
|
+
throw new Error('当前 DSH 版本不支持独立会话创建,请升级 DSH 后再打开数据清洗补全智能体');
|
|
1599
|
+
}
|
|
1589
1600
|
const conversation = typeof ctx.get === 'function' ? ctx.get('conversation') : ctx.conversation;
|
|
1590
1601
|
if (!conversation) throw new Error('DSH 对话服务尚未就绪,请稍后重试');
|
|
1591
1602
|
markCleaningSession(sessionId);
|
|
@@ -1865,8 +1876,15 @@ window.__ModuleLoader__.load({
|
|
|
1865
1876
|
const changedBadges = new Map();
|
|
1866
1877
|
const hiddenForeignDocks = new Map();
|
|
1867
1878
|
const composerStack = marker?.parentElement?.parentElement;
|
|
1879
|
+
// 单一会话所有权由入口的 enabled 参数(useCleaningSession(sessionId) && composerPhase==='blank')
|
|
1880
|
+
// 与 React effect 生命周期共同保证:非清洗会话不会挂载,会话切换即走 cleanup 让位。
|
|
1881
|
+
// 有界同步:每次 fix 的 DOM 变更计入校正预算,达到上限即断开观察器,杜绝与其它插件互相改写的死循环。
|
|
1882
|
+
const MAX_HERO_CORRECTIONS = 8;
|
|
1883
|
+
let corrections = 0;
|
|
1884
|
+
let observer = null;
|
|
1868
1885
|
|
|
1869
1886
|
const fix = () => {
|
|
1887
|
+
let mutated = false;
|
|
1870
1888
|
const spans = [...hero.querySelectorAll('span')];
|
|
1871
1889
|
const title = spans.find((element) => element.dataset?.dcAgentHeroTitle === 'true')
|
|
1872
1890
|
?? spans.find((element) => ['探索未至之境', 'Into the Unknown'].includes(element.textContent?.trim()))
|
|
@@ -1876,12 +1894,12 @@ window.__ModuleLoader__.load({
|
|
|
1876
1894
|
if (title) {
|
|
1877
1895
|
if (!changedTitles.has(title)) changedTitles.set(title, title.textContent ?? '');
|
|
1878
1896
|
title.dataset.dcAgentHeroTitle = 'true';
|
|
1879
|
-
if (title.textContent !== '数据清洗补全智能体') title.textContent = '数据清洗补全智能体';
|
|
1897
|
+
if (title.textContent !== '数据清洗补全智能体') { title.textContent = '数据清洗补全智能体'; mutated = true; }
|
|
1880
1898
|
}
|
|
1881
1899
|
if (badge) {
|
|
1882
1900
|
if (!changedBadges.has(badge)) changedBadges.set(badge, badge.style?.display ?? '');
|
|
1883
1901
|
badge.dataset.dcAgentHeroBadge = 'true';
|
|
1884
|
-
if (badge.style && badge.style.display !== 'none') badge.style.display = 'none';
|
|
1902
|
+
if (badge.style && badge.style.display !== 'none') { badge.style.display = 'none'; mutated = true; }
|
|
1885
1903
|
}
|
|
1886
1904
|
|
|
1887
1905
|
const foreignSurfaces = composerStack?.querySelectorAll?.('[aria-label="尽调类型"], .qccDock') ?? [];
|
|
@@ -1897,13 +1915,20 @@ window.__ModuleLoader__.load({
|
|
|
1897
1915
|
if ((hideTarget?.parentElement === composerStack || hideTarget?.parentElement === marker.parentElement)
|
|
1898
1916
|
&& !hideTarget.contains?.(marker)) {
|
|
1899
1917
|
if (!hiddenForeignDocks.has(hideTarget)) hiddenForeignDocks.set(hideTarget, hideTarget.style?.display ?? '');
|
|
1900
|
-
if (hideTarget.style && hideTarget.style.display !== 'none') hideTarget.style.display = 'none';
|
|
1918
|
+
if (hideTarget.style && hideTarget.style.display !== 'none') { hideTarget.style.display = 'none'; mutated = true; }
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
if (mutated) {
|
|
1922
|
+
corrections += 1;
|
|
1923
|
+
if (corrections >= MAX_HERO_CORRECTIONS && observer) {
|
|
1924
|
+
observer.disconnect();
|
|
1925
|
+
observer = null;
|
|
1901
1926
|
}
|
|
1902
1927
|
}
|
|
1903
1928
|
};
|
|
1904
1929
|
|
|
1905
1930
|
fix();
|
|
1906
|
-
|
|
1931
|
+
observer = typeof MutationObserver === 'function' ? new MutationObserver(fix) : null;
|
|
1907
1932
|
observer?.observe?.(hero, { childList: true, subtree: true });
|
|
1908
1933
|
return () => {
|
|
1909
1934
|
observer?.disconnect?.();
|
|
@@ -1955,6 +1980,16 @@ window.__ModuleLoader__.load({
|
|
|
1955
1980
|
}).filter(Boolean).slice(0, 50);
|
|
1956
1981
|
}
|
|
1957
1982
|
|
|
1983
|
+
function imageFilesFromTransfer(transfer) {
|
|
1984
|
+
const files = Array.from(transfer?.files || []).filter((file) => String(file?.type ?? '').startsWith('image/'));
|
|
1985
|
+
// Safari/Chromium clipboard implementations may expose screenshots only as items.
|
|
1986
|
+
if (files.length) return files;
|
|
1987
|
+
return Array.from(transfer?.items || [])
|
|
1988
|
+
.filter((item) => item.kind === 'file' && String(item.type ?? '').startsWith('image/'))
|
|
1989
|
+
.map((item) => item.getAsFile?.())
|
|
1990
|
+
.filter(Boolean);
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1958
1993
|
async function imageFileBase64(file) {
|
|
1959
1994
|
if (!file || typeof file.arrayBuffer !== 'function') throw new Error('图片文件不可读。');
|
|
1960
1995
|
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
@@ -2025,8 +2060,8 @@ window.__ModuleLoader__.load({
|
|
|
2025
2060
|
type: 'image', fileName: imageFileName, sizeBytes: imageSizeBytes,
|
|
2026
2061
|
});
|
|
2027
2062
|
setError(null);
|
|
2028
|
-
|
|
2029
|
-
|
|
2063
|
+
setOpen(false);
|
|
2064
|
+
requestWorkbenchOpen('upload', sessionId);
|
|
2030
2065
|
return;
|
|
2031
2066
|
}
|
|
2032
2067
|
if (command.state === 'failed') {
|
|
@@ -2141,8 +2176,9 @@ window.__ModuleLoader__.load({
|
|
|
2141
2176
|
if (mode === 'text' && !values.length) return '请先录入企业名称或统一社会信用代码,每行一条。';
|
|
2142
2177
|
if (mode === 'excel' && !values.length) return '请先上传并解析 Excel / CSV / JSON 文件。';
|
|
2143
2178
|
if (mode === 'image' && !imageCommand) return '请先选择、粘贴或拖入一张包含企业名单的图片。';
|
|
2144
|
-
if (mode === 'image' && imageCommand?.state
|
|
2145
|
-
if (mode === 'image' &&
|
|
2179
|
+
if (mode === 'image' && imageCommand?.state === 'failed') return imageCommand.error?.message || '图片识别失败,请重新选择图片。';
|
|
2180
|
+
if (mode === 'image' && imageCommand?.state === 'running') return '图片正在通过企查查智能文档解析识别,请稍候。';
|
|
2181
|
+
if (mode === 'image' && imageCommand?.state === 'completed' && !values.length) return '图片中未提取到可核验的企业名称或统一社会信用代码。';
|
|
2146
2182
|
return null;
|
|
2147
2183
|
};
|
|
2148
2184
|
const promptConfig = () => ({
|
|
@@ -2154,6 +2190,8 @@ window.__ModuleLoader__.load({
|
|
|
2154
2190
|
enrichmentKeys,
|
|
2155
2191
|
anchorKeys,
|
|
2156
2192
|
matchRules,
|
|
2193
|
+
imageCommandId: imageCommand?.commandId ?? '',
|
|
2194
|
+
imageState: imageCommand?.state ?? null,
|
|
2157
2195
|
});
|
|
2158
2196
|
const generatePrompt = () => {
|
|
2159
2197
|
const sourceError = validateSource();
|
|
@@ -2165,6 +2203,11 @@ window.__ModuleLoader__.load({
|
|
|
2165
2203
|
return;
|
|
2166
2204
|
}
|
|
2167
2205
|
const config = promptConfig();
|
|
2206
|
+
if (mode === 'image' && imageCommand?.state === 'prepared' && imageAttachment && typeof removeImage === 'function') {
|
|
2207
|
+
// qcc-document-mcp 只读取 Host 暂存文件;发送前释放原生附件,文本模型不会收到图片。
|
|
2208
|
+
removeImage(sessionId, imageAttachment);
|
|
2209
|
+
setImageAttachment(null);
|
|
2210
|
+
}
|
|
2168
2211
|
inputActions.setDraft(buildTaskPrompt(config));
|
|
2169
2212
|
if (mode === 'text') {
|
|
2170
2213
|
requestWorkbenchDataset(entriesToDataset(config.entries), sessionId, { type: 'text', fileName: '' });
|
|
@@ -2183,22 +2226,6 @@ window.__ModuleLoader__.load({
|
|
|
2183
2226
|
|
|
2184
2227
|
const nextWizard = () => {
|
|
2185
2228
|
if (wizardStep === 1) {
|
|
2186
|
-
if (mode === 'image' && imageCommand?.state === 'prepared') {
|
|
2187
|
-
if (!inputActions || typeof inputActions.setDraft !== 'function') {
|
|
2188
|
-
setError('当前会话输入机尚未就绪,请稍后重试。');
|
|
2189
|
-
return;
|
|
2190
|
-
}
|
|
2191
|
-
// Host 已持久暂存图片;发送识别命令前释放 Composer 附件,避免不支持
|
|
2192
|
-
// 视觉输入的文本模型拒绝本轮。识别工具只按 commandId 读取 Host 暂存副本。
|
|
2193
|
-
if (imageAttachment && typeof removeImage === 'function') {
|
|
2194
|
-
removeImage(sessionId, imageAttachment);
|
|
2195
|
-
setImageAttachment(null);
|
|
2196
|
-
}
|
|
2197
|
-
inputActions.setDraft(imageCommand.prompt);
|
|
2198
|
-
setError(null);
|
|
2199
|
-
setOpen(false);
|
|
2200
|
-
return;
|
|
2201
|
-
}
|
|
2202
2229
|
const sourceError = validateSource();
|
|
2203
2230
|
if (sourceError) { setError(sourceError); return; }
|
|
2204
2231
|
}
|
|
@@ -2226,8 +2253,8 @@ window.__ModuleLoader__.load({
|
|
|
2226
2253
|
react.useEffect(() => {
|
|
2227
2254
|
if (!enabled) return undefined;
|
|
2228
2255
|
const handleWindowPaste = (event) => {
|
|
2229
|
-
const files = event.clipboardData
|
|
2230
|
-
const hasImage =
|
|
2256
|
+
const files = imageFilesFromTransfer(event.clipboardData);
|
|
2257
|
+
const hasImage = files.length > 0;
|
|
2231
2258
|
if (!hasImage) return;
|
|
2232
2259
|
const target = event.target;
|
|
2233
2260
|
const inComposer = !open && Boolean(target?.closest?.('textarea, [contenteditable="true"], [role="textbox"]'));
|
|
@@ -2329,7 +2356,7 @@ window.__ModuleLoader__.load({
|
|
|
2329
2356
|
role: 'group',
|
|
2330
2357
|
'aria-label': '粘贴、拖入或选择企业名单图片',
|
|
2331
2358
|
onPaste: (event) => {
|
|
2332
|
-
const files = event.clipboardData
|
|
2359
|
+
const files = imageFilesFromTransfer(event.clipboardData);
|
|
2333
2360
|
if (files?.length) {
|
|
2334
2361
|
event.preventDefault();
|
|
2335
2362
|
acceptImageFiles(files);
|
|
@@ -2340,7 +2367,7 @@ window.__ModuleLoader__.load({
|
|
|
2340
2367
|
onDrop: (event) => {
|
|
2341
2368
|
event.preventDefault();
|
|
2342
2369
|
setImageDropActive(false);
|
|
2343
|
-
acceptImageFiles(event.dataTransfer
|
|
2370
|
+
acceptImageFiles(imageFilesFromTransfer(event.dataTransfer));
|
|
2344
2371
|
},
|
|
2345
2372
|
},
|
|
2346
2373
|
h('p', null, '可直接在此粘贴图片,或拖入 / 选择 PNG、JPEG、WebP。图片会在对话框显示缩略图,点击可放大。'),
|
|
@@ -2358,7 +2385,7 @@ window.__ModuleLoader__.load({
|
|
|
2358
2385
|
? '识别中…'
|
|
2359
2386
|
: imageCommand?.state === 'failed'
|
|
2360
2387
|
? '识别失败,可移除后重试'
|
|
2361
|
-
: '
|
|
2388
|
+
: '已暂存,可继续选择匹配规则和补全字段'),
|
|
2362
2389
|
),
|
|
2363
2390
|
h('button', { type: 'button', className: 'dcAgentPromptImageRemove', disabled: busy || imageCommand?.state === 'running', onClick: removeCurrentImage }, '移除'),
|
|
2364
2391
|
) : h('label', { className: 'dcAgentPromptFile' },
|
|
@@ -2420,14 +2447,15 @@ window.__ModuleLoader__.load({
|
|
|
2420
2447
|
h('pre', { className: 'dcAgentPromptPreview' }, buildTaskPrompt(promptConfig())),
|
|
2421
2448
|
) : null,
|
|
2422
2449
|
h('p', { className: 'dcAgentPromptNote' }, '向导只生成任务描述并保存任务设置。只有你在任务中明确确认后,才会使用当前客户自己的企查查 MCP 连接、账号和额度。'),
|
|
2450
|
+
mode === 'image' && imageCommand?.state === 'prepared' && imageCommand?.providerIssue
|
|
2451
|
+
? h('p', { className: 'dcAgentPromptNote', role: 'status' }, `图片已载入,可继续选择规则和字段;暂存有效期 15 分钟。${imageCommand.providerIssue.message}`)
|
|
2452
|
+
: null,
|
|
2423
2453
|
error ? h('p', { className: 'dcAgentPromptError', role: 'alert' }, error) : null,
|
|
2424
2454
|
h('div', { className: 'dcAgentPromptActions' },
|
|
2425
2455
|
h('button', { type: 'button', className: 'dcAgentPromptAction', disabled: wizardStep === 1, onClick: () => setWizardStep(Math.max(1, wizardStep - 1)) }, '上一步'),
|
|
2426
2456
|
h('button', { type: 'button', className: 'dcAgentPromptAction is-primary', disabled: busy || imageCommand?.state === 'running', onClick: nextWizard }, busy
|
|
2427
2457
|
? '处理中…'
|
|
2428
|
-
: wizardStep === 1 && mode === 'image' && imageCommand?.state === '
|
|
2429
|
-
? '回填图片识别指令'
|
|
2430
|
-
: wizardStep === 1 && mode === 'image' && imageCommand?.state === 'running'
|
|
2458
|
+
: wizardStep === 1 && mode === 'image' && imageCommand?.state === 'running'
|
|
2431
2459
|
? '图片识别中…'
|
|
2432
2460
|
: wizardStep === 4 ? '回填到对话框' : '下一步'),
|
|
2433
2461
|
),
|
|
@@ -3776,6 +3804,7 @@ window.__ModuleLoader__.load({
|
|
|
3776
3804
|
clearCleaningDraft,
|
|
3777
3805
|
entriesToDataset,
|
|
3778
3806
|
extractPromptEntries,
|
|
3807
|
+
imageFilesFromTransfer,
|
|
3779
3808
|
guessMappings,
|
|
3780
3809
|
deactivateCleaningSession,
|
|
3781
3810
|
installSessionOwnershipBridge,
|
package/lib/image-intake.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 图片企业名单接入。
|
|
3
3
|
*
|
|
4
|
-
* Browser 只负责把用户明确选择/粘贴的图片暂存到 Host
|
|
5
|
-
* Agent-owned
|
|
6
|
-
*
|
|
4
|
+
* Browser 只负责把用户明确选择/粘贴的图片暂存到 Host;真实文字识别由
|
|
5
|
+
* Agent-owned 高层工具在当前会话执行上下文中调用企查查智能文档解析 MCP。
|
|
6
|
+
* 本地图片必须走官方 qcc-document-mcp(file_path),远端 qcc-document 只接受
|
|
7
|
+
* 公网 file_url,不能直接读取 Host 临时文件。图片使用 0600 临时文件,识别
|
|
7
8
|
* 完成、失败、取消或 TTL 到期后立即删除;不会进入 storageDomain 或导出制品。
|
|
8
9
|
*/
|
|
9
10
|
import { randomUUID } from 'node:crypto';
|
|
@@ -12,7 +13,26 @@ import { tmpdir } from 'node:os';
|
|
|
12
13
|
import { join } from 'node:path';
|
|
13
14
|
|
|
14
15
|
export const TOOL_IMAGE_EXTRACT = 'data_cleaning_extract_image_companies';
|
|
15
|
-
export const
|
|
16
|
+
export const IMAGE_PROVIDER_QCC_DOCUMENT_LOCAL = 'qcc-document-mcp';
|
|
17
|
+
export const QCC_DOCUMENT_REMOTE_PARSE = 'mcp__qcc-document__parse_document';
|
|
18
|
+
export const QCC_DOCUMENT_LOCAL_TOOL_PAIRS = Object.freeze([
|
|
19
|
+
Object.freeze({
|
|
20
|
+
parse: 'mcp__document__parse_document',
|
|
21
|
+
result: 'mcp__document__get_parse_result',
|
|
22
|
+
}),
|
|
23
|
+
Object.freeze({
|
|
24
|
+
parse: 'mcp__qcc-document-mcp__parse_document',
|
|
25
|
+
result: 'mcp__qcc-document-mcp__get_parse_result',
|
|
26
|
+
}),
|
|
27
|
+
Object.freeze({
|
|
28
|
+
parse: 'mcp__qcc-document-local__parse_document',
|
|
29
|
+
result: 'mcp__qcc-document-local__get_parse_result',
|
|
30
|
+
}),
|
|
31
|
+
Object.freeze({
|
|
32
|
+
parse: 'mcp__document-mcp__parse_document',
|
|
33
|
+
result: 'mcp__document-mcp__get_parse_result',
|
|
34
|
+
}),
|
|
35
|
+
]);
|
|
16
36
|
|
|
17
37
|
export const IMAGE_LIMITS = Object.freeze({
|
|
18
38
|
maxBytes: 8 * 1024 * 1024,
|
|
@@ -76,6 +96,7 @@ function unwrapProviderValue(result) {
|
|
|
76
96
|
throw new ImageIntakeError('DC_IMAGE_PROVIDER_FAILED', String(message), 502);
|
|
77
97
|
}
|
|
78
98
|
let value = result?.value ?? result;
|
|
99
|
+
if (value?.structuredContent !== undefined) value = value.structuredContent;
|
|
79
100
|
if (value && Array.isArray(value.content)) {
|
|
80
101
|
const text = value.content.filter((item) => item?.type === 'text').map((item) => item.text).join('\n');
|
|
81
102
|
if (text) {
|
|
@@ -87,6 +108,11 @@ function unwrapProviderValue(result) {
|
|
|
87
108
|
|
|
88
109
|
export function providerText(result) {
|
|
89
110
|
const value = unwrapProviderValue(result);
|
|
111
|
+
const markdown = [
|
|
112
|
+
...(Array.isArray(value?.details) ? value.details : []),
|
|
113
|
+
value,
|
|
114
|
+
].map((detail) => String(detail?.result_md ?? detail?.resultMd ?? '').trim()).filter(Boolean).join('\n');
|
|
115
|
+
if (markdown) return markdown;
|
|
90
116
|
const lines = Array.isArray(value?.ocr?.lines)
|
|
91
117
|
? value.ocr.lines.map((line) => String(line?.text ?? '').trim()).filter(Boolean)
|
|
92
118
|
: [];
|
|
@@ -97,6 +123,77 @@ export function providerText(result) {
|
|
|
97
123
|
return text;
|
|
98
124
|
}
|
|
99
125
|
|
|
126
|
+
function providerEnvelope(result) {
|
|
127
|
+
const value = unwrapProviderValue(result);
|
|
128
|
+
if (typeof value === 'string') return { status: 'success', details: [{ result_md: value }] };
|
|
129
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
130
|
+
throw new ImageIntakeError('DC_IMAGE_DOCUMENT_CONTRACT', '企查查智能文档解析返回了无法识别的结果。', 502);
|
|
131
|
+
}
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function documentStatus(value) {
|
|
136
|
+
const raw = String(value?.status ?? value?.state ?? '').trim().toLowerCase();
|
|
137
|
+
if (['success', 'succeeded', 'completed', 'complete', 'done'].includes(raw)) return 'success';
|
|
138
|
+
if (['failed', 'failure', 'error', 'cancelled', 'canceled'].includes(raw)) return 'failed';
|
|
139
|
+
if (['processing', 'pending', 'queued', 'running', 'accepted', 'submitted'].includes(raw)) return 'processing';
|
|
140
|
+
if (providerTextOrEmpty(value)) return 'success';
|
|
141
|
+
return raw || 'processing';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function providerTextOrEmpty(value) {
|
|
145
|
+
try { return providerText(value); } catch { return ''; }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function providerTaskId(value) {
|
|
149
|
+
return String(value?.task_id ?? value?.taskId ?? '').trim();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function documentFailure(value) {
|
|
153
|
+
const code = String(value?.error?.code ?? value?.code ?? '').trim();
|
|
154
|
+
const auth = /(?:^|\D)(?:401|403)(?:\D|$)|AUTH|TOKEN|CREDENTIAL/i.test(`${code} ${value?.message ?? ''}`);
|
|
155
|
+
if (auth) {
|
|
156
|
+
return new ImageIntakeError(
|
|
157
|
+
'DC_IMAGE_QCC_AUTH_REQUIRED',
|
|
158
|
+
'企查查本地文档解析连接未授权或授权已失效,请配置 qcc-document-mcp 后重试。',
|
|
159
|
+
401,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return new ImageIntakeError(
|
|
163
|
+
'DC_IMAGE_DOCUMENT_FAILED',
|
|
164
|
+
code ? `企查查智能文档解析失败(错误码 ${code}),请检查图片后重试。` : '企查查智能文档解析失败,请检查图片后重试。',
|
|
165
|
+
502,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function delay(ms, signal) {
|
|
170
|
+
if (signal?.aborted) return Promise.reject(signal.reason ?? new Error('aborted'));
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const finish = () => {
|
|
173
|
+
signal?.removeEventListener('abort', abort);
|
|
174
|
+
resolve();
|
|
175
|
+
};
|
|
176
|
+
const timer = setTimeout(finish, ms);
|
|
177
|
+
const abort = () => {
|
|
178
|
+
clearTimeout(timer);
|
|
179
|
+
signal?.removeEventListener('abort', abort);
|
|
180
|
+
reject(signal.reason ?? new Error('aborted'));
|
|
181
|
+
};
|
|
182
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function definitionParameters(definition) {
|
|
187
|
+
return definition?.parameters ?? definition?.schema?.parameters ?? definition?.inputSchema ?? null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function supportsLocalPath(definition, name) {
|
|
191
|
+
const parameters = definitionParameters(definition);
|
|
192
|
+
if (parameters?.properties?.file_path) return true;
|
|
193
|
+
// 官方 npm 服务的稳定 serverName;旧 DSH 投射可能不保留完整 input schema。
|
|
194
|
+
return /(?:qcc-)?document-(?:mcp|local)__parse_document$/.test(String(name ?? ''));
|
|
195
|
+
}
|
|
196
|
+
|
|
100
197
|
const CREDIT_RE = /\b[0-9A-HJ-NPQRTUWXY]{18}\b/gi;
|
|
101
198
|
const COMPANY_END = '(?:有限责任公司|股份有限公司|集团有限公司|有限公司|集团公司|公司|普通合伙|有限合伙|合伙企业|个人独资企业|农民专业合作社|合作社|事务所|研究院|研究所|中心|商行|工厂|厂)';
|
|
102
199
|
const COMPANY_RE = new RegExp(`[\\p{Script=Han}A-Za-z0-9()()·&++—\\-]{2,72}${COMPANY_END}`, 'gu');
|
|
@@ -158,6 +255,7 @@ function publicRecord(record, now = Date.now()) {
|
|
|
158
255
|
mimeType: record.mimeType,
|
|
159
256
|
sizeBytes: record.sizeBytes,
|
|
160
257
|
provider: record.provider,
|
|
258
|
+
parseTool: record.parseTool,
|
|
161
259
|
createdAt: record.createdAt,
|
|
162
260
|
updatedAt: record.updatedAt,
|
|
163
261
|
expiresInMs: Math.max(0, record.expiresAt - now),
|
|
@@ -172,35 +270,83 @@ function safeFailure(error) {
|
|
|
172
270
|
}
|
|
173
271
|
return {
|
|
174
272
|
code: 'DC_IMAGE_PROVIDER_FAILED',
|
|
175
|
-
message: '
|
|
273
|
+
message: '企查查智能文档解析当前不可用或配置无效。请连接本地 qcc-document-mcp,或改用文本/Excel 名单。',
|
|
176
274
|
};
|
|
177
275
|
}
|
|
178
276
|
|
|
179
277
|
export class ImageIntakeStore {
|
|
180
|
-
constructor({
|
|
278
|
+
constructor({
|
|
279
|
+
tools,
|
|
280
|
+
clock = () => Date.now(),
|
|
281
|
+
ttlMs = IMAGE_LIMITS.ttlMs,
|
|
282
|
+
pollMs = 500,
|
|
283
|
+
maxPolls = 60,
|
|
284
|
+
} = {}) {
|
|
181
285
|
if (!tools || typeof tools.get !== 'function' || typeof tools.execute !== 'function') {
|
|
182
286
|
throw new TypeError('ImageIntakeStore requires ctx.tools get/execute');
|
|
183
287
|
}
|
|
184
288
|
this.tools = tools;
|
|
185
289
|
this.clock = clock;
|
|
186
290
|
this.ttlMs = ttlMs;
|
|
291
|
+
this.pollMs = pollMs;
|
|
292
|
+
this.maxPolls = maxPolls;
|
|
187
293
|
this.records = new Map();
|
|
188
294
|
}
|
|
189
295
|
|
|
190
|
-
|
|
191
|
-
try { return this.tools.get(
|
|
296
|
+
toolDefinition(name) {
|
|
297
|
+
try { return this.tools.get(name); } catch { return undefined; }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
providerDefinition(preferredParseTool) {
|
|
301
|
+
const pairs = preferredParseTool
|
|
302
|
+
? [...QCC_DOCUMENT_LOCAL_TOOL_PAIRS].sort((left) => left.parse === preferredParseTool ? -1 : 1)
|
|
303
|
+
: QCC_DOCUMENT_LOCAL_TOOL_PAIRS;
|
|
304
|
+
for (const pair of pairs) {
|
|
305
|
+
const parse = this.toolDefinition(pair.parse);
|
|
306
|
+
const result = this.toolDefinition(pair.result);
|
|
307
|
+
if (parse && result && supportsLocalPath(parse, parse.name ?? pair.parse)) {
|
|
308
|
+
return {
|
|
309
|
+
provider: IMAGE_PROVIDER_QCC_DOCUMENT_LOCAL,
|
|
310
|
+
parse,
|
|
311
|
+
result,
|
|
312
|
+
parseName: parse.name ?? pair.parse,
|
|
313
|
+
resultName: result.name ?? pair.result,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
remoteDefinition() {
|
|
321
|
+
const candidates = [
|
|
322
|
+
QCC_DOCUMENT_REMOTE_PARSE,
|
|
323
|
+
...QCC_DOCUMENT_LOCAL_TOOL_PAIRS.map((pair) => pair.parse),
|
|
324
|
+
];
|
|
325
|
+
for (const name of candidates) {
|
|
326
|
+
const definition = this.toolDefinition(name);
|
|
327
|
+
const parameters = definitionParameters(definition);
|
|
328
|
+
if (definition && parameters?.properties?.file_url && !parameters?.properties?.file_path) {
|
|
329
|
+
return definition;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return this.toolDefinition(QCC_DOCUMENT_REMOTE_PARSE);
|
|
192
333
|
}
|
|
193
334
|
|
|
194
335
|
capabilities() {
|
|
195
336
|
const definition = this.providerDefinition();
|
|
196
337
|
return {
|
|
197
338
|
ready: Boolean(definition),
|
|
198
|
-
provider: definition
|
|
339
|
+
provider: definition?.provider ?? null,
|
|
340
|
+
parseTool: definition?.parseName ?? null,
|
|
341
|
+
resultTool: definition?.resultName ?? null,
|
|
342
|
+
remoteUrlOnlyConnected: Boolean(this.remoteDefinition()),
|
|
343
|
+
localFileConnectorRequired: !definition,
|
|
199
344
|
nativeAttachmentUi: true,
|
|
200
345
|
pasteAndDrop: true,
|
|
201
346
|
formats: ['image/png', 'image/jpeg', 'image/webp'],
|
|
202
347
|
limits: IMAGE_LIMITS,
|
|
203
348
|
persistence: 'ephemeral-host-file',
|
|
349
|
+
billing: 'current-user-qcc-document-account',
|
|
204
350
|
};
|
|
205
351
|
}
|
|
206
352
|
|
|
@@ -249,13 +395,6 @@ export class ImageIntakeStore {
|
|
|
249
395
|
async prepare(input = {}) {
|
|
250
396
|
await this.cleanup();
|
|
251
397
|
const provider = this.providerDefinition();
|
|
252
|
-
if (!provider) {
|
|
253
|
-
throw new ImageIntakeError(
|
|
254
|
-
'DC_IMAGE_PROVIDER_UNAVAILABLE',
|
|
255
|
-
'当前 DSH 没有可用的图片文字识别 Provider。请安装并配置 Modlens,或改用文本/Excel 名单。',
|
|
256
|
-
503,
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
398
|
const decoded = decodeImage(input.content);
|
|
260
399
|
const commandId = `dci-${randomUUID()}`;
|
|
261
400
|
const at = new Date(this.clock()).toISOString();
|
|
@@ -268,7 +407,9 @@ export class ImageIntakeStore {
|
|
|
268
407
|
fileName: safeName(input.fileName),
|
|
269
408
|
mimeType: decoded.mimeType,
|
|
270
409
|
sizeBytes: decoded.bytes.length,
|
|
271
|
-
provider:
|
|
410
|
+
provider: IMAGE_PROVIDER_QCC_DOCUMENT_LOCAL,
|
|
411
|
+
parseTool: provider?.parseName ?? null,
|
|
412
|
+
resultTool: provider?.resultName ?? null,
|
|
272
413
|
path,
|
|
273
414
|
createdAt: at,
|
|
274
415
|
updatedAt: at,
|
|
@@ -280,7 +421,7 @@ export class ImageIntakeStore {
|
|
|
280
421
|
};
|
|
281
422
|
this.records.set(commandId, record);
|
|
282
423
|
this.scheduleExpiry(record);
|
|
283
|
-
return
|
|
424
|
+
return this.status(commandId);
|
|
284
425
|
}
|
|
285
426
|
|
|
286
427
|
require(commandId) {
|
|
@@ -293,7 +434,23 @@ export class ImageIntakeStore {
|
|
|
293
434
|
}
|
|
294
435
|
|
|
295
436
|
status(commandId) {
|
|
296
|
-
|
|
437
|
+
const record = this.require(commandId);
|
|
438
|
+
const ready = Boolean(this.providerDefinition(record.parseTool));
|
|
439
|
+
return {
|
|
440
|
+
...publicRecord(record, this.clock()),
|
|
441
|
+
providerReady: ready,
|
|
442
|
+
providerIssue: ready ? null : this.providerIssue(),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
providerIssue() {
|
|
447
|
+
const remoteOnly = Boolean(this.remoteDefinition());
|
|
448
|
+
return {
|
|
449
|
+
code: remoteOnly ? 'DC_IMAGE_LOCAL_DOCUMENT_PROVIDER_REQUIRED' : 'DC_IMAGE_PROVIDER_UNAVAILABLE',
|
|
450
|
+
message: remoteOnly
|
|
451
|
+
? '当前只连接了远端 qcc-document,它不能读取本地图片。请在 MCP 连接器中配置本地 qcc-document-mcp 后再发送识别任务。'
|
|
452
|
+
: '当前 DSH 没有可用的企查查本地文档解析连接。请配置 qcc-document-mcp 后再发送识别任务,或改用文本/Excel 名单。',
|
|
453
|
+
};
|
|
297
454
|
}
|
|
298
455
|
|
|
299
456
|
async run(commandId, exec) {
|
|
@@ -303,25 +460,44 @@ export class ImageIntakeStore {
|
|
|
303
460
|
}
|
|
304
461
|
if (record.promise) return record.promise;
|
|
305
462
|
if (record.state === 'completed') return { commandId, ...record.result };
|
|
306
|
-
const provider = this.providerDefinition();
|
|
463
|
+
const provider = this.providerDefinition(record.parseTool);
|
|
307
464
|
if (!provider) {
|
|
308
|
-
|
|
465
|
+
const issue = this.providerIssue();
|
|
466
|
+
throw new ImageIntakeError(issue.code, issue.message, 503);
|
|
309
467
|
}
|
|
468
|
+
record.parseTool = provider.parseName;
|
|
469
|
+
record.resultTool = provider.resultName;
|
|
310
470
|
record.state = 'running';
|
|
311
471
|
record.updatedAt = new Date(this.clock()).toISOString();
|
|
312
|
-
|
|
313
|
-
name
|
|
472
|
+
const execute = (name, args) => this.tools.execute({
|
|
473
|
+
name,
|
|
314
474
|
callId: `dc-image-${randomUUID()}`,
|
|
315
475
|
rootCallId: exec.rootCallId,
|
|
316
476
|
parent: exec.token,
|
|
317
477
|
agent: exec.agent,
|
|
318
478
|
signal: exec.signal,
|
|
319
|
-
arguments:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
479
|
+
arguments: args,
|
|
480
|
+
});
|
|
481
|
+
record.promise = execute(provider.parseName, {
|
|
482
|
+
file_path: record.path,
|
|
483
|
+
wait: true,
|
|
484
|
+
}).then(async (parseResult) => {
|
|
485
|
+
let envelope = providerEnvelope(parseResult);
|
|
486
|
+
let status = documentStatus(envelope);
|
|
487
|
+
const taskId = providerTaskId(envelope);
|
|
488
|
+
for (let poll = 0; status === 'processing' && poll < this.maxPolls; poll += 1) {
|
|
489
|
+
if (!taskId) {
|
|
490
|
+
throw new ImageIntakeError('DC_IMAGE_DOCUMENT_CONTRACT', '企查查文档解析未返回 task_id。', 502);
|
|
491
|
+
}
|
|
492
|
+
await delay(this.pollMs, exec.signal);
|
|
493
|
+
envelope = providerEnvelope(await execute(provider.resultName, { task_id: taskId }));
|
|
494
|
+
status = documentStatus(envelope);
|
|
495
|
+
}
|
|
496
|
+
if (status === 'processing') {
|
|
497
|
+
throw new ImageIntakeError('DC_IMAGE_DOCUMENT_TIMEOUT', '企查查文档解析仍在处理中,请稍后重新识别。', 504);
|
|
498
|
+
}
|
|
499
|
+
if (status === 'failed') throw documentFailure(envelope);
|
|
500
|
+
const text = providerText(envelope);
|
|
325
501
|
const entries = extractCompanyEntries(text);
|
|
326
502
|
if (!entries.length) {
|
|
327
503
|
throw new ImageIntakeError('DC_IMAGE_NO_COMPANY', '图片文字已识别,但未提取到企业全称或 18 位统一社会信用代码。', 422);
|
|
@@ -368,22 +544,22 @@ export class ImageIntakeStore {
|
|
|
368
544
|
|
|
369
545
|
export function serializeImageExtractionPrompt(command) {
|
|
370
546
|
return [
|
|
371
|
-
'
|
|
547
|
+
'请使用企查查智能文档解析识别我刚刚在向导中安全暂存的企业名单图片,并把结果交回数据清洗补全工作台供我逐条核验。',
|
|
372
548
|
'',
|
|
373
549
|
`图片文件:${command.fileName}。`,
|
|
374
550
|
'识别目标:逐行提取企业全称、18 位统一社会信用代码或注册号;不得猜测模糊字符。',
|
|
375
|
-
'
|
|
551
|
+
'本步骤会使用当前用户自己连接的企查查 qcc-document-mcp;文档解析额度或费用由该账号自行承担。',
|
|
376
552
|
`安全图片凭证:${command.commandId}`,
|
|
377
553
|
'',
|
|
378
554
|
`发送本说明后,请仅调用一次图片名单识别工具(${TOOL_IMAGE_EXTRACT}),参数只传递上述安全图片凭证。`,
|
|
379
|
-
'
|
|
555
|
+
'高层工具会在 Host 内准确调用一次 parse_document,并只在异步处理中查询 get_parse_result;不要绕过高层工具直接调用任何 mcp__qcc-* 工具。',
|
|
380
556
|
].join('\n');
|
|
381
557
|
}
|
|
382
558
|
|
|
383
559
|
export function registerImageIntakeTool(tools, store) {
|
|
384
560
|
return tools.register({
|
|
385
561
|
name: TOOL_IMAGE_EXTRACT,
|
|
386
|
-
description: '
|
|
562
|
+
description: 'Parse one already-staged local company-list image through QCC qcc-document-mcp. Call only when a visible data-cleaning prompt supplies a dci-* commandId. The Host owns the temporary image, calls parse_document once, and polls get_parse_result only while the returned task is processing.',
|
|
387
563
|
parameters: {
|
|
388
564
|
type: 'object',
|
|
389
565
|
additionalProperties: false,
|
|
@@ -404,7 +580,7 @@ export function registerImageIntakeTool(tools, store) {
|
|
|
404
580
|
},
|
|
405
581
|
render: (_args, value) => [{
|
|
406
582
|
type: 'text',
|
|
407
|
-
text:
|
|
583
|
+
text: `企查查智能文档解析已识别图片企业名单:${value.entryCount} 条,已同步回数据清洗补全工作台等待核验。`,
|
|
408
584
|
}],
|
|
409
585
|
},
|
|
410
586
|
async execute(args, exec) {
|
package/lib/web.js
CHANGED
|
@@ -384,7 +384,8 @@ export function mountWebRoutes(wctx, { logger, report, TOOL_NAME, SKILL_NAME })
|
|
|
384
384
|
tool: TOOL_IMAGE_EXTRACT,
|
|
385
385
|
toolRegistered: report.imageIntakeToolRegistered === true,
|
|
386
386
|
capabilities: imageIntake.capabilities(),
|
|
387
|
-
qccCalls:
|
|
387
|
+
qccCalls: true,
|
|
388
|
+
billing: 'current-user-qcc-document-account',
|
|
388
389
|
});
|
|
389
390
|
});
|
|
390
391
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-data-cleaning-agent",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Clean, complete, and profile enterprise name lists in DeepSeek Harness — a data cleaning & completion agent plugin with local CSV/XLSX/JSON engine and optional Qichacha (QCC) MCP enrichment. Maintained by Qichacha/QCC.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"docs/RELEASE-0.6.3.md",
|
|
38
38
|
"docs/RELEASE-0.7.0.md",
|
|
39
39
|
"docs/RELEASE-0.8.0.md",
|
|
40
|
+
"docs/RELEASE-0.8.1.md",
|
|
40
41
|
"docs/UI-WORKFLOW-V2.md",
|
|
41
42
|
"docs/UI-WORKFLOW-V2-MIGRATION.md",
|
|
42
43
|
"docs/UI-WORKFLOW-V2-ACCEPTANCE.md",
|