dsh-data-cleaning-agent 0.8.20 → 0.8.22
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 +15 -0
- package/README.en.md +1 -1
- package/README.md +3 -3
- package/docs/RELEASE-0.8.22.md +11 -0
- package/lib/client.js +55 -7
- package/lib/qcc-command.js +3 -2
- package/lib/qcc.js +5 -1
- package/lib/web.js +10 -1
- package/package.json +2 -2
- package/docs/RELEASE-0.8.20.md +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.8.22] - 2026-09-09
|
|
8
|
+
|
|
9
|
+
- 表格预览保留可读列宽、横向滚动和固定表头,避免多列挤压成竖排。
|
|
10
|
+
- 结果文件操作分行并留白,长文件名换行;失败计数单独展示,避免零补全被标绿。
|
|
11
|
+
- 失败行导出保留规范化错误码和工具名,不输出原始上游响应或凭据。
|
|
12
|
+
- 修复自动展开后关闭/切换会话时,排队中的尺寸回调重新写入布局留白的竞态。
|
|
13
|
+
- 延续 0.8.21 的实际 Host 文件地址和执行自动展开功能。
|
|
14
|
+
|
|
15
|
+
## [0.8.21] - 2026-09-09
|
|
16
|
+
|
|
17
|
+
- 从实际 Host 请求获取文件地址的协议和主机;修复本地 HTTP 链接被补成 HTTPS 的问题。
|
|
18
|
+
- 工具卡片支持同源完整文件地址,继续拒绝非同源链接。
|
|
19
|
+
- 已确认任务开始执行时自动展开工作台一次并跟随处理步骤;生成说明不触发,关闭后不反复弹出。
|
|
20
|
+
- 246 项测试及 10 组浏览器回归通过,无付费查询。
|
|
21
|
+
|
|
7
22
|
## [0.8.20] - 2026-09-09
|
|
8
23
|
|
|
9
24
|
- 普通无记录/未披露字段保留为无数据说明,尊重共享契约 reviewRequired=false,不再误计为待核验。
|
package/README.en.md
CHANGED
|
@@ -8,7 +8,7 @@ Related agents: [数据清洗补全](https://github.com/duhu2000/dsh-data-cleani
|
|
|
8
8
|
|
|
9
9
|
> 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.
|
|
10
10
|
>
|
|
11
|
-
> Current source version / 当前源码版本: **0.8.
|
|
11
|
+
> Current source version / 当前源码版本: **0.8.22** (stable release)
|
|
12
12
|
|
|
13
13
|
This release unifies spreadsheet previews, column mapping, and enrichment scope across the prompt wizard and workbench. Mapping statuses use color and text; mapped fields automatically enter the scope, with optional extra fields collapsed. Multiple confirmed output columns may share a non-identity field while preserving non-empty values. It fixes saved drafts resetting the scope to five defaults and detects stale Host versions. Preparing a draft does not query QCC; sending starts execution. Fully restart DSH after upgrading. Existing download artifacts are not rewritten.
|
|
14
14
|
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# dsh-data-cleaning-agent
|
|
2
2
|
|
|
3
|
-
本版统一向导与工作台的 Host 执行闭环:发送后自动生成新 Excel,原文件不修改,工作台入口可恢复;发布记录见 [发布说明](docs/RELEASE-0.8.
|
|
3
|
+
本版统一向导与工作台的 Host 执行闭环:发送后自动生成新 Excel,原文件不修改,工作台入口可恢复;发布记录见 [发布说明](docs/RELEASE-0.8.22.md)。
|
|
4
4
|
|
|
5
5
|
## 安装与三分钟上手
|
|
6
6
|
|
|
7
7
|
数据清洗补全智能体:面向 Excel/CSV/JSON 企业名单,提供数据清洗、表格清洗、清洗补全、去重、企业数据补全与字段补全,支持企查查 MCP 和结果导出。
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
dsh plugin --profile web add dsh-data-cleaning-agent@0.8.
|
|
10
|
+
dsh plugin --profile web add dsh-data-cleaning-agent@0.8.22
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
请先满足下文的 DSH、连接器及侧边栏依赖要求;安装后完整停止并重启对应 Profile。
|
|
@@ -25,7 +25,7 @@ dsh plugin --profile web add dsh-data-cleaning-agent@0.8.20
|
|
|
25
25
|
|
|
26
26
|
> 在 DeepSeek Harness 中清洗、补全、画像企业名单数据的智能体插件:本地 CSV/XLSX/JSON 引擎 + 可选企查查(Qichacha/QCC)MCP 企业数据补全,由企查查(Qichacha/QCC)团队发起并维护。
|
|
27
27
|
>
|
|
28
|
-
> 当前源码版本 / Current source version: **0.8.
|
|
28
|
+
> 当前源码版本 / Current source version: **0.8.22**(正式版本)
|
|
29
29
|
|
|
30
30
|
本版上传表格后展示完整清单及空白数量,用绿色、琥珀色、红色及文字区分已通过、待确认和未匹配映射。已映射字段自动纳入补全范围,额外字段默认折叠;多个原列可经确认使用同一非主体字段,分别补空并保留非空原值。修复 Host 草稿重置为默认五字段的问题,增加前后端版本错配提示。生成说明不查询,发送才执行;升级后需完整重启 DSH,历史下载文件不自动改写。
|
|
31
31
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 0.8.22 发布说明
|
|
2
|
+
|
|
3
|
+
包含 0.8.21 的实际 Host 协议文件链接与执行自动展开功能;额外修复尺寸回调在工作台关闭后写回留白的竞态。
|
|
4
|
+
|
|
5
|
+
- 表格不压缩列宽,支持左右滚动;纵向滚动固定表头。
|
|
6
|
+
- 预览和下载操作分行留白,长文件名不再挤压按钮。
|
|
7
|
+
- 单独显示失败数量;失败行在异常清单保留规范化错误码和工具名。
|
|
8
|
+
|
|
9
|
+
本次不改变企查查查询范围,不自动重跑历史任务;历史异常文件不会补写已遗漏的错误信息。
|
|
10
|
+
|
|
11
|
+
升级后完整重启 DSH。历史错误链接文本不会重写,可从工作台访问原有文件,不需要重新查询。
|
package/lib/client.js
CHANGED
|
@@ -89,19 +89,27 @@ window.__ModuleLoader__.load({
|
|
|
89
89
|
const handle = drawer.querySelector('.dcAgentResizeHandle');
|
|
90
90
|
if (!handle || !view) return () => {};
|
|
91
91
|
const reservations = new Map();
|
|
92
|
+
const registryKey = Symbol.for('dsh.data-cleaning.sizing-reservations');
|
|
93
|
+
const registry = doc[registryKey] || (doc[registryKey] = new WeakMap());
|
|
94
|
+
let disposed = false;
|
|
92
95
|
let requested = expanded ? 980 : preferredWidth;
|
|
93
96
|
let current = 0;
|
|
94
97
|
let drag = null;
|
|
95
98
|
const boundsForView = () => workbenchWidthBounds(view.innerWidth,
|
|
96
99
|
doc.querySelector('[data-conversation-scroll]')?.getBoundingClientRect().width || view.innerWidth);
|
|
97
100
|
const reserve = () => {
|
|
101
|
+
if (disposed) return;
|
|
98
102
|
const width = boundsForView().mobile ? 0 : drawer.getBoundingClientRect().width;
|
|
99
103
|
for (const el of doc.querySelectorAll('[data-conversation-scroll]')) {
|
|
100
104
|
if (!reservations.has(el)) {
|
|
101
|
-
|
|
105
|
+
const previous = registry.get(el) || {
|
|
102
106
|
value: el.style.getPropertyValue('--dc-agent-workbench-reserve'),
|
|
103
107
|
priority: el.style.getPropertyPriority('--dc-agent-workbench-reserve'),
|
|
104
|
-
|
|
108
|
+
owners: 0,
|
|
109
|
+
};
|
|
110
|
+
previous.owners += 1;
|
|
111
|
+
registry.set(el, previous);
|
|
112
|
+
reservations.set(el, previous);
|
|
105
113
|
sizeObserver?.observe(el, { box: 'border-box' });
|
|
106
114
|
}
|
|
107
115
|
if (el.style.getPropertyValue('--dc-agent-workbench-reserve') !== `${width}px`) {
|
|
@@ -110,6 +118,7 @@ window.__ModuleLoader__.load({
|
|
|
110
118
|
}
|
|
111
119
|
};
|
|
112
120
|
const apply = (value) => {
|
|
121
|
+
if (disposed) return;
|
|
113
122
|
const bounds = boundsForView();
|
|
114
123
|
const fallback = view.innerWidth <= 1040 ? Math.min(420, view.innerWidth * .48) : Math.min(460, view.innerWidth * .42);
|
|
115
124
|
current = Math.round(Math.min(bounds.max, Math.max(bounds.min, Number.isFinite(value) ? value : fallback)));
|
|
@@ -173,6 +182,7 @@ window.__ModuleLoader__.load({
|
|
|
173
182
|
observer?.observe(doc.body, { childList: true, subtree: true });
|
|
174
183
|
apply(requested);
|
|
175
184
|
return () => {
|
|
185
|
+
disposed = true;
|
|
176
186
|
cancel();
|
|
177
187
|
observer?.disconnect();
|
|
178
188
|
sizeObserver?.disconnect();
|
|
@@ -188,6 +198,9 @@ window.__ModuleLoader__.load({
|
|
|
188
198
|
drawer.style.removeProperty('--dc-agent-panel-width');
|
|
189
199
|
delete drawer.dataset.narrow;
|
|
190
200
|
for (const [el, previous] of reservations) {
|
|
201
|
+
previous.owners -= 1;
|
|
202
|
+
if (previous.owners > 0) continue;
|
|
203
|
+
registry.delete(el);
|
|
191
204
|
if (previous.value) el.style.setProperty('--dc-agent-workbench-reserve', previous.value, previous.priority);
|
|
192
205
|
else el.style.removeProperty('--dc-agent-workbench-reserve');
|
|
193
206
|
}
|
|
@@ -970,6 +983,25 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
970
983
|
}
|
|
971
984
|
.dcAgentFieldAction:hover, .dcAgentFieldAction:focus-visible { background: var(--dc-selected); }
|
|
972
985
|
.dcAgentPreviewTable { margin-top: 12px; overflow: auto; border: 1px solid var(--dc-border); border-radius: 8px; }
|
|
986
|
+
.dcAgentArtifactList { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; min-width: 0; }
|
|
987
|
+
.dcAgentArtifactList > .dcAgentButton { display: inline-flex; align-items: center; box-sizing: border-box; max-width: 100%; min-width: 0; height: auto; white-space: normal; overflow-wrap: anywhere; line-height: 1.5; text-decoration: none; }
|
|
988
|
+
.dcAgentArtifactList > a.dcAgentButton { flex-basis: 100%; }
|
|
989
|
+
.dcAgentPreviewTable { min-width: 0; max-width: 100%; scrollbar-gutter: stable; }
|
|
990
|
+
.dcAgentPreviewTable > .dcAgentTable {
|
|
991
|
+
width: max-content; min-width: 100%; max-width: none;
|
|
992
|
+
table-layout: auto; border-collapse: separate; border-spacing: 0;
|
|
993
|
+
}
|
|
994
|
+
.dcAgentPreviewTable > .dcAgentTable th,
|
|
995
|
+
.dcAgentPreviewTable > .dcAgentTable td {
|
|
996
|
+
min-width: 140px; max-width: none; white-space: nowrap;
|
|
997
|
+
word-break: normal; overflow-wrap: normal;
|
|
998
|
+
border-right: 1px solid var(--dc-border);
|
|
999
|
+
}
|
|
1000
|
+
.dcAgentPreviewTable > .dcAgentTable th:first-child,
|
|
1001
|
+
.dcAgentPreviewTable > .dcAgentTable td:first-child { min-width: 64px; }
|
|
1002
|
+
.dcAgentPreviewTable > .dcAgentTable th {
|
|
1003
|
+
position: sticky; top: 0; z-index: 2; background: var(--dc-table-head);
|
|
1004
|
+
}
|
|
973
1005
|
.dcAgentHistoryTask { display: block; width: 100%; padding: 10px; text-align: left; }
|
|
974
1006
|
.dcAgentHistoryTask h3 { margin: 0; font-size: 12px; }
|
|
975
1007
|
/*
|
|
@@ -1862,9 +1894,15 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1862
1894
|
return response.task;
|
|
1863
1895
|
}
|
|
1864
1896
|
function watchHostedCommand(actions, sessionId, command) {
|
|
1897
|
+
let openedForExecution = false;
|
|
1898
|
+
const ownerVisible = () => typeof document === 'undefined' || [...document.querySelectorAll('.dcAgentExperience')].some(node => node.dataset.sessionId === sessionId);
|
|
1865
1899
|
void waitForQccCommand(command.commandId, 1800, async (status) => {
|
|
1866
1900
|
if (!status || status.state === 'prepared') return;
|
|
1867
1901
|
await refreshHostedTask(actions, sessionId, command.taskId, null, true).catch(() => {});
|
|
1902
|
+
if (!openedForExecution && ownerVisible() && isCleaningSession(sessionId) && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) {
|
|
1903
|
+
openedForExecution = true;
|
|
1904
|
+
openWorkbench(actions, workflowTaskBySession.get(String(sessionId))?.stage || 'match', sessionId);
|
|
1905
|
+
}
|
|
1868
1906
|
if (isCleaningSession(sessionId) && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) actions.setCommandProgress?.({ taskId: command.taskId, ...status.progress });
|
|
1869
1907
|
}).then(async (completed) => {
|
|
1870
1908
|
await refreshHostedTask(actions, sessionId, command.taskId, completed.run);
|
|
@@ -2180,6 +2218,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2180
2218
|
const WORKBENCH_DATASET_EVENT = 'dsh:data-cleaning-workbench-dataset';
|
|
2181
2219
|
const WORKBENCH_DRAFT_EVENT = 'dsh:data-cleaning-workbench-draft';
|
|
2182
2220
|
const WORKBENCH_PREPARE_EVENT = 'dsh:data-cleaning-workbench-prepare';
|
|
2221
|
+
const WORKBENCH_OWNER_LEFT_EVENT = 'dsh:data-cleaning-workbench-owner-left';
|
|
2183
2222
|
|
|
2184
2223
|
// session scope 不能复用 root scope 的 store handle。DSH 当前会分别物化 root/session
|
|
2185
2224
|
// 插槽组件,因此以 document 事件跨 scope 通知;闭包引用仅作为无 DOM 测试降级。
|
|
@@ -2542,6 +2581,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2542
2581
|
function DataCleaningExperience(props) {
|
|
2543
2582
|
const { sessionId, session: ownerSession } = props;
|
|
2544
2583
|
const enabled = useCleaningSession(sessionId);
|
|
2584
|
+
react.useEffect(() => {
|
|
2585
|
+
if (!enabled) return undefined;
|
|
2586
|
+
return () => document.dispatchEvent(new CustomEvent(WORKBENCH_OWNER_LEFT_EVENT, { detail: { sessionId } }));
|
|
2587
|
+
}, [enabled, sessionId]);
|
|
2545
2588
|
const hero = ownerSession?.composerPhase === 'blank';
|
|
2546
2589
|
const [menuMount, setMenuMount] = react.useState(null);
|
|
2547
2590
|
react.useEffect(() => {
|
|
@@ -3228,7 +3271,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3228
3271
|
meta.hint ? h('div', { className: 'dcAgentToolCardHint' }, meta.hint) : null,
|
|
3229
3272
|
summary ? h('div', { className: 'dcAgentToolCardBody' }, summary) : null,
|
|
3230
3273
|
toolName === 'data_cleaning_qcc_run' ? h('div', { className: 'dcAgentArtifactList' },
|
|
3231
|
-
[...summary.matchAll(/\[([^\]]+)\]\((
|
|
3274
|
+
[...summary.matchAll(/\[([^\]]+)\]\(((?:https?:\/\/[a-zA-Z0-9.:[\]-]+)?\/data-cleaning\/api\/workflow\/tasks\/[a-zA-Z0-9%_-]+\/artifacts\/[a-zA-Z0-9%_-]+(?:\?preview=1)?)\)/g)].filter(match => match[2].startsWith('/') || (typeof location !== 'undefined' && new URL(match[2]).origin === location.origin)).map((match, index) =>
|
|
3232
3275
|
h('a', { key: index, className: 'dcAgentButton', href: match[2], target: '_blank', rel: 'noopener noreferrer' }, match[1]))) : null,
|
|
3233
3276
|
);
|
|
3234
3277
|
}
|
|
@@ -3423,7 +3466,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3423
3466
|
h('p', { className: 'dcAgentHint', role: 'status' }, complete
|
|
3424
3467
|
? `共 ${total} 条,当前显示 ${total ? start + 1 : 0}–${Math.min(start + pageSize, total)} 条;共 ${columns.length} 列,可横向滚动。`
|
|
3425
3468
|
: `完整清单尚未就绪:预期 ${expectedCount} 条,当前载入 ${total} 条。请等待同步;刷新后若仍缺失,请重新上传源文件。`),
|
|
3426
|
-
h('div', { className: 'dcAgentPreviewTable', style: { overflow: 'auto', maxHeight: '420px' } },
|
|
3469
|
+
h('div', { className: 'dcAgentPreviewTable', tabIndex: 0, role: 'region', 'aria-label': '表格预览,可横向和纵向滚动,表头固定', style: { overflow: 'auto', maxHeight: '420px' } },
|
|
3427
3470
|
h('table', { className: 'dcAgentTable' },
|
|
3428
3471
|
h('thead', null, h('tr', null, h('th', null, '序号'), columns.map((key) => h('th', { key }, resultFieldLabel(key))))),
|
|
3429
3472
|
h('tbody', null, rows.slice(start, start + pageSize).map((row, index) => h('tr', { key: start + index },
|
|
@@ -3488,6 +3531,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3488
3531
|
|
|
3489
3532
|
react.useEffect(() => {
|
|
3490
3533
|
rootWorkbenchActions = actions;
|
|
3534
|
+
const handleOwnerLeft = () => actions.close();
|
|
3491
3535
|
const handleOpen = (event) => {
|
|
3492
3536
|
event?.preventDefault?.();
|
|
3493
3537
|
const detail = event?.detail ?? {};
|
|
@@ -3540,6 +3584,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3540
3584
|
};
|
|
3541
3585
|
if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
|
|
3542
3586
|
document.addEventListener(WORKBENCH_OPEN_EVENT, handleOpen);
|
|
3587
|
+
document.addEventListener(WORKBENCH_OWNER_LEFT_EVENT, handleOwnerLeft);
|
|
3543
3588
|
document.addEventListener(WORKBENCH_DATASET_EVENT, handleDataset);
|
|
3544
3589
|
document.addEventListener(WORKBENCH_DRAFT_EVENT, handleDraft);
|
|
3545
3590
|
document.addEventListener(WORKBENCH_PREPARE_EVENT, handlePrepare);
|
|
@@ -3547,6 +3592,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3547
3592
|
return () => {
|
|
3548
3593
|
if (typeof document !== 'undefined' && typeof document.removeEventListener === 'function') {
|
|
3549
3594
|
document.removeEventListener(WORKBENCH_OPEN_EVENT, handleOpen);
|
|
3595
|
+
document.removeEventListener(WORKBENCH_OWNER_LEFT_EVENT, handleOwnerLeft);
|
|
3550
3596
|
document.removeEventListener(WORKBENCH_DATASET_EVENT, handleDataset);
|
|
3551
3597
|
document.removeEventListener(WORKBENCH_DRAFT_EVENT, handleDraft);
|
|
3552
3598
|
document.removeEventListener(WORKBENCH_PREPARE_EVENT, handlePrepare);
|
|
@@ -4138,6 +4184,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4138
4184
|
? (qccRun.summary?.ambiguous ?? 0) + (qccRun.summary?.fieldReview ?? 0)
|
|
4139
4185
|
: cachedTask?.enrichmentSummary?.reviewRequired
|
|
4140
4186
|
?? cachedTask?.matchSummary?.reviewRequired;
|
|
4187
|
+
const qccFailedCount = qccRun?.summary?.failed ?? cachedTask?.enrichmentSummary?.failed ?? cachedTask?.matchSummary?.failed ?? 0;
|
|
4141
4188
|
const stat = (label, value, tone) => h('div', { className: 'dcAgentCard' },
|
|
4142
4189
|
h('span', null, label),
|
|
4143
4190
|
h('b', { className: tone ? `is-${tone}` : null }, displayStatValue(value))
|
|
@@ -4373,12 +4420,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4373
4420
|
const availableArtifacts = (cachedTask?.artifacts || []).filter(artifact => artifact.kind !== 'review' || artifact.rowCount > 0);
|
|
4374
4421
|
const canCreateArtifacts = Boolean(exportRows()?.length) && ['rules_confirmed', 'diagnosed', 'export_ready', 'partial'].includes(cachedTask?.state);
|
|
4375
4422
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4376
|
-
h('p', { className: 'dcAgentHint' }, '结果文件由 Host
|
|
4423
|
+
h('p', { className: 'dcAgentHint' }, '结果文件由 Host 保存;有失败、未匹配或待核验记录时展示异常清单。任务或插件重启后,可从任务历史继续下载;无需再次调用企查查。'),
|
|
4377
4424
|
h('div', { className: 'dcAgentGrid' },
|
|
4378
4425
|
stat('输入行数', dataset ? dataset.rowCount : workflowTask?.source?.rowCount ?? '—'),
|
|
4379
4426
|
stat('匹配补全', qccEnrichedCount, qccEnrichedCount > 0 ? 'good' : null),
|
|
4380
4427
|
stat('待核验', qccReviewCount, qccReviewCount > 0 ? 'warn' : null),
|
|
4381
4428
|
stat('任务状态', WORKFLOW_STATE_LABELS[cachedTask?.state] || cachedTask?.state || '进行中'),
|
|
4429
|
+
stat('失败', qccFailedCount, qccFailedCount > 0 ? 'bad' : null),
|
|
4382
4430
|
),
|
|
4383
4431
|
availableArtifacts.length ? h('div', { className: 'dcAgentArtifactList' },
|
|
4384
4432
|
availableArtifacts.filter(artifact => artifact.format === 'xlsx').map(artifact => h('a', {
|
|
@@ -4555,8 +4603,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4555
4603
|
max: commandProgress.totalUnique, value: Math.min(commandProgress.totalUnique, Math.max(0, commandProgress.completedUnique || 0)),
|
|
4556
4604
|
'aria-label': '主体处理进度', style: { width: '100%', accentColor: cachedTask.state === 'completed' ? '#16845b' : cachedTask.state === 'failed' ? '#c62828' : cachedTask.state === 'partial' ? '#a66a00' : '#087dcc' },
|
|
4557
4605
|
}) : null,
|
|
4558
|
-
h('p', { style: { color: Number(qccReviewCount) > 0 ? '#a66a00' :
|
|
4559
|
-
Number.isFinite(qccEnrichedCount) ? `已处理并补全 ${qccEnrichedCount} 条 · 待核验 ${Number.isFinite(qccReviewCount) ? qccReviewCount : '统计中'} 条(按输入行统计;未披露字段保留空白)` : '结果统计中,处理结束后展示补全与待核验数量'),
|
|
4606
|
+
h('p', { style: { color: qccFailedCount > 0 ? '#c62828' : Number(qccReviewCount) > 0 ? '#a66a00' : qccEnrichedCount > 0 ? '#16845b' : 'inherit' } },
|
|
4607
|
+
Number.isFinite(qccEnrichedCount) ? `已处理并补全 ${qccEnrichedCount} 条 · 待核验 ${Number.isFinite(qccReviewCount) ? qccReviewCount : '统计中'} 条 · 失败 ${qccFailedCount} 条(按输入行统计;未披露字段保留空白)` : '结果统计中,处理结束后展示补全与待核验数量'),
|
|
4560
4608
|
) : null,
|
|
4561
4609
|
error ? h('div', { className: 'dcAgentError', role: 'alert' }, error) : null,
|
|
4562
4610
|
step !== 'history' ? h('details', { className: 'dcAgentSection' },
|
package/lib/qcc-command.js
CHANGED
|
@@ -138,7 +138,7 @@ export class QccCommandStore {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
prepare(input) {
|
|
141
|
+
prepare(input, { artifactOrigin = '' } = {}) {
|
|
142
142
|
this.cleanup(true);
|
|
143
143
|
const normalized = normalizedInput(input);
|
|
144
144
|
const estimate = normalized.kind === 'enrich'
|
|
@@ -148,6 +148,7 @@ export class QccCommandStore {
|
|
|
148
148
|
const at = new Date(this.clock()).toISOString();
|
|
149
149
|
const record = {
|
|
150
150
|
commandId,
|
|
151
|
+
artifactOrigin,
|
|
151
152
|
taskId: normalized.taskId,
|
|
152
153
|
kind: normalized.kind,
|
|
153
154
|
state: 'prepared',
|
|
@@ -265,7 +266,7 @@ export class QccCommandStore {
|
|
|
265
266
|
state: run.state,
|
|
266
267
|
summary: run.summary,
|
|
267
268
|
...(record.delivery ? { deliveryState: record.delivery.state, artifactCount: record.delivery.artifacts.length,
|
|
268
|
-
artifacts: record.delivery.artifacts.filter(a => a.format === 'xlsx' && (a.kind !== 'review' || a.rowCount > 0)).map(a => ({ fileName: a.fileName, url:
|
|
269
|
+
artifacts: record.delivery.artifacts.filter(a => a.format === 'xlsx' && (a.kind !== 'review' || a.rowCount > 0)).map(a => ({ fileName: a.fileName, url: `${record.artifactOrigin || ''}/data-cleaning/api/workflow/tasks/${encodeURIComponent(record.taskId)}/artifacts/${encodeURIComponent(a.id)}` })) } : {}),
|
|
269
270
|
});
|
|
270
271
|
}
|
|
271
272
|
}
|
package/lib/qcc.js
CHANGED
|
@@ -1074,7 +1074,11 @@ export class QccHostBridge {
|
|
|
1074
1074
|
...(result.fieldIssues ? { qcc_field_notes: Object.entries(result.fieldIssues).filter(([, issue]) => issue.reviewRequired === false).map(([field, issue]) => field + ': ' + issue.message).join(';') } : {}),
|
|
1075
1075
|
};
|
|
1076
1076
|
}
|
|
1077
|
-
return {
|
|
1077
|
+
return {
|
|
1078
|
+
...row, qcc_match_status: result?.status ?? 'failed',
|
|
1079
|
+
// Only export normalized diagnostic identifiers, never raw upstream messages or credentials.
|
|
1080
|
+
...(result?.error ? { qcc_error: [result.error.code, result.error.toolName, result.error.upstreamCode].filter(Boolean).join(' · ') } : {}),
|
|
1081
|
+
};
|
|
1078
1082
|
});
|
|
1079
1083
|
|
|
1080
1084
|
const indexesFor = (name) => normalized.filter((item) => item.companyName === name).map((item) => item.index);
|
package/lib/web.js
CHANGED
|
@@ -659,11 +659,20 @@ export function mountWebRoutes(wctx, { logger, report, TOOL_NAME, SKILL_NAME })
|
|
|
659
659
|
}
|
|
660
660
|
const payload = JSON.parse((await readBody(req)).toString('utf8'));
|
|
661
661
|
if (payload.kind && payload.kind !== 'enrich') requirePaidConfirmation(payload);
|
|
662
|
+
// Resolve from the actual browser request, never from model/task input.
|
|
663
|
+
let artifactOrigin = '';
|
|
664
|
+
const requestHost = req.headers?.host;
|
|
665
|
+
if (typeof requestHost === 'string' && /^[a-zA-Z0-9.:[\]-]+$/.test(requestHost)) {
|
|
666
|
+
try {
|
|
667
|
+
const origin = new URL(req.headers.origin || `${req.socket?.encrypted ? 'https' : 'http'}://${requestHost}`);
|
|
668
|
+
if (['http:', 'https:'].includes(origin.protocol) && origin.host === requestHost) artifactOrigin = origin.origin;
|
|
669
|
+
} catch { /* Relative links remain available for hosts without an origin. */ }
|
|
670
|
+
}
|
|
662
671
|
return writeJson(res, 201, {
|
|
663
672
|
ok: true,
|
|
664
673
|
marker: 'g5-agent-command',
|
|
665
674
|
paidCalls: false,
|
|
666
|
-
command: qccCommands.prepare(payload.workflowOwned ? await execution.prepare(payload) : payload),
|
|
675
|
+
command: qccCommands.prepare(payload.workflowOwned ? await execution.prepare(payload) : payload, { artifactOrigin }),
|
|
667
676
|
});
|
|
668
677
|
}
|
|
669
678
|
if (rest.length === 1 && req.method === 'GET') {
|
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.22",
|
|
4
4
|
"description": "Data cleaning and data enrichment for CSV/XLSX/JSON enterprise lists in DeepSeek Harness, including spreadsheet cleaning, deduplication, profiling, optional Qichacha MCP and exports.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"docs/RELEASE-0.8.1.md",
|
|
41
41
|
"docs/RELEASE-0.8.16.md",
|
|
42
42
|
"docs/RELEASE-0.8.17.md",
|
|
43
|
-
"docs/RELEASE-0.8.
|
|
43
|
+
"docs/RELEASE-0.8.22.md",
|
|
44
44
|
"docs/TEMPLATE-MAPPING-ACCEPTANCE.md",
|
|
45
45
|
"docs/UI-WORKFLOW-V2.md",
|
|
46
46
|
"docs/UI-WORKFLOW-V2-MIGRATION.md",
|
package/docs/RELEASE-0.8.20.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# 0.8.20 发布说明
|
|
2
|
-
|
|
3
|
-
修复未披露数据被误判为人工核验;保留未披露说明和空白值,不伪造数据。真正的多候选或无法判定的问题继续进入待核验。
|
|
4
|
-
|
|
5
|
-
当前页和历史列表同步最新任务状态。处理进度条区分处理中、完成、需核验和失败;100% 仅表示主体处理结束。摘要尚未生成时显示“结果统计中”。
|
|
6
|
-
|
|
7
|
-
任务展示名优先尊重自定义名称,否则由源文件名、行数和创建时间生成;当前页与历史一致。
|
|
8
|
-
|
|
9
|
-
验证:246 项自动测试、10 组浏览器回归。无付费查询,源文件未修改。
|
|
10
|
-
|
|
11
|
-
升级并完整重启 DSH 后生效;历史任务的旧异常文件和统计不会自动重算,不应仅为更新展示而重复查询。
|