dsh-data-cleaning-agent 0.8.9 → 0.8.10

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,6 +2,17 @@
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]
6
+
7
+ ## [0.8.10] - 2026-09-07
8
+
9
+ ### Changed
10
+ - 依据共享 DSH-UX-001 v1.3.0 / UX-39,为自有右侧工作台补充左侧连续拖拽分隔条、方向键和 Home/End 调宽、双击复位、拖动取消、手动宽度恢复;中央会话按实际面板宽度同步让位。
11
+ - 按宿主会话区域实际可用宽度约束上下限,空间不足时全屏降级;关闭、切换或卸载恢复自有布局变量并清理监听,不改变 Host 任务、QCC 调用或发送机制。
12
+
13
+ ### Tests
14
+ - 补充实际拖拽、取消、展开转手动、键盘调宽、关闭重开、宿主区域尺寸变化及让位清理回归;发布前通过 214 项检查测试与 10 组隔离 Chromium UI 回归。
15
+
5
16
  ## [0.8.9] - 2026-09-07
6
17
 
7
18
  ### Changed
package/README.en.md CHANGED
@@ -2,9 +2,9 @@
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.9** (stable release)
5
+ > Current source version / 当前源码版本: **0.8.10** (stable release)
6
6
 
7
- This release aligns the workbench stage menu with the tender agent: five equal-width columns in one row, line icons above short labels, and a pale-blue selection with a blue underline. Narrow layouts stay within the panel. All 128 fields, existing workflows, and native DSH input and send behavior are preserved.
7
+ This release adds continuous resizing from the workbench's left edge, keeping the native composer clear. Arrow keys, Home/End, double-click reset, and width restoration on reopening are supported. Constrained layouts use a full-screen drawer. All 128 fields, QCC-blue menus, existing workflows, and native DSH input and send behavior are preserved.
8
8
 
