dsh-data-cleaning-agent 0.9.11 → 0.9.13

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 CHANGED
@@ -1,3 +1,16 @@
1
+ # 0.9.13 · 2026-09-14
2
+
3
+ - 实际提交被 Host 接纳后自动展开所属会话工作台,草稿回填及失败发送不展开。
4
+ - 同会话任务凭证及接纳回调去重,手动收起后不被轮询再次拉开;后台会话不抢占当前界面。
5
+ - 保留 0.9.12 统一首页及品牌隔离修复。
6
+
7
+ # 0.9.12 · 2026-09-14
8
+
9
+ - Remove home subtitle and its spacing, retain native controls.
10
+ - Keep launcher naming stable and prevent duplicate launches.
11
+ - Preserve other agents’ branding and surfaces; clarify recorded progress and synchronization failures.
12
+ - Adopt DSH-UX-001 v1.5.4; 277 checks and 10 isolated browser scenarios pass.
13
+
1
14
  # Changelog
2
15
 
3
16
  本文件记录 `dsh-data-cleaning-agent` 的版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。
package/README.en.md CHANGED
@@ -10,9 +10,9 @@ Related agents: [数据清洗补全](https://github.com/duhu2000/dsh-data-cleani
10
10
 
11
11
  > 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.
12
12
  >
13
- > Current source version / 当前源码版本: **0.9.11** (stable release)
13
+ > Current source version / 当前源码版本: **0.9.13** (stable release)
14
14
 
15
- 0.9.11 uses the current cleaning Session's rendered Host phase instead of the nonexistent Session.composerPhase property. Branding appears on the real home screen and restores when leaving it.
15
+ 0.9.13 removes the home subtitle and its spacing, keeps native workspace/mode/composer controls, prevents duplicate menu launches, and scopes branding to its own home. Task records are distinguished from live model activity. The 0.9.11 Host phase and Session restoration fixes remain intact.
16
16
 
17
17
  0.9.10 observes the Host settling-to-hero transition so the database logo and product title appear even after delayed layout. Unknown structures do not trigger global headline rewriting. Rollback version: 0.9.9.
18
18
 
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  数据清洗补全智能体:面向 Excel/CSV/JSON 企业名单,提供数据清洗、表格清洗、清洗补全、去重、企业数据补全与字段补全,支持企查查 MCP 和结果导出。
10
10
 
11
11
  ```sh
12
- dsh plugin --profile web add dsh-data-cleaning-agent@0.9.11
12
+ dsh plugin --profile web add dsh-data-cleaning-agent@0.9.13
13
13
  ```
14
14
 
15
15
  请先满足下文的 DSH、连接器及侧边栏依赖要求;安装后完整停止并重启对应 Profile。
@@ -27,9 +27,9 @@ dsh plugin --profile web add dsh-data-cleaning-agent@0.9.11
27
27
 
28
28
  > 在 DeepSeek Harness 中清洗、补全、画像企业名单数据的智能体插件:本地 CSV/XLSX/JSON 引擎 + 可选企查查(Qichacha/QCC)MCP 企业数据补全,由企查查(Qichacha/QCC)团队发起并维护。
29
29
  >
30
- > 当前源码版本 / Current source version: **0.9.11**(正式版本)
30
+ > 当前源码版本 / Current source version: **0.9.13**(正式版本)
31
31
 
32
- 0.9.11 修正真实 Host 的首页状态接口:不再依赖 Session.composerPhase,按当前清洗会话所在宿主根节点的 data-phase 显示 Logo 与标题,并在离开首页时恢复。
32
+ 0.9.13 统一简洁首页:移除副标题与占位,保留原生工作区、模式和输入框。菜单名称固定并防止重复创建;品牌标题仅在所属首页生效,任务记录与模型运行状态明确区分。保留 0.9.11 的真实 Host 阶段与会话切换修复。
33
33
 
34
34
  0.9.10 补齐首页 settling → hero 阶段监听,避免首次挂载漏掉 Logo 与标题替换;兼容“探索未知之境”,未知结构不做全局标题改写。回滚版本为 0.9.9。
35
35
 
@@ -0,0 +1,9 @@
1
+ # 0.9.13 — 接纳任务后自动展开工作台
2
+
3
+ - 实际提交接纳后,自动展开所属 Session 的工作台,方便查看任务执行进展。
4
+ - 草稿输入与回填不展开;失败发送不伪造已启动。
5
+ - 同一任务凭证与提交回调只自动展开一次,手动收起后不会被后续轮询拉开。
6
+ - 后台会话不抢占当前会话;接纳回调观察器跟随真实绑定切换,并在卸载时释放。
7
+ - 保留 0.9.12 的统一首页、稳定菜单及真实状态提示。
8
+
9
+ 无需改动业务数据、任务存储或用户 MCP 授权。
@@ -0,0 +1,24 @@
1
+ # DSH-UX-001 v1.5.4 采用记录
2
+
3
+ 日期:2026-09-14。基线:0.9.11 / a0ec971db0d25d42f67f99e4328cca6f49a2bc2f。
4
+ 本轮仅本地代码与验收,不发布,不修改正式 Profile 或全局 DSH。
5
+
6
+ ## 已采用
7
+
8
+ - UX-05:首页删除 ProductHome 副标题组件、挂载与 dcAgentHomeSummary 样式;没有替代空白容器。保留原生工作区/模式/输入控件以及输入框下能力菜单,不修改 npm description/搜索文案。
9
+ - UX-45:左菜单固定“数据清洗补全”;创建中通过 disabled/aria-busy 表示,ref 门闩阻止同一渲染周期重复创建。失败仍沿用已有工作台错误状态,菜单名称不变。
10
+ - UX-46:保留 0.9.11 的真实 data-phase、openState、所有权立即重读和独立 Session namespace。只接管原生已知 Hero 标题或本插件标记;不覆盖其他产品标题,不再隐藏其他产品 dock。清理仅恢复仍为本插件标题的文字,不调用会话 rename API。
11
+ - UX-47:任务区明确展示“任务记录”,匹配/补全记录不冒充模型实时运行证明;元数据恢复/轮询失败明确为“状态同步异常”,保留最后记录且不重新查询。已有确认、取消、失败、完成状态继续来自 Host 任务数据,实际主体进度仍来自服务端计数,不伪造百分比。
12
+
13
+ ## 验证范围
14
+
15
+ - npm run check:277 项通过,包括安装包检查、正文语法与既有业务测试。
16
+ - 新增/调整断言:首页不含副标题组件或专属样式;同时点击只创建一个 Session;外产品标题和 dock 不被接管;既有 settling→hero、文本刷新、普通 Session、phase observer 清理继续回归。
17
+ - scripts/ui-layout-regression.mjs:10/10 通过,真实 React/Chromium,合成 Host DOM/API fixture,浅/深色 × 5 尺寸。验证品牌、普通会话恢复、晚挂载和无副标题;完整现有导入/映射/确认/下载测试保留。
18
+ - 浏览器 fixture 不等于完整真实 DSH 宿主。当前本轮未验证四包真实共装、用户正式页面或真实模型/QCC/OCR;正式版本仍为 0.9.11。
19
+
20
+ ## 边界
21
+
22
+ 清洗目前没有已验证的模型 running 状态接口,不新增假定宿主正在思考的状态条,也不声称掌握模型停止原因。恢复仅读取任务元数据。模型运行态由原生会话显示。
23
+
24
+ 证据:`/private/tmp/dc-unified-home-check.log`、`/private/tmp/dc-unified-home-ui-final.log` 与本工作树 `_scratch/ui-layout/results.json`。最后的未知标题保护收紧由观察器行为测试覆盖,未影响布局;其后完整 277 项检查再次通过。
package/lib/client.js CHANGED
@@ -200,14 +200,13 @@ window.__ModuleLoader__.load({
200
200
  }
201
201
  .dcAgentCapability svg { flex: 0 0 auto; }
202
202
 
203
- /* 原生 input.dock 保留轻量简介;菜单独立 Portal 到 composer 后,不重排共享 slot。 */
203
+ /* 首页无副标题或占位;菜单独立 Portal 到 composer 后,不重排共享 slot。 */
204
204
 
205
205
  .dcAgentExperience {
206
206
  width: 100%;
207
207
  box-sizing: border-box;
208
208
  }
209
209
  .dcAgentCapabilityMount { width: 100%; padding: 8px 0; flex: none; }
210
- .dcAgentHomeSummary { margin: 0 20px 8px; text-align: center; color: var(--dc-muted); font-size: 13px; line-height: 20px; }
211
210
  .dcAgentPromptBackdrop { position: fixed; inset: 0; z-index: 2000; display: grid; place-items: center; padding: 16px; box-sizing: border-box; background: rgba(8, 15, 28, .36); pointer-events: auto; }
212
211
  body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
213
212
 
@@ -2137,14 +2136,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2137
2136
  function watchHostedCommand(actions, sessionId, command) {
2138
2137
  const generation = commandWatchGeneration;
2139
2138
  const isActive = () => generation === commandWatchGeneration;
2140
- let openedForExecution = false;
2141
- const ownerVisible = () => typeof document === 'undefined' || [...document.querySelectorAll('.dcAgentExperience')].some(node => node.dataset.sessionId === sessionId);
2142
2139
  void waitForQccCommand(command.commandId, 1800, async (status) => {
2143
2140
  if (!status || status.state === 'prepared') return;
2144
2141
  await refreshHostedTask(actions, sessionId, command.taskId, null, true).catch(() => {});
2145
- if (!openedForExecution && ownerVisible() && isCleaningSession(sessionId) && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) {
2146
- openedForExecution = true;
2147
- openWorkbench(actions, workflowTaskBySession.get(String(sessionId))?.stage || 'match', sessionId);
2142
+ if (isActive() && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) {
2143
+ sessionWorkbenchController?.revealSubmitted(sessionId, command.commandId,
2144
+ workflowTaskBySession.get(String(sessionId))?.stage || 'match');
2148
2145
  }
2149
2146
  if (isCleaningSession(sessionId) && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) actions.setCommandProgress?.({ taskId: command.taskId, ...status.progress });
2150
2147
  }, isActive).then(async (completed) => {
@@ -2439,7 +2436,73 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2439
2436
  if (!sessionConversation || typeof sessionConversation.send !== 'function') {
2440
2437
  throw new Error('当前 DSH 版本没有可用的 Session conversation.send 能力');
2441
2438
  }
2442
- await sessionConversation.send(prompt);
2439
+ const result = await sessionConversation.send(prompt);
2440
+ if (result !== false) sessionWorkbenchController?.revealSubmitted(sessionId, submittedCommandId(prompt));
2441
+ }
2442
+
2443
+ function submittedCommandId(prompt) {
2444
+ return String(prompt || '').match(/\bdc[qi]-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i)?.[0] || null;
2445
+ }
2446
+
2447
+ /** Observe the public admission settlement; drafts and failed sends never reveal a Tab. */
2448
+ function installSubmittedWorkbenchBridge(ctx, controller) {
2449
+ const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
2450
+ let stopSession = () => {};
2451
+ let current = null;
2452
+ let currentFace = null;
2453
+ const connect = () => {
2454
+ const sessionId = sessions?.list?.getSnapshot?.()?.current;
2455
+ const face = sessionId ? sessions?.binding?.(sessionId)?.session : null;
2456
+ if (sessionId === current && face === currentFace) return;
2457
+ stopSession(); stopSession = () => {}; current = sessionId; currentFace = face;
2458
+ if (!sessionId) return;
2459
+ if (typeof face?.beginSubmission === 'function') {
2460
+ const original = face.beginSubmission;
2461
+ const wrapped = function(input) {
2462
+ if (!isCleaningSession(sessionId)) return original.call(this, input);
2463
+ let observed = false;
2464
+ return original.call(this, { ...input, onRetire: settlement => {
2465
+ try { input.onRetire?.(settlement); }
2466
+ finally {
2467
+ if (settlement.reason === 'observed' && !observed) {
2468
+ observed = true;
2469
+ controller.revealSubmitted(sessionId, submittedCommandId(input.text));
2470
+ }
2471
+ }
2472
+ } });
2473
+ };
2474
+ face.beginSubmission = wrapped;
2475
+ stopSession = () => { if (face.beginSubmission === wrapped) face.beginSubmission = original; };
2476
+ return;
2477
+ }
2478
+ // Older DSH versions publish committed nodes on the Session read face.
2479
+ if (!face?.subscribe || !face?.getSnapshot) return;
2480
+ let previous = face.getSnapshot();
2481
+ let baseline = previous.nodes?.length ?? 0;
2482
+ let highestSeq = Math.max(0, ...(previous.nodes || []).map(node => Number(node.seq) || 0));
2483
+ stopSession = face.subscribe(() => {
2484
+ const snapshot = face.getSnapshot();
2485
+ const nodes = snapshot.nodes || [];
2486
+ const appended = highestSeq ? nodes.filter(node => node.seq > highestSeq) : nodes.slice(baseline);
2487
+ highestSeq = Math.max(highestSeq, ...nodes.map(node => Number(node.seq) || 0));
2488
+ baseline = nodes.length;
2489
+ const restoring = (previous.openState && previous.openState !== 'open') || snapshot.loadingOlder;
2490
+ previous = snapshot;
2491
+ if (restoring) return;
2492
+ if (!isCleaningSession(sessionId)) return;
2493
+ for (const node of appended) {
2494
+ if (node.kind !== 'user' && node.role !== 'user' && node.message?.role !== 'user') continue;
2495
+ const content = node.message?.content ?? node.content ?? node.blocks ?? node.parts;
2496
+ const prompt = node.text || (typeof content === 'string' ? content
2497
+ : Array.isArray(content) ? content.map(part => typeof part === 'string' ? part : part.text || '').join('\n') : '');
2498
+ controller.revealSubmitted(sessionId, submittedCommandId(prompt));
2499
+ }
2500
+ });
2501
+ };
2502
+ connect();
2503
+ const stopList = sessions?.list?.subscribe?.(connect);
2504
+ connect();
2505
+ return () => { stopSession(); stopList?.(); };
2443
2506
  }
2444
2507
 
2445
2508
  function setQccAgentDraft(ctx, sessionId, prompt) {
@@ -2531,6 +2594,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2531
2594
  const { wide, useStore, actions, startSession } = props;
2532
2595
  useStore((state) => state.open);
2533
2596
  const [topMount, setTopMount] = react.useState(null);
2597
+ const [opening, setOpening] = react.useState(false);
2598
+ const openingRef = react.useRef(false);
2534
2599
 
2535
2600
  react.useEffect(() => {
2536
2601
  let disposed = false;
@@ -2559,7 +2624,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2559
2624
  className: 'dcAgentLauncher',
2560
2625
  'data-wide': wide,
2561
2626
  'aria-label': '数据清洗补全',
2627
+ 'aria-busy': opening,
2628
+ disabled: opening,
2562
2629
  onClick: async () => {
2630
+ if (openingRef.current) return;
2631
+ openingRef.current = true;
2632
+ setOpening(true);
2563
2633
  try {
2564
2634
  if (typeof startSession === 'function') {
2565
2635
  const sessionId = await startSession();
@@ -2568,6 +2638,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2568
2638
  } catch (error) {
2569
2639
  actions.setError(error instanceof Error ? error.message : String(error));
2570
2640
  console.error('[dc-agent] open failed:', error);
2641
+ } finally {
2642
+ openingRef.current = false;
2643
+ setOpening(false);
2571
2644
  }
2572
2645
  },
2573
2646
  children: h('span', { className: 'dcAgentLauncherContent' }, h(DatabaseLogo), wide ? h('span', null, '数据清洗补全') : null),
@@ -2604,11 +2677,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2604
2677
  authorization_required: '待连接企查查', cancelled: '已取消', parse_failed: '解析失败',
2605
2678
  };
2606
2679
 
2607
- function ProductHome() {
2608
- return h('p', { className: 'dcAgentHomeSummary' },
2609
- '导入企业名单或图片,核验主体、补全字段,并导出可追溯结果。');
2610
- }
2611
-
2612
2680
  function workflowNavigationIssue(step, task, hasData, profile, qccRun, hasLocalResult = false) {
2613
2681
  if (['upload', 'history'].includes(step)) return null;
2614
2682
  if (step === 'download' && task?.artifacts?.length) return null;
@@ -2744,8 +2812,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2744
2812
  const changedMarks = new Map();
2745
2813
  const changedRows = new Map();
2746
2814
  const ownedLogos = new Map();
2747
- const hiddenForeignDocks = new Map();
2748
- const composerStack = marker?.parentElement?.parentElement;
2749
2815
  // 单一会话所有权由入口 enabled 与真实宿主 data-phase=hero 共同保证,
2750
2816
  // 与 React effect 生命周期共同保证:非清洗会话不会挂载,会话切换即走 cleanup 让位。
2751
2817
  // 有界同步:每次 fix 的 DOM 变更计入校正预算,达到上限即断开观察器,杜绝与其它插件互相改写的死循环。
@@ -2763,7 +2829,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2763
2829
  ?? hero.querySelector?.('[class*="headlineText"]');
2764
2830
  const badge = spans.find((element) => element.dataset?.dcAgentHeroBadge === 'true')
2765
2831
  ?? spans.find((element) => ['预览版', 'Preview'].includes(element.textContent?.trim()));
2766
- if (title) {
2832
+ if (title && ['数据清洗补全智能体', '探索未至之境', '探索未知之境', 'Into the Unknown'].includes(title.textContent?.trim())) {
2767
2833
  if (!changedTitles.has(title)) changedTitles.set(title, title.textContent ?? '');
2768
2834
  title.dataset.dcAgentHeroTitle = 'true';
2769
2835
  if (title.textContent !== '数据清洗补全智能体') { title.textContent = '数据清洗补全智能体'; mutated = true; }
@@ -2791,28 +2857,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2791
2857
  }
2792
2858
  }
2793
2859
  }
2794
- if (badge) {
2860
+ if (badge && title?.dataset?.dcAgentHeroTitle === 'true') {
2795
2861
  if (!changedBadges.has(badge)) changedBadges.set(badge, badge.style?.display ?? '');
2796
2862
  badge.dataset.dcAgentHeroBadge = 'true';
2797
2863
  if (badge.style && badge.style.display !== 'none') { badge.style.display = 'none'; mutated = true; }
2798
2864
  }
2799
2865
 
2800
- const foreignSurfaces = composerStack?.querySelectorAll?.('[aria-label="尽调类型"], .qccDock') ?? [];
2801
- for (const foreignSurface of foreignSurfaces) {
2802
- let hideTarget = foreignSurface;
2803
- while (hideTarget?.parentElement
2804
- && hideTarget.parentElement !== composerStack
2805
- && hideTarget.parentElement !== marker.parentElement) {
2806
- hideTarget = hideTarget.parentElement;
2807
- }
2808
- // DSH 的 list slot 可能把多个插件放进同一个 data-slot 容器。此时只能隐藏
2809
- // 对方自己的根节点,不能隐藏共享 slot;独立 host 则隐藏其直接容器。
2810
- if ((hideTarget?.parentElement === composerStack || hideTarget?.parentElement === marker.parentElement)
2811
- && !hideTarget.contains?.(marker)) {
2812
- if (!hiddenForeignDocks.has(hideTarget)) hiddenForeignDocks.set(hideTarget, hideTarget.style?.display ?? '');
2813
- if (hideTarget.style && hideTarget.style.display !== 'none') { hideTarget.style.display = 'none'; mutated = true; }
2814
- }
2815
- }
2816
2866
  if (mutated) {
2817
2867
  corrections += 1;
2818
2868
  if (corrections >= MAX_HERO_CORRECTIONS && observer) {
@@ -2842,9 +2892,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2842
2892
  if (badge.style) badge.style.display = originalDisplay;
2843
2893
  delete badge.dataset.dcAgentHeroBadge;
2844
2894
  }
2845
- for (const [foreignDock, originalDisplay] of hiddenForeignDocks) {
2846
- if (foreignDock.style) foreignDock.style.display = originalDisplay;
2847
- }
2848
2895
  };
2849
2896
  }
2850
2897
 
@@ -2895,7 +2942,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2895
2942
  },
2896
2943
  dependencyMessage ? h('p', { className: 'dcAgentError', role: 'alert' }, dependencyMessage) : null,
2897
2944
  menuMount ? reactDom.createPortal(h(CapabilityBar, { sessionId }), menuMount) : h(CapabilityBar, { sessionId }),
2898
- hero ? h(ProductHome, { sessionId }) : null,
2899
2945
  );
2900
2946
  }
2901
2947
 
@@ -3872,7 +3918,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3872
3918
  const taskId = workflowTaskBySession.get(key)?.id || (/^dcw-[a-zA-Z0-9-]+$/.test(saved || '') ? saved : null);
3873
3919
  if (taskId) await refreshHostedTask(actions, activeSessionId, taskId, null, false);
3874
3920
  }).catch((taskError) => {
3875
- if (!disposed) actions.setError(taskError instanceof Error ? taskError.message : String(taskError));
3921
+ if (!disposed) actions.setError(`状态同步异常:${taskError instanceof Error ? taskError.message : String(taskError)}。保留最近任务记录,未重新执行查询。`);
3876
3922
  });
3877
3923
  }
3878
3924
  return () => { disposed = true; };
@@ -3890,7 +3936,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3890
3936
  if (!task) return;
3891
3937
  if (['matching', 'enriching'].includes(task.state)) timer = setTimeout(poll, 2000);
3892
3938
  else if (step === 'match' || step === 'enrich') actions.setStep(task.stage || step);
3893
- } catch (error) { if (!disposed) actions.setError(error.message); }
3939
+ } catch (error) { if (!disposed) actions.setError(`状态同步异常:${error.message}。保留最近任务记录,未重新执行查询。`); }
3894
3940
  };
