dsh-data-cleaning-agent 0.5.0 → 0.5.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
@@ -4,9 +4,42 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
- ## [0.5.0] - Release candidate (2026-09-02)
7
+ ## [0.5.2] - 2026-09-03
8
8
 
9
- > 风险 / 知产 / 经营三域批量补全、Mockup 对齐工作台与发布候选;尚未打 tag、发布 npm 或生成 GitHub Release。
9
+ > DSH 原生 UI 对齐版本;不改变 QCC 工具范围、OAuth、计费确认或数据契约。
10
+
11
+ ### Added
12
+ - 在 DSH 原生输入框工具行增加上传清洗、质量体检、匹配核验、字段补全和任务历史五个能力入口。
13
+ - 在原生会话头增加「清洗工作台」恢复入口,任务历史页复用 Host `/mvp/jobs` 状态。
14
+
15
+ ### Changed
16
+ - 左栏入口采用与 MCP连接器一致的兼容方式:公开 `sidebar.footer.action` 仅托管生命周期和降级,
17
+ 实际按钮 Portal 到 `sidebar.workspaces` 前,显示在「新会话」与「工作区」之间。
18
+ - 点击左栏入口通过 DSH `workspaces` / `sessions` / `conversation` 服务打开中央原生会话并预填清洗提示词。
19
+ - 工作台由 980px 模态遮罩改为默认 510px 的非模态右侧面板;中央原生会话保持可见,移动端仍安全降级为全宽面板。
20
+
21
+ ### Fixed
22
+ - 修复应用入口错误显示在侧栏底部、点击后只出现大尺寸右侧遮罩而没有中央原生对话的问题。
23
+ - 修复 DSH 禁止同一 store handle 跨 root/session scope 复用导致的运行时挂载错误;会话按钮通过同页事件桥接根工作台状态。
24
+ - 右栏打开时按实际重叠量左移输入框能力栏,避免窄桌面视口中「匹配核验」「字段补全」「任务历史」被遮挡。
25
+ - 为 alpha.2 的 `uiWorkspace.connectWorkspace` 与 rc.2 的 `workspaces.connectWorkspace` 增加能力探测 Bridge,避免预发布版本入口只打开工作台却无法预填原生会话。
26
+
27
+ ## [0.5.1] - 2026-09-03
28
+
29
+ > 文档与发布流程修正版本;无运行时、QCC 工具契约或 API 变化。
30
+
31
+ ### Fixed
32
+ - 准备 0.5.1 文档补丁,使下一份 npm tarball 的中英文 README 正确反映最新已发布版本。
33
+
34
+ ### Changed
35
+ - 发布工作流启用严格文案 Gate:`v*` 标签只有在中英文 README 均切换到当前正式版本时才能进入
36
+ `npm publish`,防止候选状态再次进入不可变 npm 包。
37
+ - `docs:check` 增加普通分支/标签发布双模式与三项回归测试;运行时能力、QCC 工具契约和 API 均不变。
38
+
39
+ ## [0.5.0] - 2026-09-03
40
+
41
+ > 风险 / 知产 / 经营三域批量补全与 Mockup 对齐工作台;已通过 npm OIDC Trusted Publishing
42
+ > 发布并生成 GitHub Release。
10
43
 
11
44
  ### Added
12
45
  - 冻结三域 91 工具契约:风险 38、知识产权 18、经营 35;兼容 canonical、OAuth 0.1.7 legacy 与短名,
@@ -38,6 +71,7 @@
38
71
  - rc.2 实际渲染完成上传映射、体检和中文字段清洗闭环。
39
72
  - 2026-09-03 以维护者测试账号完成最小真实 Phase-3 E2E:1 家公开主体、1 个风险工具,
40
73
  估算/实际调用均为 2,补全 1 行、待复核 0、错误 0;知产与经营域未做付费实调。
74
+ - Release workflow `33708528501` 通过,npm provenance 为 SLSA v1;公共 Registry 全新安装与 ESM 导入通过。
41
75
 
42
76
  ## [0.4.0] - 2026-09-02
43
77
 
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.5.0** (release candidate; npm `latest` remains 0.4.0)
5
+ > Current source version / 当前源码版本: **0.5.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)
@@ -40,10 +40,10 @@ Fully restart DeepSeek Harness afterwards (stop and re-run `dsh web`). Then say
40
40
  "help me clean this batch of company list data" and the plugin loads its built-in Skill and
41
41
  drives the clean / complete / profile tools.
42
42
 
43
- After restart, a "Data Cleaning" entry appears at the bottom of the sidebar: click it to open the
44
- workbench (upload & map profile match & review enrich & export, four steps). When the model calls the clean /
45
- complete / profile tools, matching tool cards render in the conversation, and a jobs pill in the
46
- workbench header shows queued / running tasks.
43
+ After restart, a "Data Cleaning" entry appears between "New Session" and "Workspaces" near the top
44
+ of the sidebar. It opens a native DSH conversation with five composer actions (upload, profile,
45
+ match, enrich, and history) and a right-side workbench for the four-step flow. Clean / complete /
46
+ profile tool calls continue to render matching cards in the native conversation.
47
47
 
48
48
  Without the `dsh` CLI, use the install script:
49
49
 
@@ -65,7 +65,7 @@ Or let an agent install it for you:
65
65
  | Parse | web `/data-cleaning/api/mvp/parse` | CSV / XLSX / JSON |
66
66
  | Async jobs | web `/data-cleaning/api/mvp/jobs` | job state machine + persistent storage |
67
67
  | UI | web `/data-cleaning/` | upload → clean/complete → export |
68
- | In-app entry | sidebar "Data Cleaning" button | opens the Mockup-aligned workbench: upload & map profile match & review enrich & export |
68
+ | In-app entry | top sidebar entry + five native composer actions | keeps the DSH conversation in the center and opens the Mockup-aligned workbench on the right |
69
69
  | Tool cards | `tool.call.toolview` (`data_clean_rows`/`data_complete_rows`/`data_profile`) | render clean/complete/profile result cards in-conversation with running/done/failed state |
70
70
  | Task progress | workbench header jobs pill | polls `/data-cleaning/api/mvp/jobs`; shows queued / running tasks |
71
71
  | Skill | `data-cleaning` | guides the model through the workflow |
@@ -90,7 +90,7 @@ Besides local deterministic completion, the plugin supports Qichacha MCP enterpr
90
90
  Ambiguous candidates are never auto-selected. A loopback-only, fail-closed E2E runner is ready. On 2026-09-01 an isolated rc.2
91
91
  Host passed real OAuth, restart recovery, and 400 QCC calls across 20 public companies. Natural-expiry
92
92
  token refresh, dynamic-tool recovery, a post-refresh real call, and 401/429/quota fault injection also passed.
93
- - **0.5.0 three-domain batch extension (release candidate)**: a frozen 91-tool contract covers risk (38),
93
+ - **0.5.0 three-domain batch extension (released)**: a frozen 91-tool contract covers risk (38),
94
94
  intellectual property (18), and operation (35). The workbench supports domain selection, a zero-call upper-bound
95
95
  estimate, separate paid-call confirmation, manual ambiguous-candidate locking, partial-failure retry, 30-minute
96
96
  Host-memory recovery, and result/review CSV exports. rc.2 and alpha.2 passed 24/24 zero-call Host smoke checks;
@@ -104,7 +104,9 @@ install the matching `@deepseek-ai/dsh-mcp-client` explicitly; see the compatibi
104
104
 
105
105
  See [the 0.4.0 release record](docs/RELEASE-0.4.0.md) for scope, validation gates, and rollback steps.
106
106
  See [the Phase-3 acceptance record](docs/PHASE3-ACCEPTANCE.md) and
107
- [the 0.5.0 release-candidate record](docs/RELEASE-0.5.0.md) for current gates, upgrade, and rollback.
107
+ [the 0.5.0 release record](docs/RELEASE-0.5.0.md) for verification, upgrade, and rollback.
108
+ See [the 0.5.1 release record](docs/RELEASE-0.5.1.md) for the README fix and release-text gate.
109
+ See [the 0.5.2 release record](docs/RELEASE-0.5.2.md) for native DSH UI alignment, verification, and rollback.
108
110
 
