dsh-data-cleaning-agent 0.8.1 → 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 CHANGED
@@ -2,7 +2,13 @@
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
- ## [Unreleased]
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`,拖入共用相同取图逻辑。
6
12
 
7
13
  ## [0.8.1] - 2026-09-05
8
14
 
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.1** (stable release)
5
+ > Current source version / 当前源码版本: **0.8.2** (stable release)
6
6
 
7
7
  [![CI](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml/badge.svg)](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml)
8
8
  [![npm](https://img.shields.io/npm/v/dsh-data-cleaning-agent)](https://www.npmjs.com/package/dsh-data-cleaning-agent)
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.1**(正式版本)
5
+ > 当前源码版本 / Current source version: **0.8.2**(正式版本)
6
6
 
7
7
  [![CI](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml/badge.svg)](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml)
8
8
  [![npm](https://img.shields.io/npm/v/dsh-data-cleaning-agent)](https://www.npmjs.com/package/dsh-data-cleaning-agent)
@@ -73,3 +73,9 @@ dsh web
73
73
  ```
74
74
 
75
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>。
@@ -67,7 +67,10 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
67
67
  ```
68
68
 
69
69
  市场中的远端 `qcc-document` 只接受公网 `file_url`,不能读取 Host 本地临时文件;只连接远端服务时,
70
- 插件会 fail closed 并提示配置本地服务。图片仅作为 0600 权限的 Host 临时文件,成功、失败、取消或
70
+ 图片仍可载入并显示缩略图、继续选择规则和字段,页面会单独提示配置本地服务;真正执行 OCR 时才
71
+ 阻断不可用的连接,绝不会向远端接口传本地路径。15 分钟暂存期内补齐连接后无需重新选图,超时或 Host
72
+ 重启后需重新选择。选图本身不会执行 OCR,回填并发送完整任务说明后才开始识别。
73
+ 图片仅作为 0600 权限的 Host 临时文件,成功、解析失败、取消或
71
74
  15 分钟超时后删除,不进入任务 KV、模型上下文或导出制品。`qcc-document-mcp` 会读取该临时文件并
72
75
  上传到企查查文档解析网关;用户应只提交有权处理且允许上传的数据。文档解析与后续企业补全均使用
73
76
  当前用户自己连接的企查查账号和额度;本插件不读取或保存凭据。
package/lib/client.js CHANGED
@@ -1565,6 +1565,15 @@ window.__ModuleLoader__.load({
1565
1565
  return mount;
1566
1566
  }
1567
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
+
1568
1577
  /** 使用 DSH 原生工作区、会话和输入机打开中央对话,不构造第二套聊天界面。 */
1569
1578
  async function startCleaningSession(ctx) {
1570
1579
  const workspace = ctx.workspaces.list.getSnapshot();
@@ -1577,21 +1586,17 @@ window.__ModuleLoader__.load({
1577
1586
  if (targetWorkspaceId === undefined) {
1578
1587
  throw new Error('请先选择一个工作空间,再打开数据清洗补全智能体');
1579
1588
  }
1580
- // rc.2 暴露 workspaces.connectWorkspace;alpha.2 把同一导航策略迁移到
1581
- // uiWorkspace.connectWorkspace,纯 workspaces controller 不再带该方法。
1582
- // 只在检测到真实方法后调用,避免把 alpha 实验面当成稳定契约。
1583
- const uiWorkspace = typeof ctx.get === 'function' ? ctx.get('uiWorkspace') : ctx.uiWorkspace;
1584
- const connectWorkspace = typeof ctx.workspaces.connectWorkspace === 'function'
1585
- ? ctx.workspaces.connectWorkspace.bind(ctx.workspaces)
1586
- : typeof uiWorkspace?.connectWorkspace === 'function'
1587
- ? uiWorkspace.connectWorkspace.bind(uiWorkspace)
1588
- : null;
1589
- const sessionId = connectWorkspace
1590
- ? await connectWorkspace(targetWorkspaceId)
1591
- : typeof ctx.sessions.create === 'function'
1592
- ? await ctx.sessions.create({ workspaceId: targetWorkspaceId })
1593
- : null;
1594
- 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
+ }
1595
1600
  const conversation = typeof ctx.get === 'function' ? ctx.get('conversation') : ctx.conversation;
1596
1601
  if (!conversation) throw new Error('DSH 对话服务尚未就绪,请稍后重试');
1597
1602
  markCleaningSession(sessionId);
@@ -1975,6 +1980,16 @@ window.__ModuleLoader__.load({
1975
1980
  }).filter(Boolean).slice(0, 50);
1976
1981
  }
1977
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
+
1978
1993
  async function imageFileBase64(file) {
1979
1994
  if (!file || typeof file.arrayBuffer !== 'function') throw new Error('图片文件不可读。');
1980
1995
  const bytes = new Uint8Array(await file.arrayBuffer());
@@ -2238,8 +2253,8 @@ window.__ModuleLoader__.load({
2238
2253
  react.useEffect(() => {
2239
2254
  if (!enabled) return undefined;
2240
2255
  const handleWindowPaste = (event) => {
2241
- const files = event.clipboardData?.files;
2242
- const hasImage = [...(files || [])].some((file) => String(file?.type ?? '').startsWith('image/'));
2256
+ const files = imageFilesFromTransfer(event.clipboardData);
2257
+ const hasImage = files.length > 0;
2243
2258
  if (!hasImage) return;
2244
2259
  const target = event.target;
2245
2260
  const inComposer = !open && Boolean(target?.closest?.('textarea, [contenteditable="true"], [role="textbox"]'));
@@ -2341,7 +2356,7 @@ window.__ModuleLoader__.load({
2341
2356
  role: 'group',
2342
2357
  'aria-label': '粘贴、拖入或选择企业名单图片',
2343
2358
  onPaste: (event) => {
2344
- const files = event.clipboardData?.files;
2359
+ const files = imageFilesFromTransfer(event.clipboardData);
2345
2360
  if (files?.length) {
2346
2361
  event.preventDefault();
2347
2362
  acceptImageFiles(files);
@@ -2352,7 +2367,7 @@ window.__ModuleLoader__.load({
2352
2367
  onDrop: (event) => {
2353
2368
  event.preventDefault();
2354
2369
  setImageDropActive(false);
2355
- acceptImageFiles(event.dataTransfer?.files);
2370
+ acceptImageFiles(imageFilesFromTransfer(event.dataTransfer));
2356
2371
  },
2357
2372
  },
2358
2373
  h('p', null, '可直接在此粘贴图片,或拖入 / 选择 PNG、JPEG、WebP。图片会在对话框显示缩略图,点击可放大。'),
@@ -2432,6 +2447,9 @@ window.__ModuleLoader__.load({
2432
2447
  h('pre', { className: 'dcAgentPromptPreview' }, buildTaskPrompt(promptConfig())),
2433
2448
  ) : null,
2434
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,
2435
2453
  error ? h('p', { className: 'dcAgentPromptError', role: 'alert' }, error) : null,
2436
2454
  h('div', { className: 'dcAgentPromptActions' },
2437
2455
  h('button', { type: 'button', className: 'dcAgentPromptAction', disabled: wizardStep === 1, onClick: () => setWizardStep(Math.max(1, wizardStep - 1)) }, '上一步'),
@@ -3786,6 +3804,7 @@ window.__ModuleLoader__.load({
3786
3804
  clearCleaningDraft,
3787
3805
  entriesToDataset,
3788
3806
  extractPromptEntries,
3807
+ imageFilesFromTransfer,
3789
3808
  guessMappings,
3790
3809
  deactivateCleaningSession,
3791
3810
  installSessionOwnershipBridge,
@@ -395,16 +395,6 @@ export class ImageIntakeStore {
395
395
  async prepare(input = {}) {
396
396
  await this.cleanup();
397
397
  const provider = this.providerDefinition();
398
- if (!provider) {
399
- const remoteOnly = Boolean(this.remoteDefinition());
400
- throw new ImageIntakeError(
401
- remoteOnly ? 'DC_IMAGE_LOCAL_DOCUMENT_PROVIDER_REQUIRED' : 'DC_IMAGE_PROVIDER_UNAVAILABLE',
402
- remoteOnly
403
- ? '当前只连接了远端 qcc-document,它不能读取本地图片。请在 MCP 连接器中配置本地 qcc-document-mcp。'
404
- : '当前 DSH 没有可用的企查查本地文档解析连接。请配置 qcc-document-mcp,或改用文本/Excel 名单。',
405
- 503,
406
- );
407
- }
408
398
  const decoded = decodeImage(input.content);
409
399
  const commandId = `dci-${randomUUID()}`;
410
400
  const at = new Date(this.clock()).toISOString();
@@ -417,9 +407,9 @@ export class ImageIntakeStore {
417
407
  fileName: safeName(input.fileName),
418
408
  mimeType: decoded.mimeType,
419
409
  sizeBytes: decoded.bytes.length,
420
- provider: provider.provider,
421
- parseTool: provider.parseName,
422
- resultTool: provider.resultName,
410
+ provider: IMAGE_PROVIDER_QCC_DOCUMENT_LOCAL,
411
+ parseTool: provider?.parseName ?? null,
412
+ resultTool: provider?.resultName ?? null,
423
413
  path,
424
414
  createdAt: at,
425
415
  updatedAt: at,
@@ -431,7 +421,7 @@ export class ImageIntakeStore {
431
421
  };
432
422
  this.records.set(commandId, record);
433
423
  this.scheduleExpiry(record);
434
- return publicRecord(record, this.clock());
424
+ return this.status(commandId);
435
425
  }
436
426
 
437
427
  require(commandId) {
@@ -444,7 +434,23 @@ export class ImageIntakeStore {
444
434
  }
445
435
 
446
436
  status(commandId) {
447
- return publicRecord(this.require(commandId), this.clock());
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
+ };
448
454
  }
449
455
 
450
456
  async run(commandId, exec) {
@@ -456,8 +462,11 @@ export class ImageIntakeStore {
456
462
  if (record.state === 'completed') return { commandId, ...record.result };
457
463
  const provider = this.providerDefinition(record.parseTool);
458
464
  if (!provider) {
459
- throw new ImageIntakeError('DC_IMAGE_PROVIDER_UNAVAILABLE', '企查查本地文档解析连接已离线。', 503);
465
+ const issue = this.providerIssue();
466
+ throw new ImageIntakeError(issue.code, issue.message, 503);
460
467
  }
468
+ record.parseTool = provider.parseName;
469
+ record.resultTool = provider.resultName;
461
470
  record.state = 'running';
462
471
  record.updatedAt = new Date(this.clock()).toISOString();
463
472
  const execute = (name, args) => this.tools.execute({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-data-cleaning-agent",
3
- "version": "0.8.1",
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",