3895
3941
  timer = setTimeout(poll, 2000);
3896
3942
  return () => { disposed = true; clearTimeout(timer); };
@@ -4831,8 +4877,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
4831
4877
  ) : null,
4832
4878
  h('div', { className: 'dcAgentWbBody' },
4833
4879
  cachedTask && step !== 'history' ? h('section', { className: 'dcAgentSection', 'aria-label': '当前任务进展', 'aria-live': 'polite' },
4834
- h('b', null, cachedTask.source?.type === 'image' && cachedTask.state === 'draft' ? '图片已暂存,待识别' : WORKFLOW_STATE_LABELS[cachedTask.state] || cachedTask.state),
4880
+ h('b', null, cachedTask.source?.type === 'image' && cachedTask.state === 'draft' ? '图片已暂存,待识别' : `任务记录:${WORKFLOW_STATE_LABELS[cachedTask.state] || '状态未知'}`),
4835
4881
  h('h3', null, workflowDisplayName(cachedTask)),
4882
+ ['matching', 'enriching'].includes(cachedTask.state) ? h('p', { className: 'dcAgentHint' }, '以上为最近同步的任务记录,不代表模型仍在运行。若会话已停止,请查看原会话的确认或错误提示;不要重复启动。') : null,
4836
4883
  h('details', null, h('summary', null, '任务详情'), h('p', { className: 'dcAgentHint' }, `任务编号:${cachedTask.id}`)),
4837
4884
  h('p', null, `${cachedTask.source?.type === 'image' ? '图片识别' : cachedTask.source?.type === 'text' ? '粘贴名单' : cachedTask.source ? '上传表格' : '待选择数据来源'}${cachedTask.source?.fileName ? ' · ' + cachedTask.source.fileName : ''} · ${cachedTask.source?.type === 'image' && cachedTask.state === 'draft' ? '条目数待识别' : `${cachedTask.source?.rowCount || 0} 条`} · ${cachedTask.fieldSelection?.length || 0} 个补全字段`),
4838
4885
  commandProgress?.taskId === cachedTask.id && Number.isFinite(commandProgress.totalUnique) ? h('p', null, `已处理主体 ${commandProgress.completedUnique}/${commandProgress.totalUnique}(去重后,含未匹配或失败,不代表补全成功)`) : null,
@@ -4957,6 +5004,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
4957
5004
  }
4958
5005
  let disposed = false;
4959
5006
  const instances = new Map();
5007
+ const revealedCommands = new Set();
4960
5008
  const spec = createWorkbenchStore();
4961
5009
  const instanceFor = sessionId => {
4962
5010
  if (!sessionId) throw new Error('请先打开数据清洗会话。');
@@ -5071,6 +5119,21 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
5071
5119
  sessions.open(task.originSessionId);
5072
5120
  // Navigation only: no task binding, command preparation, or draft writes.
5073
5121
  },
5122
+ revealSubmitted(sessionId, commandId, step) {
5123
+ if (disposed || !sessionId) return false;
5124
+ const key = commandId && `${sessionId}:${commandId}`;
5125
+ if (key && revealedCommands.has(key)) return false;
5126
+ // Consume the event even if its owner is no longer visible. Polling must
5127
+ // not pull open an old task when the user later returns to its Session.
5128
+ if (key) revealedCommands.add(key);
5129
+ const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
5130
+ if (sessions?.list?.getSnapshot?.()?.current !== sessionId || !isCleaningSession(sessionId)) return false;
5131
+ try { return controller.open(sessionId, step); }
5132
+ catch (error) {
5133
+ instanceFor(sessionId).actions.setError(error.message || String(error));
5134
+ return false;
5135
+ }
5136
+ },
5074
5137
  storeFor: instanceFor,
5075
5138
  actionsFor: sessionId => instanceFor(sessionId).actions,
5076
5139
  open(sessionId, step) {
@@ -5086,6 +5149,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
5086
5149
  },
5087
5150
  };