109
111
  See [docs/PLAN-OSS.md](docs/PLAN-OSS.md) for details.
110
112
 
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.5.0**(发布候选;npm `latest` 仍为 0.4.0)
5
+ > 当前源码版本 / Current source version: **0.5.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)
@@ -35,8 +35,9 @@ dsh plugin --profile web add dsh-data-cleaning-agent
35
35
  安装后**完全重启** DeepSeek Harness(停止后重新运行 `dsh web`)。
36
36
  之后在对话中说「帮我清洗这批企业名单数据」,插件会自动加载内嵌 Skill 并调度清洗/补全/画像工具。
37
37
 
38
- 重启后侧边栏底部会出现「数据清洗」入口:点击打开工作台(上传与映射 数据体检 → 匹配核验 → 补全与导出四步),
39
- 模型调用清洗/补全/画像工具时对话内会渲染对应工具卡片,工作台头部用任务 pill 展示排队/进行中的任务。
38
+ 重启后,「数据清洗」入口会显示在侧边栏顶部的「新会话」与「工作区」之间。点击后打开 DSH 原生会话,
39
+ 输入框工具行提供上传清洗、质量体检、匹配核验、字段补全和任务历史五个入口;右侧工作台承载
40
+ 上传与映射 → 数据体检 → 匹配核验 → 补全与导出四步。模型调用工具时,对话内会渲染对应工具卡片。
40
41
 
41
42
  没有 `dsh` CLI 时,也可以用安装脚本:
42
43
 
@@ -58,7 +59,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
58
59
  | 解析 | web `/data-cleaning/api/mvp/parse` | CSV / XLSX / JSON |
59
60
  | 异步任务 | web `/data-cleaning/api/mvp/jobs` | 任务状态机 + 持久化存储 |
60
61
  | UI | web `/data-cleaning/` | 上传 → 清洗/补全 → 导出 |
61
- | 应用内入口 | 侧边栏「数据清洗」按钮 | 打开 Mockup 对齐工作台:上传与映射 数据体检 → 匹配核验 → 补全与导出 |
62
+ | 应用内入口 | 侧栏顶部「数据清洗」+ 原生输入框五能力按钮 | 中央保留 DSH 原生对话,右侧打开 Mockup 对齐工作台 |
62
63
  | 工具卡片 | `tool.call.toolview`(`data_clean_rows`/`data_complete_rows`/`data_profile`) | 对话内渲染清洗/补全/画像结果卡,含运行/已完成/失败状态 |
63
64
  | 任务进度 | 工作台头部任务 pill | 轮询 `/data-cleaning/api/mvp/jobs`,展示排队/运行中任务 |
64
65
  | Skill | `data-cleaning` | 引导模型按工作流调度上述工具 |
@@ -83,7 +84,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
83
84
  默认关闭的本机 E2E Runner 已就绪;2026-09-01 已在隔离 rc.2 Host 完成真实 OAuth、跨重启恢复和
84
85
  20 家公开企业的 400 次 QCC 调用;自然过期 token 的真实刷新、动态工具恢复及 1 行续期后调用也已通过。
85
86
  401 / 429 / 配额耗尽使用 Web→Bridge→ToolRuntime 故障注入验证,不额外消耗真实付费批次。
86
- - **0.5.0 三域批量扩展(发布候选)**:风险 38、知识产权 18、经营 35 个工具已冻结为 91 工具契约;
87
+ - **0.5.0 三域批量扩展(已发布)**:风险 38、知识产权 18、经营 35 个工具已冻结为 91 工具契约;
87
88
  工作台支持域组勾选、零调用上界估算、独立付费确认、多候选人工锁定、部分失败人工重试、
88
89
  30 分钟 Host 内存恢复、结果 CSV 与复核 CSV。rc.2 / alpha.2 零调用 Host 冒烟 24/24 通过,
89
90
  rc.2 实际渲染与中文企业字段映射闭环通过。2026-09-03 又以维护者自己的测试账号完成最小真实
@@ -100,7 +101,9 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
100
101
  [docs/PHASE2-ACCEPTANCE.md](docs/PHASE2-ACCEPTANCE.md)。
101
102
  发布记录、验收门和回滚步骤见 [docs/RELEASE-0.4.0.md](docs/RELEASE-0.4.0.md)。
102
103
  0.5.0 三域验收状态见 [docs/PHASE3-ACCEPTANCE.md](docs/PHASE3-ACCEPTANCE.md),
103
- 发布候选、升级与回滚见 [docs/RELEASE-0.5.0.md](docs/RELEASE-0.5.0.md)。
104
+ 发布记录、升级与回滚见 [docs/RELEASE-0.5.0.md](docs/RELEASE-0.5.0.md)。
105
+ 0.5.1 文档补丁与防回归发布门见 [docs/RELEASE-0.5.1.md](docs/RELEASE-0.5.1.md)。
106
+ 0.5.2 DSH 原生 UI 对齐范围、验收与回滚见 [docs/RELEASE-0.5.2.md](docs/RELEASE-0.5.2.md)。
104
107
 
105
108
  ## 本地开发
106
109
 
@@ -14,10 +14,18 @@
14
14
  2026-09-01 的 0.4.0 发布内容已分别在 rc.2(43153)和 alpha.2(43154)
15
15
  隔离 Host 完成 tarball 加载冒烟,两者均返回 `enrichSkillRegistered:true`;测试进程已停止。
16
16
 
17
- 2026-09-02 0.5.0 发布候选在 rc.2(43136)与 alpha.2(43137)完成 24/24 零调用 Host 冒烟:
17
+ 2026-09-03 已发布的 0.5.0 rc.2(43136)与 alpha.2(43137)完成 24/24 零调用 Host 冒烟:
18
18
  MVP 路由、Phase-3 capabilities、estimate 与未确认 enrich 阻断均通过。rc.2 另完成实际工作台渲染、
19
19
  中文企业名称映射和本地清洗闭环;alpha.2 仍只定位为兼容探针。
20
20
 
21
+ 0.5.1 为已发布的 README 状态与发布 Gate 文档补丁,不修改 Host/Client、QCC 契约或运行时依赖,
22
+ 因此继承 0.5.0 的 DSH、Node 与 OAuth 兼容矩阵。
23
+
24
+ 0.5.2 源码候选只调整 Client UI:入口 Portal 到 `sidebar.workspaces` 前,中央区域复用 DSH 原生会话,
25
+ 五能力入口使用公开 `conversation.input.left`,会话头恢复入口使用公开
26
+ `conversation.session.header.actions`,右侧面板继续使用 additive `shell.overlay`。不替换单占位
27
+ `details` / `conversation.session.header`,也不修改 Host、QCC 工具契约或计费安全门。
28
+
21
29
  ## 2. Node 运行时
22
30
 
23
31
  - 本包 `engines.node` 声明 `>=20`。
@@ -70,7 +78,7 @@ MVP 路由、Phase-3 capabilities、estimate 与未确认 enrich 阻断均通过
70
78
 
71
79
  ### 4.2 应用内入口(M1–M3)双基线实测
72
80
 