9
9
  [![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)
10
10
  [![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,9 +2,9 @@
2
2
 
3
3
  > 在 DeepSeek Harness 中清洗、补全、画像企业名单数据的智能体插件:本地 CSV/XLSX/JSON 引擎 + 可选企查查(Qichacha/QCC)MCP 企业数据补全,由企查查(Qichacha/QCC)团队发起并维护。
4
4
  >
5
- > 当前源码版本 / Current source version: **0.8.9**(正式版本)
5
+ > 当前源码版本 / Current source version: **0.8.10**(正式版本)
6
6
 
7
- 本版将右侧阶段菜单对齐招投标工作台:单行五个等宽分栏、上图标下短标题,选中项采用浅蓝底和蓝色底线;窄屏不换行、不横向溢出。保留企查查蓝界面、全部 128 个字段和原有工作流,不替换 DSH 原生输入和发送机制。
7
+ 本版支持拖动右侧工作台左边缘连续调宽,中央输入区同步让位;支持方向键微调、Home/End 边界、双击恢复默认和关闭重开保留宽度。空间不足时全屏降级。保留企查查蓝菜单、全部 128 个字段和原有工作流,不替换 DSH 原生输入和发送机制。
8
8
 
9
9
  [![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)
10
10
  [![npm](https://img.shields.io/npm/v/dsh-data-cleaning-agent)](https://www.npmjs.com/package/dsh-data-cleaning-agent)
package/lib/client.js CHANGED
@@ -77,6 +77,123 @@ window.__ModuleLoader__.load({
77
77
  'aria-hidden': true, focusable: false }, h('path', { d: STAGE_ICON_PATHS[kind] }));
78
78
  }
79
79
 
80
+ function workbenchWidthBounds(viewport, available = viewport) {
81
+ const mobile = viewport <= 760 || available < 740;
82
+ return { mobile, min: mobile ? viewport : 320, max: mobile ? viewport : Math.max(320, available - 420) };
83
+ }
84
+
85
+ // 自有抽屉的可逆兼容适配;不改 Better Sidebar 或其他插件的面板偏好。
86
+ function installWorkbenchSizing(drawer, { preferredWidth, expanded, onWidthChange }) {
87
+ const doc = drawer.ownerDocument;
88
+ const view = doc.defaultView;
89
+ const handle = drawer.querySelector('.dcAgentResizeHandle');
90
+ if (!handle || !view) return () => {};
91
+ const reservations = new Map();
92
+ let requested = expanded ? 980 : preferredWidth;
93
+ let current = 0;
94
+ let drag = null;
95
+ const boundsForView = () => workbenchWidthBounds(view.innerWidth,
96
+ doc.querySelector('[data-conversation-scroll]')?.getBoundingClientRect().width || view.innerWidth);
97
+ const reserve = () => {
98
+ const width = boundsForView().mobile ? 0 : drawer.getBoundingClientRect().width;
99
+ for (const el of doc.querySelectorAll('[data-conversation-scroll]')) {
100
+ if (!reservations.has(el)) {
101
+ reservations.set(el, {
102
+ value: el.style.getPropertyValue('--dc-agent-workbench-reserve'),
103
+ priority: el.style.getPropertyPriority('--dc-agent-workbench-reserve'),
104
+ });
105
+ sizeObserver?.observe(el, { box: 'border-box' });
106
+ }
107
+ if (el.style.getPropertyValue('--dc-agent-workbench-reserve') !== `${width}px`) {
108
+ el.style.setProperty('--dc-agent-workbench-reserve', `${width}px`);
109
+ }
110
+ }
111
+ };
112
+ const apply = (value) => {
113
+ const bounds = boundsForView();
114
+ const fallback = view.innerWidth <= 1040 ? Math.min(420, view.innerWidth * .48) : Math.min(460, view.innerWidth * .42);
115
+ current = Math.round(Math.min(bounds.max, Math.max(bounds.min, Number.isFinite(value) ? value : fallback)));
116
+ drawer.dataset.narrow = String(bounds.mobile);
117
+ drawer.style.setProperty('--dc-agent-panel-width', `${current}px`);
118
+ handle.setAttribute('aria-valuemin', String(bounds.min));
119
+ handle.setAttribute('aria-valuemax', String(bounds.max));
120
+ handle.setAttribute('aria-valuenow', String(current));
121
+ handle.setAttribute('aria-valuetext', `${current} 像素`);
122
+ reserve();
123
+ };
124
+ const end = (cancel = false) => {
125
+ if (!drag) return;
126
+ const previous = drag;
127
+ drag = null;
128
+ drawer.classList.remove('is-resizing');
129
+ if (handle.hasPointerCapture?.(previous.id)) handle.releasePointerCapture(previous.id);
130
+ if (cancel) apply(requested);
131
+ else { requested = current; onWidthChange(current); }
132
+ };
133
+ const down = (event) => {
134
+ if (event.button !== 0 || drag || boundsForView().mobile) return;
135
+ event.preventDefault();
136
+ handle.focus();
137
+ drag = { id: event.pointerId, startX: event.clientX, startWidth: current };
138
+ handle.setPointerCapture(event.pointerId);
139
+ drawer.classList.add('is-resizing');
140
+ };
141
+ const move = (event) => {
142
+ if (drag?.id !== event.pointerId) return;
143
+ apply(drag.startWidth + drag.startX - event.clientX);
144
+ };
145
+ const up = (event) => { if (drag?.id === event.pointerId) end(); };
146
+ const cancel = () => end(true);
147
+ const keydown = (event) => {
148
+ if (event.key === 'Escape' && drag) {
149
+ event.preventDefault(); event.stopPropagation(); cancel(); return;
150
+ }
151
+ if (drag || boundsForView().mobile) return;
152
+ const bounds = boundsForView();
153
+ const delta = event.shiftKey ? 48 : 16;
154
+ const value = { ArrowLeft: current + delta, ArrowRight: current - delta, Home: bounds.min, End: bounds.max }[event.key];
155
+ if (value === undefined) return;
156
+ event.preventDefault(); event.stopPropagation();
157
+ requested = value; apply(value); onWidthChange(current);
158
+ };
159
+ const reset = () => { cancel(); requested = null; apply(null); onWidthChange(null); };
160
+ const resize = () => { cancel(); apply(requested); };
161
+ const sizeObserver = typeof view.ResizeObserver === 'function' ? new view.ResizeObserver(resize) : null;
162
+ handle.addEventListener('pointerdown', down);
163
+ handle.addEventListener('pointermove', move);
164
+ handle.addEventListener('pointerup', up);
165
+ handle.addEventListener('pointercancel', cancel);
166
+ handle.addEventListener('lostpointercapture', cancel);
167
+ handle.addEventListener('keydown', keydown);
168
+ handle.addEventListener('dblclick', reset);
169
+ view.addEventListener('resize', resize);
170
+ view.addEventListener('blur', cancel);
171
+ // 会话挂载点可被宿主替换;只对当前存在的滚动区设置自有让位变量。
172
+ const observer = typeof view.MutationObserver === 'function' ? new view.MutationObserver(reserve) : null;
173
+ observer?.observe(doc.body, { childList: true, subtree: true });
174
+ apply(requested);
175
+ return () => {
176
+ cancel();
177
+ observer?.disconnect();
178
+ sizeObserver?.disconnect();
179
+ handle.removeEventListener('pointerdown', down);
180
+ handle.removeEventListener('pointermove', move);
181
+ handle.removeEventListener('pointerup', up);
182
+ handle.removeEventListener('pointercancel', cancel);
183
+ handle.removeEventListener('lostpointercapture', cancel);
184
+ handle.removeEventListener('keydown', keydown);
185
+ handle.removeEventListener('dblclick', reset);
186
+ view.removeEventListener('resize', resize);
187
+ view.removeEventListener('blur', cancel);
188
+ drawer.style.removeProperty('--dc-agent-panel-width');
189
+ delete drawer.dataset.narrow;
190
+ for (const [el, previous] of reservations) {
191
+ if (previous.value) el.style.setProperty('--dc-agent-workbench-reserve', previous.value, previous.priority);
192
+ else el.style.removeProperty('--dc-agent-workbench-reserve');
193
+ }
194
+ };
195
+ }
196
+
80
197
  // 搜索仅改变可见字段,绝不清除已选字段或改变 Host 的字段目录。
81
198
  function visibleCatalogFields(group, query) {
82
199
  const needle = String(query || '').trim().toLowerCase();
@@ -166,12 +283,13 @@ window.__ModuleLoader__.load({
166
283
  }
167
284
 
168
285
  .dcAgentWorkbench {
286
+ position: relative;
169
287
  box-sizing: border-box;
170
288
  display: flex;
171
289
  flex-direction: column;
172
- width: min(460px, 42vw);
290
+ width: var(--dc-agent-panel-width, min(460px, 42vw));
173
291
  max-width: calc(100vw - 420px);
174
- min-width: 360px;
292
+ min-width: 320px;
175
293
  height: 100vh;
176
294
  border: 1px solid var(--dc-border);
177
295
  border-radius: 12px 0 0 12px;
@@ -181,7 +299,14 @@ window.__ModuleLoader__.load({
181
299
  overflow: hidden;
182
300
  pointer-events: auto;
183
301
  }
184
- .dcAgentWorkbench.is-expanded { width: min(980px, calc(100vw - 72px)); }
302
+ .dcAgentWorkbench.is-expanded { width: var(--dc-agent-panel-width, min(980px, calc(100vw - 420px))); }
303
+ .dcAgentResizeHandle { position: absolute; left: 0; top: 0; bottom: 0; width: 8px; z-index: 5; cursor: col-resize; touch-action: none; outline: none; }
304
+ .dcAgentResizeHandle::after { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: transparent; }
305
+ .dcAgentResizeHandle:hover::after, .dcAgentResizeHandle:focus-visible::after, .is-resizing .dcAgentResizeHandle::after { background: var(--dc-action); }
306
+ .dcAgentWorkbench.is-resizing { user-select: none; }
307
+ .dcAgentWorkbench[data-narrow="true"] { width: 100vw; max-width: none; min-width: 0; border-radius: 0; }
308
+ .dcAgentWorkbench[data-narrow="true"] .dcAgentResizeHandle { display: none; }
309
+ .dcAgentOverlay:has(.dcAgentWorkbench[data-narrow="true"]) { background: rgba(8, 10, 14, 0.22); pointer-events: auto; }
185
310
 
186
311
  .dcAgentCapabilities {
187
312
  display: flex;
@@ -847,24 +972,25 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
847
972
  @media (min-width: 1041px) {
848
973
  body:has(.dcAgentWorkbench) [data-conversation-scroll] {
849
974
  box-sizing: border-box;
850
- padding-right: min(460px, 42vw);
975
+ padding-right: var(--dc-agent-workbench-reserve, min(460px, 42vw));
851
976
  }
852
977
  }
853
978
  @media (min-width: 761px) and (max-width: 1040px) {
854
979
  body:has(.dcAgentWorkbench) [data-conversation-scroll] {
855
980
  box-sizing: border-box;
856
- padding-right: min(420px, 48vw);
981
+ padding-right: var(--dc-agent-workbench-reserve, min(420px, 48vw));
857
982
  }
858
983
  }
859
984
  @media (min-width: 761px) {
860
985
  body:has(.dcAgentWorkbench.is-expanded) [data-conversation-scroll] {
861
986
  box-sizing: border-box;
862
- padding-right: min(980px, calc(100vw - 420px));
987
+ padding-right: var(--dc-agent-workbench-reserve, min(980px, calc(100vw - 420px)));
863
988
  }
864
989
  }
865
990
  @media (max-width: 760px) {
866
991
  .dcAgentWorkbench, .dcAgentWorkbench.is-expanded { width: 100vw; max-width: none; min-width: 0; border-radius: 0; }
867
992
  .dcAgentOverlay { background: rgba(8, 10, 14, 0.22); pointer-events: auto; }
993
+ .dcAgentResizeHandle { display: none; }
868
994
  .dcAgentQccBadge, .dcAgentJobsPill { display: none; }
869
995
  .dcAgentCandidate { grid-template-columns: 1fr; }
870
996
  .dcAgentCapabilities { justify-content: flex-start; padding-inline: 12px; }
@@ -877,7 +1003,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
877
1003
  .dcAgentMappingRow > b { display: none; }
878
1004
  }
879
1005
  @media (min-width: 761px) and (max-width: 1040px) {
880
- .dcAgentWorkbench { width: 420px; max-width: 48vw; min-width: 360px; }
1006
+ .dcAgentWorkbench { width: var(--dc-agent-panel-width, 420px); }
881
1007
  .dcAgentQccBadge, .dcAgentJobsPill { display: none; }
882
1008
  }
883
1009
  @media (max-width: 360px) {
@@ -1487,6 +1613,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1487
1613
  init: () => ({
1488
1614
  open: false,
1489
1615
  expanded: false,
1616
+ preferredWidth: null,
1490
1617
  step: 'upload',
1491
1618
  busy: false,
1492
1619
  error: null,
@@ -1521,6 +1648,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1521
1648
  },
1522
1649
  close: (draft) => { draft.open = false; },
1523
1650
  toggleExpanded: (draft) => { draft.expanded = !draft.expanded; },
1651
+ setPreferredWidth: (draft, width) => {
1652
+ draft.preferredWidth = Number.isFinite(width) ? width : null;
1653
+ draft.expanded = false;
1654
+ },
1524
1655
  setStep: (draft, step) => { draft.step = step; },
1525
1656
  setActiveSession: (draft, sessionId) => { draft.activeSessionId = sessionId ?? null; },
1526
1657
  setBusy: (draft, busy) => { draft.busy = busy; },
@@ -2968,6 +3099,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2968
3099
  const open = useStore((state) => state.open);
2969
3100
  const step = useStore((state) => state.step);
2970
3101
  const expanded = useStore((state) => state.expanded);
3102
+ const preferredWidth = useStore((state) => state.preferredWidth);
2971
3103
  const busy = useStore((state) => state.busy);
2972
3104
  const error = useStore((state) => state.error);
2973
3105
  const input = useStore((state) => state.input);
@@ -2993,6 +3125,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2993
3125
  const activeSessionId = useStore((state) => state.activeSessionId);
2994
3126
  const [fieldQuery, setFieldQuery] = react.useState('');
2995
3127
 
3128
+ react.useEffect(() => {
3129
+ if (!open || typeof document === 'undefined') return undefined;
3130
+ const drawer = document.querySelector('.dcAgentWorkbench');
3131
+ if (!drawer) return undefined;
3132
+ return installWorkbenchSizing(drawer, { preferredWidth, expanded, onWidthChange: actions.setPreferredWidth });
3133
+ }, [open, expanded, preferredWidth, actions]);
3134
+
2996
3135
  react.useEffect(() => {
2997
3136
  rootWorkbenchActions = actions;
2998
3137
  const handleOpen = (event) => {
@@ -3104,7 +3243,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3104
3243
  view?.removeEventListener?.('resize', align);
3105
3244
  reset();
3106
3245
  };
3107
- }, [open, expanded]);
3246
+ }, [open, expanded, preferredWidth]);
3108
3247
 
3109
3248
  // DSH 的 useStore 基于 React hooks;所有订阅必须在每次渲染时以相同顺序调用。
3110
3249
  // 把关闭态 guard 放在全部 useStore 之后,避免首次关闭、随后打开时触发
@@ -3979,6 +4118,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3979
4118
  'aria-modal': 'false',
3980
4119
  'aria-label': '数据清洗补全工作台',
3981
4120
  'data-session-id': activeSessionId ?? undefined,
4121
+ id: 'dc-agent-workbench',
3982
4122
  onKeyDown: (event) => {
3983
4123
  if (event.key === 'Escape') {
3984
4124
  event.preventDefault();
@@ -3987,6 +4127,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3987
4127
  }
3988
4128
  },
3989
4129
  },
4130
+ h('div', { className: 'dcAgentResizeHandle', role: 'separator', tabIndex: 0,
4131
+ 'aria-label': '调整工作台宽度', 'aria-orientation': 'vertical', 'aria-controls': 'dc-agent-workbench',
4132
+ title: '拖动调整宽度;左右方向键微调,Home/End 最小/最大,双击恢复默认' }),
3990
4133
  h('header', { className: 'dcAgentWbHeader' },
3991
4134
  h('div', { className: 'dcAgentWbTitle' },
3992
4135
  h('span', { className: 'dcAgentWbIcon', 'aria-hidden': 'true' }, h(DatabaseLogo, { size: 22 })),
@@ -4153,6 +4296,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
4153
4296
  exports.inject = inject;
4154
4297
  // 测试用纯函数,不构成 Host / DSH 稳定 API。
4155
4298
  exports.__testing = {
4299
+ workbenchWidthBounds,
4156
4300
  DatabaseLogo,
4157
4301
  visibleCatalogFields,
4158
4302
  installCapabilityMount,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-data-cleaning-agent",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
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",