5088
5151
  sessionWorkbenchController = controller;
5152
+ const stopSubmittedBridge = installSubmittedWorkbenchBridge(ctx, controller);
5089
5153
  const listeners = {
5090
5154
  [WORKBENCH_OPEN_EVENT]: event => {
5091
5155
  const detail = event.detail || {};
@@ -5133,6 +5197,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
5133
5197
  const shared = { controller, references: 1, release() {
5134
5198
  if (--shared.references > 0) return;
5135
5199
  disposed = true;
5200
+ stopSubmittedBridge();
5136
5201
  commandWatchGeneration += 1;
5137
5202
  for (const remove of bindings) remove();
5138
5203
  host.removeEventListener?.('click', previewClick, true);
@@ -5141,6 +5206,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
5141
5206
  disconnectFixture();
5142
5207
  void providerFiber?.dispose();
5143
5208
  instances.clear();
5209
+ revealedCommands.clear();
5144
5210
  if (host[CONTROLLER_KEY] === shared) delete host[CONTROLLER_KEY];
5145
5211
  if (sessionWorkbenchController === controller) sessionWorkbenchController = null;
5146
5212
  } };
@@ -5235,11 +5301,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
5235
5301
  createWorkbenchStore,
5236
5302
  WorkbenchContent,
5237
5303
  installSessionWorkbench,
5304
+ sendQccAgentCommand,
5305
+ installSubmittedWorkbenchBridge,
5238
5306
  DatabaseLogo,
5239
5307
  visibleCatalogFields,
5240
5308
  installCapabilityMount,
5241
5309
  PromptDialog,
5242
- ProductHome,
5243
5310
  workflowNavigationIssue,
5244
5311
  buildTaskPrompt,
5245
5312
  DatasetReview,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-data-cleaning-agent",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
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",
@@ -21,6 +21,8 @@
21
21
  "marketing",
22
22
  "docs/USER-GUIDE.md",
23
23
  "docs/RELEASE-0.9.11.md",
24
+ "docs/RELEASE-0.9.13.md",
25
+ "docs/UNIFIED-HOME-1.5.4-ADOPTION.md",
24
26
  "docs/FIRST-CONTRIBUTION.md",
25
27
  "docs/COMPATIBILITY.md",
26
28
  "docs/UI-V1.5.0-ADOPTION.md",