73
- 侧边栏「数据清洗」入口、全屏工作台、三张工具卡片(`data_clean_rows` /
81
+ 0.5.0/0.5.1 的侧边栏底部入口、全屏工作台、三张工具卡片(`data_clean_rows` /
74
82
  `data_complete_rows` / `data_profile`)与任务 pill 在双基线均已通过隔离 `DSH_HOME` 冒烟验证:
75
83
 
76
84
  - **rc.2**:根 HTML 直接引用 `/plugins/dsh-data-cleaning-agent/client.js?rev=…`,client bundle
@@ -81,6 +89,12 @@ MVP 路由、Phase-3 capabilities、estimate 与未确认 enrich 阻断均通过
81
89
 
82
90
  两条基线均返回 `[dc-agent] host apply() ran`,且未发起任何真实 QCC 调用。
83
91
 
92
+ 0.5.2 将入口和工作台改为 Mockup 对齐结构;已在 rc.2 与 alpha.2 的全新隔离安装中复验顶部
93
+ Portal、原生会话、五能力按钮、跨 scope 状态桥、窄桌面布局避让与非模态右栏。alpha.2 将
94
+ 工作区导航方法放在 `uiWorkspace.connectWorkspace`,rc.2 使用 `workspaces.connectWorkspace`;
95
+ 插件只做运行时能力探测并保留 `sessions.create` 安全降级,不承诺 alpha 实验面稳定。Portal 失败时
96
+ 仍保留 footer 降级按钮。
97
+
84
98
  ### 4.3 0.5.0 三域兼容面
85
99
 
86
100
  | 能力 | rc.2 | alpha.2 | 备注 |
@@ -99,6 +113,6 @@ MVP 路由、Phase-3 capabilities、estimate 与未确认 enrich 阻断均通过
99
113
  - `/data-cleaning/api/g5/enrich` 为 0.4.0 已发布能力,单批上限 100 行、并发上限 4,
100
114
  且必须显式 `confirmPaidCalls:true` 和唯一 `idempotencyKey`;token 到期刷新与 401/429/配额故障门已通过,
101
115
  npm/GitHub Release 均已发布 `v0.4.0`。
102
- - `/data-cleaning/api/phase3/*` 为 0.5.0 发布候选;单批最多 100 行、并发最多 4、默认/硬调用上限
116
+ - `/data-cleaning/api/phase3/*` 为 0.5.0 已发布能力;单批最多 100 行、并发最多 4、默认/硬调用上限
103
117
  500/2000。run 只保留在 Host 内存 30 分钟,Host 重启不恢复。
104
118
  - alpha.2 的实际 UI 只作兼容探针;0.5.0 的稳定发布与回滚判断以 rc.2 为准。
@@ -30,7 +30,7 @@
30
30
  | --- | --- | --- | --- | --- |
31
31
  | 一期 | 0.3.0 ✅ | 方案 A:模型中介式 Skill `enterprise-enrichment` | 核心工商 7 字段 + 风险标签 | `qcc-dsh-mcp-oauth` 已连接 |
32
32
  | 二期 | 0.4.0 ✅ | 方案 A 扩展 Skill:工商全景 + 股权穿透 | 工商域 16 工具 + 历史工商 | 同上 |
33
- | 三期 | 0.5.0 RC | 方案 A 扩展 Skill:风险/知产/经营 + 方案 B 批量后端 | 风险 38 + 知产 18 + 经营 35 | 本地/双基线零调用门与最小真实 Phase-3 E2E 已过;待发布授权 |
33
+ | 三期 | 0.5.0 | 方案 A 扩展 Skill:风险/知产/经营 + 方案 B 批量后端 | 风险 38 + 知产 18 + 经营 35 | 已发布;最小真实 Phase-3 E2E 与公共安装已过 |
34
34
  | 四期(可选) | 0.6.0 | 历史轨迹 + 董监高 + 招投标 | 历史 34 + 人员 44 + 招投标 6 | 企业认证账号(历史域) |
35
35
 
36
36
  每期之间不互相阻塞:二期工商全景、三期风险知产都可独立评审与合入。
@@ -1,8 +1,8 @@
1
- # 0.5.0 发布候选记录
1
+ # 0.5.0 发布记录
2
2
 
3
- > 状态:**Release candidate;未打 tag、未发布 npm、未创建 GitHub Release。**
3
+ > 状态:**✅ 已发布;npm `latest`、Git tag GitHub Release 均为 `0.5.0`。**
4
4
  >
5
- > npm `latest` 仍为 `0.4.0`。
5
+ > 发布时间:2026-09-03T02:41:10Z
6
6
 
7
7
  ## 1. 范围
8
8
 
@@ -76,9 +76,12 @@ dsh plugin --profile web add dsh-data-cleaning-agent@0.4.0
76
76
  - [x] 维护者测试账号最小真实 Phase-3 E2E:1 家公开主体、1 个风险工具、`maxCalls: 2`,
77
77
  实际 2 次调用,1 行补全、0 待复核、0 错误;隔离端口 `43166`,临时凭据副本已清理。
78
78
 
79
- ## 6. 发布前外部门
79
+ ## 6. 发布结果
80
80
 
81
- - [ ] 用户明确批准创建并推送 `v0.5.0` tag。
82
- - [ ] 等待 OIDC workflow 完成 npm provenance 与 GitHub Release;再核验公共 Registry 全新安装。
81
+ - [x] 用户明确批准并已创建、推送 annotated tag `v0.5.0`,指向 `c1c889f`。
82
+ - [x] Release workflow `33708528501` 成功,完成 npm OIDC publish 与 GitHub Release
83
+ - [x] npm Registry 返回 `0.5.0`,provenance predicate 为 SLSA v1。
84
+ - [x] 公共 Registry 全新安装成功,ESM 导入可见 `apply`、`inject` 与 `name`。
83
85
 
84
- 发布前任何一项失败都不得移动 `latest`。
86
+ 已知文档差异:0.5.0 tarball 的 README 是 tag 时的不可变快照,仍含“发布候选 / latest 0.4.0”字样;
87
+ 运行包、provenance 与 Release 正常,GitHub `main` 已修正。npm 同版本不可覆盖,后续仅可由新补丁版本更新包页文案。
@@ -0,0 +1,52 @@
1
+ # 0.5.1 文档补丁发布记录
2
+
3
+ > 版本:**0.5.1**(文档与发布流程修正,无运行时代码变化)
4
+ >
5
+ > 状态:**✅ 已发布;npm `latest`、Git tag 与 GitHub Latest 均为 `0.5.1`。**
6
+ >
7
+ > 发布时间:2026-09-03T03:06:54Z
8
+
9
+ ## 1. 目的与范围
10
+
11
+ 0.5.1 只修正 0.5.0 npm tarball 中不可变 README 的历史状态文案,并加固发布流程:
12
+
13
+ - 中英文 README 随 `package.json` 更新,并在标签提交中切换为不含动态 `latest` 的 0.5.1 正式态;
14
+ - 标签发布时,`docs:check` 强制要求两份 README 都声明当前版本已正式发布;
15
+ - Release workflow 显式设置 `DSH_RELEASE_MODE=1`,严格文案门在 `npm publish` 前执行;
16
+ - 增加普通分支允许候选、标签拒绝候选、标签接受正式文案三项单测;
17
+ - 新发布记录进入 npm 包白名单。
18
+
19
+ 本补丁不修改 DSH Host/Client、QCC Bridge、OAuth、工具契约、API、数据流或 UI,不产生 QCC 调用。
20
+
21
+ ## 2. 兼容性
22
+
23
+ 运行时兼容性与 0.5.0 完全相同:
24
+
25
+ - 稳定基线:DSH `0.1.1-rc.2`;
26
+ - 兼容探针:DSH `0.1.2-alpha.2`;
27
+ - Node CI:22 / 24;
28
+ - QCC OAuth:`qcc-dsh-mcp-oauth@0.1.7`。
29
+
30
+ ## 3. 发布前门
31
+
32
+ - [x] `npm run check` 全绿:128/128 测试、36 个发布白名单文件。
33
+ - [x] `DSH_RELEASE_MODE=1 npm run docs:check` 在候选文案下按预期以退出码 1 失败。
34
+ - [x] 候选提交 `c03b1bd` 的 CI `33709568591` 全绿:Linux Node 22/24、Windows Node 24。
35
+ - [x] 发布前将 README.md / README.en.md 切换为不含动态 `latest` 的 0.5.1 正式发布态。
36
+ - [x] `DSH_RELEASE_MODE=1 npm run check` 在正式文案下全绿:128/128、36 文件。
37
+ - [x] 用户单独批准 `v0.5.1` tag、OIDC npm publish 与 GitHub Release。
38
+ - [x] `v0.5.1` annotated tag 指向 `19205ec`;Release workflow `33710151625` 全绿。
39
+ - [x] npm OIDC publish 与 SLSA v1 provenance、GitHub Latest 均已验证。
40
+ - [x] 公共 Registry 全新安装通过,ESM 导出与中英文 README 正式态均正确。
41
+
42
+ 后续任何版本仍须重新取得发布授权,不得覆盖已发布版本。
43
+
44
+ ## 4. 回滚
45
+
46
+ 本补丁无运行时代码变化。需要回滚时安装已发布的 0.5.0:
47
+
48
+ ```bash
49
+ dsh plugin --profile web add dsh-data-cleaning-agent@0.5.0
50
+ ```
51
+
52
+ 不得覆盖已发布的 npm 版本。
@@ -0,0 +1,43 @@
1
+ # 0.5.2 DSH 原生 UI 对齐发布记录
2
+
3
+ > 版本:**0.5.2**
4
+ >
5
+ > 状态:**正式版本;由 `v0.5.2` 标签触发 npm OIDC Trusted Publishing 与 GitHub Release。**
6
+ >
7
+ > 发布日期:2026-09-03
8
+
9
+ ## 1. 目的与范围
10
+
11
+ 0.5.2 将数据清洗补全智能体的入口和工作流对齐 DSH 原生交互:
12
+
13
+ - 左栏入口位于「新会话」与「工作区」之间,不再显示在侧栏底部;
14
+ - 点击入口后复用中央原生会话,并预填数据清洗提示词;
15
+ - 原生输入框工具行提供上传清洗、质量体检、匹配核验、字段补全和任务历史五个入口;
16
+ - 工作台改为非模态右侧面板,中央会话保持可见;
17
+ - 任务历史复用 Host `/mvp/jobs` 状态,可恢复已有任务;
18
+ - 对 DSH `0.1.1-rc.2` 与 `0.1.2-alpha.2` 的 workspace 连接服务做隔离能力探测。
19
+
20
+ 本版本不扩展历史域、人员域或招投标域,不改变 QCC OAuth、计费确认、工具允许列表或数据契约。
21
+
22
+ ## 2. 验证
23
+
24
+ - `DSH_RELEASE_MODE=1 npm run check`:lint、双语正式版本文案、marketing、pack 白名单和 132 项测试全部通过;
25
+ - DSH `0.1.1-rc.2` / `0.1.2-alpha.2` 全新隔离安装与 UI 冒烟通过;
26
+ - 已验证顶部入口、中央原生会话、五能力按钮、非模态右栏、布局避让、上传解析和质量体检;
27
+ - 本轮 UI 验收没有调用 QCC,也没有触碰生产 DSH Host。
28
+
29
+ ## 3. 升级与回滚
30
+
31
+ 升级:
32
+
33
+ ```bash
34
+ dsh plugin --profile web add dsh-data-cleaning-agent@0.5.2
35
+ ```
36
+
37
+ 如需回滚,安装上一稳定版本:
38
+
39
+ ```bash
40
+ dsh plugin --profile web add dsh-data-cleaning-agent@0.5.1
41
+ ```
42
+
43
+ npm 已发布版本不可覆盖;如发布后发现问题,使用新的补丁版本修复。
@@ -106,7 +106,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
106
106
  信用代码后续跑;retryable 失败只能由用户通过 `/g5/retry` 显式重试。run 仅在 Host 内存保留
107
107
  30 分钟,Host 重启后失效,不把原始企业行持久化落盘。
108
108
 
109
- ### 3.4 三域批量补全(0.5.0 发布候选)
109
+ ### 3.4 三域批量补全(0.5.0
110
110
 
111
111
  0.5.0 在工作台中增加三个可选域:
112
112
 
@@ -151,6 +151,6 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
151
151
  `enterprise-enrichment` Skill(方案 A 模型中介式)。字段契约与二期规划见
152
152
  [QCC-ENRICHMENT-DESIGN.md](QCC-ENRICHMENT-DESIGN.md)。
153
153
  - **Q:可以在后台批量补全吗?** A:可以。0.4.0 G5 工商批量已发布并完成真实 OAuth/token 验收;
154
- 0.5.0 风险/知产/经营三域为发布候选,先用 estimate 核对调用上限,再显式确认付费调用。
154
+ 0.5.0 风险/知产/经营三域已发布,先用 estimate 核对调用上限,再显式确认使用当前用户自己的 QCC 账号额度。
155
155
  - **Q:刷新页面或重启后还能恢复吗?** A:同一 Host 进程内可用 `runId` 恢复,默认保留 30 分钟;
156
156
  Host 重启后内存 run 失效,但已下载 CSV 和本地源文件不受影响。
package/lib/client.js CHANGED
@@ -3,11 +3,13 @@
3
3
  *
4
4
  * 通过 `window.__ModuleLoader__.load({id, factory})` 注册为惰性 CJS 工厂,
5
5
  * 与 dsh-mcp-connector@0.2.32(生产 profile 中已实测的「MCP连接器」)同构:
6
- * - `react` / `@deepseek-ai/dsh-client-ui-primitives` 由 web shell 静态模块表提供,
6
+ * - `react` / `react-dom` / `@deepseek-ai/dsh-client-ui-primitives` 由 web shell 静态模块表提供,
7
7
  * 第三方 bundle 无需打包 React、无需构建步骤。
8
8
  * - `defineStore` 首选 `@deepseek-ai/dsh-client-store`,回退 `@deepseek-ai/dsh-client-runtime/client`。
9
- * - 入口注册到 `sidebar.footer.action`(order 10,排在 MCP连接器的 order 0 下方),
10
- * 工作台注册到 `shell.overlay`(order 200)。
9
+ * - `sidebar.footer.action` 仅托管入口生命周期和降级渲染;实际入口 Portal
10
+ * `sidebar.workspaces` 前,位于「新会话」与「工作区」之间。
11
+ * - DSH 原生会话保持在中间,能力入口使用 `conversation.input.left`,右侧非模态
12
+ * 工作台使用 `shell.overlay`,会话头使用 `conversation.session.header.actions`。
11
13
  *
12
14
  * 0.5.0 工作台复用本地 `/mvp/*` 与三域 `/phase3/*` 后端,完整覆盖上传映射、
13
15
  * 数据体检、匹配核验、补全导出;计费调用仍由 Host Bridge 的确认/幂等/上限门约束。
@@ -20,7 +22,19 @@ window.__ModuleLoader__.load({
20
22
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
21
23
 
22
24
  const react = require('react');
23
- const { Button } = require('@deepseek-ai/dsh-client-ui-primitives');
25
+ const reactDom = require('react-dom');
26
+ const primitives = require('@deepseek-ai/dsh-client-ui-primitives');
27
+ const {
28
+ Button,
29
+ IconArchiveOutline20,
30
+ IconChecklistOutline14,
31
+ IconCloseOutline16,
32
+ IconDataOutline16,
33
+ IconDownloadOutline16,
34
+ IconFullscreenOutline16,
35
+ IconPaperclipOutline16,
36
+ IconSearchOutline16,
37
+ } = primitives;
24
38
 
25
39
  let defineStore;
26
40
  try {
@@ -41,20 +55,45 @@ window.__ModuleLoader__.load({
41
55
  /** 客户端所需服务:与 mcp-connector 对齐(槽位 + 会话/工作区/输入机)。 */
42
56
  const inject = ['slots', 'sessions', 'workspaces', 'conversation'];
43
57
 
44
- const FOOTER_STYLE_ID = 'dsh-data-cleaning-agent-sidebar';
58
+ const UI_STYLE_ID = 'dsh-data-cleaning-agent-ui';
59
+ const SIDEBAR_WORKSPACES_SELECTOR = '[data-slot="sidebar.workspaces"]';
60
+ const TOP_MOUNT_SELECTOR = '[data-data-cleaning-top-mount="true"]';
45
61
  const stylesCss = `
46
- [data-slot="sidebar.footer.action"] {
47
- display: flex !important;
48
- flex-direction: column;
62
+ .dcAgentTopMount {
63
+ flex: none;
49
64
  min-width: 0;
50
65
  width: 100%;
51
66
  }
52
67
 
68
+ .dcAgentTopEntry {
69
+ box-sizing: border-box;
70
+ width: 100%;
71
+ padding-right: var(--dsh-sidebar-inline-padding, 12px);
72
+ }
73
+
53
74
  .dcAgentLauncher {
54
75
  flex: none;
55
76
  box-sizing: border-box;
56
77
  width: 100%;
78
+ height: 42px;
79
+ margin: 0 0 8px;
80
+ padding: 0 10px 0 8px;
81
+ justify-content: flex-start;
57
82
  min-width: 0;
83
+ overflow: hidden;
84
+ border-radius: 12px;
85
+ white-space: nowrap;
86
+ }
87
+ .dcAgentLauncher[data-wide="false"] {
88
+ width: 36px;
89
+ height: 36px;
90
+ padding: 0;
91
+ justify-content: center;
92
+ border-radius: 50%;
93
+ }
94
+ .dcAgentTopEntry[data-wide="false"] {
95
+ width: 36px;
96
+ padding-right: 0;
58
97
  }
59
98
 
60
99
  .dcAgentOverlay {
@@ -64,22 +103,79 @@ window.__ModuleLoader__.load({
64
103
  display: flex;
65
104
  align-items: stretch;
66
105
  justify-content: flex-end;
67
- background: rgba(8, 10, 14, 0.22);
106
+ pointer-events: none;
107
+ background: transparent;
68
108
  }
69
109
 
70
110
  .dcAgentWorkbench {
71
111
  display: flex;
72
112
  flex-direction: column;
73
- width: min(980px, calc(100vw - 72px));
113
+ width: min(510px, calc(100vw - 72px));
74
114
  height: 100vh;
75
115
  border: 1px solid light-dark(#d5dae4, #2a3038);
76
- border-radius: 14px 0 0 14px;
116
+ border-radius: 12px 0 0 12px;
77
117
  background: light-dark(#ffffff, #161b23);
78
118
  color: light-dark(#172033, #edf2fa);
79
- box-shadow: light-dark(0 18px 50px rgba(33, 55, 88, .14), 0 20px 60px rgba(0, 0, 0, .42));
119
+ box-shadow: light-dark(-10px 0 32px rgba(33, 55, 88, .12), -12px 0 40px rgba(0, 0, 0, .32));
80
120
  overflow: hidden;
121
+ pointer-events: auto;
122
+ }
123
+ .dcAgentWorkbench.is-expanded { width: min(980px, calc(100vw - 72px)); }
124
+
125
+ .dcAgentCapabilities {
126
+ display: flex;
127
+ align-items: center;
128
+ gap: 4px;
129
+ max-width: min(56vw, 560px);
130
+ overflow-x: auto;
131
+ scrollbar-width: none;
132
+ transform: translateX(var(--dc-agent-workbench-shift, 0px));
133
+ transition: transform 160ms ease;
134
+ }
135
+ .dcAgentCapabilities::-webkit-scrollbar { display: none; }
136
+ .dcAgentCapability {
137
+ display: inline-flex;
138
+ align-items: center;
139
+ gap: 5px;
140
+ flex: 0 0 auto;
141
+ min-height: 30px;
142
+ padding: 4px 8px;
143
+ border: 1px solid transparent;
144
+ border-radius: 8px;
145
+ background: transparent;
146
+ color: light-dark(#5d687a, #aab5c7);
147
+ font: inherit;
148
+ font-size: 12px;
149
+ cursor: pointer;
150
+ }
151
+ .dcAgentCapability:hover,
152
+ .dcAgentCapability:focus-visible,
153
+ .dcAgentCapability.is-active {
154
+ border-color: light-dark(#c9d9f8, #365681);
155
+ background: light-dark(#edf4ff, #172841);
156
+ color: light-dark(#1556cf, #8cb3ff);
157
+ }
158
+ .dcAgentCapability svg { flex: 0 0 auto; }
159
+
160
+ .dcAgentHeaderAction {
161
+ display: inline-flex;
162
+ align-items: center;
163
+ gap: 6px;
164
+ min-height: 30px;
165
+ padding: 4px 9px;
166
+ border: 1px solid light-dark(#d9dee8, #394352);
167
+ border-radius: 8px;
168
+ background: light-dark(#ffffff, #191f28);
169
+ color: inherit;
170
+ font: inherit;
171
+ font-size: 12px;
172
+ cursor: pointer;
173
+ }
174
+ .dcAgentHeaderAction:hover,
175
+ .dcAgentHeaderAction:focus-visible {
176
+ border-color: light-dark(#2869e6, #6d9eff);
177
+ color: light-dark(#1556cf, #8cb3ff);
81
178
  }
82
- .dcAgentWorkbench.is-expanded { width: calc(100vw - 72px); }
83
179
 
84
180
  .dcAgentWbHeader {
85
181
  display: flex;
@@ -365,17 +461,19 @@ window.__ModuleLoader__.load({
365
461
  }
366
462
  @media (max-width: 760px) {
367
463
  .dcAgentWorkbench, .dcAgentWorkbench.is-expanded { width: 100vw; border-radius: 0; }
464
+ .dcAgentOverlay { background: rgba(8, 10, 14, 0.22); pointer-events: auto; }
368
465
  .dcAgentStepper { grid-template-columns: repeat(2, minmax(0, 1fr)); }
369
466
  .dcAgentQccBadge { display: none; }
370
467
  .dcAgentCandidate { grid-template-columns: 1fr; }
468
+ .dcAgentCapabilityLabel { display: none; }
371
469
  }
372
470
  `;
373
471
 
374
- /** footer 多个入口纵向堆叠(与 mcp-connector 注入的列式 CSS 幂等并存)。 */
375
- function installSidebarStyles() {
376
- if (document.querySelector(`style[data-plugin="${FOOTER_STYLE_ID}"]`) !== null) return () => {};
472
+ /** 安装入口、会话能力按钮与右侧工作台样式。 */
473
+ function installUiStyles() {
474
+ if (document.querySelector(`style[data-plugin="${UI_STYLE_ID}"]`) !== null) return () => {};
377
475
  const style = document.createElement('style');
378
- style.dataset.plugin = FOOTER_STYLE_ID;
476
+ style.dataset.plugin = UI_STYLE_ID;
379
477
  style.textContent = stylesCss;
380
478
  document.head.append(style);
381
479
  return () => { style.remove(); };
@@ -392,6 +490,16 @@ window.__ModuleLoader__.load({
392
490
  { key: 'enrich', label: '补全与导出', icon: '⬇️' },
393
491
  ];
394
492
 
493
+ const CAPABILITIES = [
494
+ { key: 'upload', label: '上传清洗', icon: IconPaperclipOutline16, fallback: '+' },
495
+ { key: 'profile', label: '质量体检', icon: IconChecklistOutline14, fallback: '✓' },
496
+ { key: 'review', label: '匹配核验', icon: IconSearchOutline16, fallback: '⌕' },
497
+ { key: 'enrich', label: '字段补全', icon: IconDataOutline16, fallback: '▦' },
498
+ { key: 'history', label: '任务历史', icon: IconArchiveOutline20, fallback: '◷' },
499
+ ];
500
+
501
+ const DEFAULT_SESSION_PROMPT = '请帮我清洗并补全企业名单。我会在右侧数据清洗工作台上传文件或粘贴数据。';
502
+
395
503
  /** 后端往返:POST JSON,返回解析后的对象。 */
396
504
  async function api(path, body) {
397
505
  const res = await fetch(path, {
@@ -422,13 +530,20 @@ window.__ModuleLoader__.load({
422
530
  qccEstimate: null,
423
531
  qccRun: null,
424
532
  paidConfirmed: false,
533
+ activeSessionId: null,
425
534
  jobs: [], // 后台任务列表(/mvp/jobs 轮询),仅用于状态 pill,不接计费遥测
426
535
  }),
427
536
  actions: {
428
537
  open: (draft) => { draft.open = true; },
538
+ openAt: (draft, step, sessionId) => {
539
+ draft.open = true;
540
+ if (step) draft.step = step;
541
+ if (sessionId) draft.activeSessionId = sessionId;
542
+ },
429
543
  close: (draft) => { draft.open = false; },
430
544
  toggleExpanded: (draft) => { draft.expanded = !draft.expanded; },
431
545
  setStep: (draft, step) => { draft.step = step; },
546
+ setActiveSession: (draft, sessionId) => { draft.activeSessionId = sessionId ?? null; },
432
547
  setBusy: (draft, busy) => { draft.busy = busy; },
433
548
  setError: (draft, error) => { draft.error = error; },
434
549
  setInput: (draft, input) => { draft.input = input; },
@@ -453,27 +568,178 @@ window.__ModuleLoader__.load({
453
568
  });
454
569
  }
455
570
 
456
- /** 左栏入口按钮:使用 DSH Button,与 MCP连接器一致。 */
571
+ /**
572
+ * DSH 当前没有公开的「新会话与工作区之间」list slot。和 MCP连接器一样,
573
+ * footer slot 只托管生命周期;真实入口只依赖稳定 data-slot Portal 到工作区前。
574
+ */
575
+ function ensureTopLauncherMount() {
576
+ const workspaceSlot = document.querySelector(SIDEBAR_WORKSPACES_SELECTOR);
577
+ const parent = workspaceSlot && workspaceSlot.parentElement;
578
+ if (!workspaceSlot || !parent) return null;
579
+ let mount = parent.querySelector(TOP_MOUNT_SELECTOR);
580
+ if (!mount) {
581
+ mount = document.createElement('div');
582
+ mount.dataset.dataCleaningTopMount = 'true';
583
+ mount.className = 'dcAgentTopMount';
584
+ }
585
+ // 不与 MCP连接器各自的 MutationObserver 争抢「紧邻工作区」位置:首次挂载后
586
+ // 保持稳定顺序。MCP连接器会占据紧邻工作区的位置,本入口自然位于它上方。
587
+ if (mount.parentElement !== parent) parent.insertBefore(mount, workspaceSlot);
588
+ return mount;
589
+ }
590
+
591
+ /** 使用 DSH 原生工作区、会话和输入机打开中央对话,不构造第二套聊天界面。 */
592
+ async function startCleaningSession(ctx) {
593
+ const workspace = ctx.workspaces.list.getSnapshot();
594
+ const current = ctx.sessions.list.getSnapshot().current;
595
+ const items = Array.isArray(workspace.items) ? workspace.items : [];
596
+ const currentWorkspaceId = current === undefined
597
+ ? undefined
598
+ : items.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.includes(current))?.workspaceId;
599
+ const targetWorkspaceId = currentWorkspaceId ?? workspace.recentWorkspaceId ?? items[0]?.workspaceId;
600
+ if (targetWorkspaceId === undefined) {
601
+ throw new Error('请先选择一个工作空间,再打开数据清洗智能体');
602
+ }
603
+ // rc.2 暴露 workspaces.connectWorkspace;alpha.2 把同一导航策略迁移到
604
+ // uiWorkspace.connectWorkspace,纯 workspaces controller 不再带该方法。
605
+ // 只在检测到真实方法后调用,避免把 alpha 实验面当成稳定契约。
606
+ const uiWorkspace = typeof ctx.get === 'function' ? ctx.get('uiWorkspace') : ctx.uiWorkspace;
607
+ const connectWorkspace = typeof ctx.workspaces.connectWorkspace === 'function'
608
+ ? ctx.workspaces.connectWorkspace.bind(ctx.workspaces)
609
+ : typeof uiWorkspace?.connectWorkspace === 'function'
610
+ ? uiWorkspace.connectWorkspace.bind(uiWorkspace)
611
+ : null;
612
+ const sessionId = connectWorkspace
613
+ ? await connectWorkspace(targetWorkspaceId)
614
+ : typeof ctx.sessions.create === 'function'
615
+ ? await ctx.sessions.create({ workspaceId: targetWorkspaceId })
616
+ : null;
617
+ if (!sessionId) throw new Error('当前 DSH 版本没有可用的工作区会话创建能力');
618
+ const conversation = typeof ctx.get === 'function' ? ctx.get('conversation') : ctx.conversation;
619
+ if (!conversation) throw new Error('DSH 对话服务尚未就绪,请稍后重试');
620
+ conversation.input.shell(sessionId).setDraft(DEFAULT_SESSION_PROMPT);
621
+ ctx.sessions.open(sessionId);
622
+ return sessionId;
623
+ }
624
+
625
+ function capabilityIcon(item, size = 16) {
626
+ return typeof item.icon === 'function'
627
+ ? h(item.icon, { size, 'aria-hidden': 'true' })
628
+ : h('span', { 'aria-hidden': 'true' }, item.fallback);
629
+ }
630
+
631
+ function openWorkbench(actions, step, sessionId) {
632
+ actions.openAt(step, sessionId);
633
+ startJobsPolling(actions);
634
+ }
635
+
636
+ const WORKBENCH_OPEN_EVENT = 'dsh:data-cleaning-workbench-open';
637
+
638
+ // session scope 不能复用 root scope 的 store handle。DSH 当前会分别物化 root/session
639
+ // 插槽组件,因此以 document 事件跨 scope 通知;闭包引用仅作为无 DOM 测试降级。
640
+ let rootWorkbenchActions = null;
641
+
642
+ /** 从 session scope 的 composer/header 触发 root scope 工作台。 */
643
+ function requestWorkbenchOpen(step, sessionId) {
644
+ if (typeof document !== 'undefined'
645
+ && typeof document.createEvent === 'function'
646
+ && typeof document.dispatchEvent === 'function') {
647
+ const event = document.createEvent('CustomEvent');
648
+ event.initCustomEvent(WORKBENCH_OPEN_EVENT, false, true, { step, sessionId });
649
+ if (!document.dispatchEvent(event)) return;
650
+ }
651
+ if (rootWorkbenchActions) openWorkbench(rootWorkbenchActions, step, sessionId);
652
+ }
653
+
654
+ /** 左栏入口按钮:公开 footer 托管,真实按钮 Portal 到「新会话 / 工作区」之间。 */
457
655
  function SidebarEntry(props) {
458
- const { wide, useStore, actions } = props;
656
+ const { wide, useStore, actions, startSession } = props;
459
657
  const open = useStore((state) => state.open);
460
- return react.createElement(Button, {
658
+ const [topMount, setTopMount] = react.useState(null);
659
+
660
+ react.useEffect(() => {
661
+ let disposed = false;
662
+ const ownedMounts = new Set();
663
+ const syncMount = () => {
664
+ if (disposed) return;
665
+ const mount = ensureTopLauncherMount();
666
+ if (mount) ownedMounts.add(mount);
667
+ setTopMount((current) => current === mount ? current : mount);
668
+ };
669
+ syncMount();
670
+ let observer = null;
671
+ if (typeof window.MutationObserver === 'function' && document.body) {
672
+ observer = new window.MutationObserver(syncMount);
673
+ observer.observe(document.body, { childList: true, subtree: true });
674
+ }
675
+ return () => {
676
+ disposed = true;
677
+ if (observer) observer.disconnect();
678
+ for (const mount of ownedMounts) mount.remove();
679
+ };
680
+ }, []);
681
+
682
+ const launcher = react.createElement(Button, {
461
683
  variant: 'ghost',
462
684
  className: 'dcAgentLauncher',
463
685
  'data-wide': wide,
464
686
  'aria-label': '数据清洗',
465
687
  'aria-haspopup': 'dialog',
466
688
  'aria-expanded': open,
467
- onClick: () => {
689
+ onClick: async () => {
468
690
  try {
469
- actions.open();
470
- startJobsPolling(actions);
691
+ openWorkbench(actions, 'upload');
692
+ if (typeof startSession === 'function') {
693
+ const sessionId = await startSession();
694
+ actions.setActiveSession(sessionId);
695
+ }
471
696
  } catch (error) {
697
+ actions.setError(error instanceof Error ? error.message : String(error));
472
698
  console.error('[dc-agent] open failed:', error);
473
699
  }
474
700
  },
475
701
  children: wide ? '🧹 数据清洗' : '🧹',
476
702
  });
703
+ if (!topMount || typeof reactDom.createPortal !== 'function') return launcher;
704
+ return reactDom.createPortal(h('div', {
705
+ className: 'dcAgentTopEntry',
706
+ 'data-wide': wide,
707
+ }, launcher), topMount);
708
+ }
709
+
710
+ /** Mockup 五能力入口:位于原生 composer 的交互工具行。 */
711
+ function CapabilityBar(props) {
712
+ const { sessionId } = props;
713
+ return h('div', { className: 'dcAgentCapabilities', 'aria-label': '数据清洗智能体能力' },
714
+ CAPABILITIES.map((item) => h('button', {
715
+ key: item.key,
716
+ type: 'button',
717
+ className: 'dcAgentCapability',
718
+ 'aria-label': item.label,
719
+ title: item.label,
720
+ onClick: () => requestWorkbenchOpen(item.key, sessionId),
721
+ },
722
+ capabilityIcon(item),
723
+ h('span', { className: 'dcAgentCapabilityLabel' }, item.label),
724
+ )),
725
+ );
726
+ }
727
+
728
+ /** 会话头部的可恢复入口:关闭右栏后仍可从当前会话再次打开。 */
729
+ function WorkbenchHeaderEntry(props) {
730
+ const { sessionId } = props;
731
+ return h('button', {
732
+ type: 'button',
733
+ className: 'dcAgentHeaderAction',
734
+ 'aria-label': '打开数据清洗工作台',
735
+ title: '打开数据清洗工作台',
736
+ onClick: () => requestWorkbenchOpen(null, sessionId),
737
+ },
738
+ typeof IconDataOutline16 === 'function'
739
+ ? h(IconDataOutline16, { size: 16, 'aria-hidden': 'true' })
740
+ : h('span', { 'aria-hidden': 'true' }, '▦'),
741
+ h('span', null, '清洗工作台'),
742
+ );
477
743
  }
478
744
 
479
745
  /** M3 · 三工具 tool.call.toolview 卡片元数据(wire 名 → 展示文案)。 */
@@ -606,8 +872,8 @@ window.__ModuleLoader__.load({
606
872
  });
607
873
  }
608
874
 
609
- /** 工作台主视图:header + stepper + 当前 pane。 */
610
- function WorkbenchOverlay(props) {
875
+ /** 右侧非模态工作台:中央区域始终保留 DSH 原生会话。 */
876
+ function WorkbenchDrawer(props) {
611
877
  const { useStore, actions } = props;
612
878
  const open = useStore((state) => state.open);
613
879
  const step = useStore((state) => state.step);
@@ -626,6 +892,65 @@ window.__ModuleLoader__.load({
626
892
  const qccRun = useStore((state) => state.qccRun);
627
893
  const paidConfirmed = useStore((state) => state.paidConfirmed);
628
894
  const jobs = useStore((state) => state.jobs);
895
+ const activeSessionId = useStore((state) => state.activeSessionId);
896
+
897
+ react.useEffect(() => {
898
+ rootWorkbenchActions = actions;
899
+ const handleOpen = (event) => {
900
+ event?.preventDefault?.();
901
+ const detail = event?.detail ?? {};
902
+ openWorkbench(actions, detail.step ?? null, detail.sessionId ?? null);
903
+ };
904
+ if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
905
+ document.addEventListener(WORKBENCH_OPEN_EVENT, handleOpen);
906
+ }
907
+ return () => {
908
+ if (typeof document !== 'undefined' && typeof document.removeEventListener === 'function') {
909
+ document.removeEventListener(WORKBENCH_OPEN_EVENT, handleOpen);
910
+ }
911
+ stopJobsPolling();
912
+ if (rootWorkbenchActions === actions) rootWorkbenchActions = null;
913
+ };
914
+ }, [actions]);
915
+
916
+ // 非模态右栏会覆盖 Host 的中央画布;按实际重叠量把 composer 能力栏左移,
917
+ // 保证 Mockup 中的五个入口在窄桌面视口仍可见、可点击。展开态专注工作台,
918
+ // 不强行挤压到不足一栏的剩余区域。
919
+ react.useEffect(() => {
920
+ if (typeof document === 'undefined' || typeof document.querySelectorAll !== 'function') return undefined;
921
+ const reset = () => {
922
+ for (const bar of document.querySelectorAll('.dcAgentCapabilities')) {
923
+ bar.style?.removeProperty('--dc-agent-workbench-shift');
924
+ }
925
+ };
926
+ if (!open || expanded) {
927
+ reset();
928
+ return reset;
929
+ }
930
+ const align = () => {
931
+ const drawer = document.querySelector('.dcAgentWorkbench');
932
+ if (!drawer) return;
933
+ const drawerRect = drawer.getBoundingClientRect();
934
+ for (const bar of document.querySelectorAll('.dcAgentCapabilities')) {
935
+ const barRect = bar.getBoundingClientRect();
936
+ const overlap = Math.max(0, barRect.right - drawerRect.left + 8);
937
+ const leftRoom = Math.max(0, barRect.left - 88);
938
+ const shift = Math.min(overlap, leftRoom);
939
+ bar.style?.setProperty('--dc-agent-workbench-shift', `${-shift}px`);
940
+ }
941
+ };
942
+ const view = document.defaultView;
943
+ const frameId = typeof view?.requestAnimationFrame === 'function'
944
+ ? view.requestAnimationFrame(align)
945
+ : null;
946
+ view?.addEventListener?.('resize', align);
947
+ align();
948
+ return () => {
949
+ if (frameId !== null) view?.cancelAnimationFrame?.(frameId);
950
+ view?.removeEventListener?.('resize', align);
951
+ reset();
952
+ };
953
+ }, [open, expanded]);
629
954
 
630
955
  // DSH 的 useStore 基于 React hooks;所有订阅必须在每次渲染时以相同顺序调用。
631
956
  // 把关闭态 guard 放在全部 useStore 之后,避免首次关闭、随后打开时触发
@@ -885,7 +1210,37 @@ window.__ModuleLoader__.load({
885
1210
  );
886
1211
 
887
1212
  let pane;
888
- if (step === 'profile') {
1213
+ if (step === 'history') {
1214
+ pane = h('div', { className: 'dcAgentPane' },
1215
+ h('p', { className: 'dcAgentHint' }, '查看当前 Host 的数据清洗后台任务。任务数据仅在本机保存,不进入模型上下文。'),
1216
+ h('div', { className: 'dcAgentRow' },
1217
+ h('button', {
1218
+ type: 'button',
1219
+ className: 'dcAgentButton',
1220
+ disabled: busy,
1221
+ 'aria-label': '刷新任务历史',
1222
+ onClick: () => pollJobsOnce(actions),
1223
+ }, '刷新任务'),
1224
+ h('button', {
1225
+ type: 'button',
1226
+ className: 'dcAgentButton is-primary',
1227
+ onClick: () => actions.setStep('upload'),
1228
+ }, '新建清洗任务'),
1229
+ ),
1230
+ jobs.length ? h('div', { className: 'dcAgentPane', 'aria-label': '任务历史列表' },
1231
+ jobs.map((job) => h('section', { key: job.id, className: 'dcAgentSection' },
1232
+ h('h3', null, job.name || job.id || '数据清洗任务'),
1233
+ h('div', { className: 'dcAgentRow' },
1234
+ h('span', { className: 'dcAgentJobsPill', 'data-state': job.state ?? 'idle' }, JOB_STATE_LABEL[job.state] ?? job.state ?? '未知'),
1235
+ h('span', { className: 'dcAgentHint' }, job.createdAt ? new Date(job.createdAt).toLocaleString() : ''),
1236
+ ),
1237
+ )),
1238
+ ) : h('section', { className: 'dcAgentSection' },
1239
+ h('h3', null, '暂无后台任务'),
1240
+ h('p', { className: 'dcAgentHint' }, '上传企业名单并开始处理后,任务状态会显示在这里。'),
1241
+ ),
1242
+ );
1243
+ } else if (step === 'profile') {
889
1244
  pane = h('div', { className: 'dcAgentPane' },
890
1245
  h('p', { className: 'dcAgentHint' }, '基于已解析数据生成本地质量画像(缺失率、去重值、金额分布)。本步骤不发起企查查调用。'),
891
1246
  profile ? h('div', null,
@@ -1048,20 +1403,34 @@ window.__ModuleLoader__.load({
1048
1403
 
1049
1404
  return h('div', {
1050
1405
  className: 'dcAgentOverlay',
1051
- role: 'dialog',
1052
- 'aria-modal': 'true',
1053
- 'aria-label': '数据清洗',
1406
+ 'data-dsh-plugin': 'data-cleaning-agent',
1054
1407
  onClick: (event) => {
1055
- if (event.target === event.currentTarget) actions.close();
1408
+ if (event.target === event.currentTarget) {
1409
+ stopJobsPolling();
1410
+ actions.close();
1411
+ }
1056
1412
  },
1057
1413
  },
1058
- h('div', { className: `dcAgentWorkbench${expanded ? ' is-expanded' : ''}` },
1414
+ h('aside', {
1415
+ className: `dcAgentWorkbench${expanded ? ' is-expanded' : ''}`,
1416
+ role: 'dialog',
1417
+ 'aria-modal': 'false',
1418
+ 'aria-label': '数据清洗工作台',
1419
+ 'data-session-id': activeSessionId ?? undefined,
1420
+ onKeyDown: (event) => {
1421
+ if (event.key === 'Escape') {
1422
+ event.preventDefault();
1423
+ stopJobsPolling();
1424
+ actions.close();
1425
+ }
1426
+ },
1427
+ },
1059
1428
  h('header', { className: 'dcAgentWbHeader' },
1060
1429
  h('div', { className: 'dcAgentWbTitle' },
1061
1430
  h('span', { className: 'dcAgentWbIcon', 'aria-hidden': 'true' }, '🧹'),
1062
1431
  h('div', null,
1063
1432
  h('b', null, '数据清洗补全'),
1064
- h('small', null, '本地确定性清洗 · 企查查三域 Host Bridge'),
1433
+ h('small', null, '本地确定性清洗 · 可选企查查 MCP 补全'),
1065
1434
  ),
1066
1435
  ),
1067
1436
  h('span', {
@@ -1106,28 +1475,51 @@ window.__ModuleLoader__.load({
1106
1475
  function apply(ctx) {
1107
1476
  // eslint-disable-next-line no-console
1108
1477
  console.log('[dc-agent] client apply() ran');
1109
- const state = { applied: true, entry: 'sidebar.footer.action', overlay: 'shell.overlay', error: null };
1478
+ const state = {
1479
+ applied: true,
1480
+ entry: 'sidebar.workspaces:before(portal)',
1481
+ lifecycleSlot: 'sidebar.footer.action',
1482
+ capabilitySlot: 'conversation.input.left',
1483
+ headerSlot: 'conversation.session.header.actions',
1484
+ overlay: 'shell.overlay',
1485
+ error: null,
1486
+ };
1110
1487
  window.__DC_MVP__ = state;
1111
1488
  try {
1112
1489
  const workbenchStore = createWorkbenchStore();
1113
- ctx.effect(() => installSidebarStyles(), 'data-cleaning-agent: sidebar styles');
1490
+ ctx.effect(() => installUiStyles(), 'data-cleaning-agent: UI styles');
1114
1491
 
1115
- // 工作台:注册到 shell.overlay(与 mcp-connector 同源)。
1492
+ // 右侧工作台:additive overlay,不替换 DSH 单占位 details 面板。
1116
1493
  ctx.slots.inject('shell.overlay', () => ctx.slots.register({
1117
1494
  name: 'shell.overlay',
1118
1495
  id: 'data-cleaning-agent',
1119
1496
  order: 200,
1120
1497
  store: workbenchStore,
1121
- }, WorkbenchOverlay));
1498
+ }, WorkbenchDrawer));
1122
1499
 
1123
- // 左栏:注册到 sidebar.footer.action,order 10 排在 MCP连接器(order 0)下方。
1500
+ // 左栏:footer 只托管生命周期和 Portal 降级;实际入口显示在工作区列表前。
1124
1501
  ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({
1125
1502
  name: 'sidebar.footer.action',
1126
1503
  id: 'data-cleaning-agent',
1127
1504
  order: 10,
1128
1505
  store: workbenchStore,
1506
+ inject: () => ({ startSession: () => startCleaningSession(ctx) }),
1129
1507
  }, SidebarEntry));
1130
1508
 
1509
+ // 原生会话输入框工具行:对齐 Mockup 的五个能力 ICON,不创建第二套聊天 UI。
1510
+ ctx.slots.inject('conversation.input.left', () => ctx.slots.register({
1511
+ name: 'conversation.input.left',
1512
+ id: 'data-cleaning-agent-capabilities',
1513
+ order: 110,
1514
+ }, CapabilityBar));
1515
+
1516
+ // 会话头部恢复入口:用户关闭右栏后可随时重新打开当前任务工作台。
1517
+ ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({
1518
+ name: 'conversation.session.header.actions',
1519
+ id: 'data-cleaning-agent-workbench',
1520
+ order: 110,
1521
+ }, WorkbenchHeaderEntry));
1522
+
1131
1523
  // M3:三个工具的 tool.call.toolview 富化卡片(keyed by wire name,替代裸 JSON 摘要)。
1132
1524
  // 说明:分三条独立 inject(而非 generator)——测试 shim 对 inject 回调仅执行一次并 push 其返回值。
1133
1525
  const toolviewKeys = ['data_clean_rows', 'data_complete_rows', 'data_profile'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-data-cleaning-agent",
3
- "version": "0.5.0",
3
+ "version": "0.5.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",
@@ -28,12 +28,14 @@
28
28
  "docs/PHASE3-ACCEPTANCE.md",
29
29
  "docs/RELEASE-0.4.0.md",
30
30
  "docs/RELEASE-0.5.0.md",
31
+ "docs/RELEASE-0.5.1.md",
32
+ "docs/RELEASE-0.5.2.md",
31
33
  "docs/G5-HOST-BRIDGE.md",
32
34
  "docs/G5-E2E-RUNBOOK.md"
33
35
  ],
34
36
  "scripts": {
35
37
  "test": "node --test",
36
- "lint": "node --check lib/index.js && node --check lib/engine.js && node --check lib/tools.js && node --check lib/skill.js && node --check lib/qcc-phase2.js && node --check lib/qcc-phase3.js && node --check lib/qcc-phase3-batch.js && node --check lib/qcc-phase2-acceptance.js && node --check lib/skill-enrich.js && node --check lib/jobs.js && node --check lib/qcc-safety.js && node --check lib/qcc.js && node --check lib/qcc-runs.js && node --check lib/web.js && node --check lib/client.js && node --check scripts/check-market-registration.mjs && node --check scripts/g5-e2e.mjs && node --check scripts/phase3-e2e.mjs && node --check scripts/phase2-acceptance.mjs",
38
+ "lint": "node --check lib/index.js && node --check lib/engine.js && node --check lib/tools.js && node --check lib/skill.js && node --check lib/qcc-phase2.js && node --check lib/qcc-phase3.js && node --check lib/qcc-phase3-batch.js && node --check lib/qcc-phase2-acceptance.js && node --check lib/skill-enrich.js && node --check lib/jobs.js && node --check lib/qcc-safety.js && node --check lib/qcc.js && node --check lib/qcc-runs.js && node --check lib/web.js && node --check lib/client.js && node --check scripts/check-readme-version.mjs && node --check scripts/check-market-registration.mjs && node --check scripts/g5-e2e.mjs && node --check scripts/phase3-e2e.mjs && node --check scripts/phase2-acceptance.mjs",
37
39
  "docs:check": "node scripts/check-readme-version.mjs",
38
40
  "marketing:check": "node scripts/check-marketing.mjs",
39
41
  "verify-pack": "node scripts/verify-pack.mjs",