dsh-form-fill-agent 0.2.5 → 0.2.7

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/README.md CHANGED
@@ -43,7 +43,7 @@ dsh plugin --profile web add dsh-form-fill-agent@latest
43
43
 
44
44
  ## 使用边界与数据保存
45
45
 
46
- 补充要求为模型可读文本;字段或工作表范围请通过向导/工作台修改,不能仅改文字后假定执行范围已同步。任务 schema 5 可读取 schema 1–5;回退旧版本请使用升级前目录副本或独立任务目录。主包为 0.2.5,共享内核和 Provider 保持 0.2.0。
46
+ 补充要求为模型可读文本;字段或工作表范围请通过向导/工作台修改,不能仅改文字后假定执行范围已同步。任务 schema 5 可读取 schema 1–5;回退旧版本请使用升级前目录副本或独立任务目录。主包为 0.2.7,共享内核和 Provider 保持 0.2.0。
47
47
 
48
48
  支持企业完整登记名称或 18 位信用代码;简称、主体不一致或多候选不会自动猜选。仅有信用代码的表格也可作为查询锚点。连接候选检索工具时可检索简称,仍需人工确认;缺少工具时降级人工输入。字段映射采用确定性规则及人工确认,不让模型编造事实。支持普通内部公式、固定区域下拉、基础条件格式和普通表格对象;复杂 Excel 结构和 Word 暂不支持,保真边界见下方安全说明。
49
49
 
@@ -51,10 +51,12 @@ dsh plugin --profile web add dsh-form-fill-agent@latest
51
51
 
52
52
  显式设置 DSH_HOME 时,任务保存至其 form-fill-tasks 目录,默认 24 小时过期;未设置时使用内存。任务保存输入表格和归一化预览,不持久化原始 MCP 响应,可主动删除任务。模型及 MCP 凭据由宿主管理。
53
53
 
54
- 0.2.5 保存任务 schema 5,兼容读取旧 schema 1–4;回退旧版本须使用升级前任务目录副本或新目录,防止丢失已确认的字段/主体设置。
54
+ 0.2.7 保存任务 schema 5,兼容读取旧 schema 1–4;回退旧版本须使用升级前任务目录副本或新目录,防止丢失已确认的字段/主体设置。
55
55
 
56
56
  ## 文档与反馈
57
57
 
58
+ 调用次数:默认无本地次数上限。可通过 DSH_FORM_FILL_MAX_CALLS 设置正整数上限,0 或未设置表示不限;修改后重启 DSH。这不是企查查余额或积分配置,上游服务限制仍有效。
59
+
58
60
  - [源码与完整说明](https://github.com/duhu2000/dsh-form-fill-agent)
59
61
  - [安全与保真边界](https://github.com/duhu2000/dsh-form-fill-agent/blob/main/docs/security.md)
60
62
  - [双基线原生验收](https://github.com/duhu2000/dsh-form-fill-agent/blob/main/docs/U2-NATIVE-ACCEPTANCE.md)
package/lib/ui.html CHANGED
@@ -16,7 +16,7 @@
16
16
  <details><summary>体验合成模板</summary><p>下面模板只使用合成数据,不查询真实企业。</p><div class="controls" id="samples"><button class="secondary" data-name="客户台账">客户台账</button><button class="secondary" data-name="供应商准入表">供应商准入表</button><button class="secondary" data-name="合同主体信息表">合同主体信息表</button></div></details></div>
17
17
  <section id="preview" hidden>
18
18
  <div class="card" data-pane="rules"><h2>字段与规则</h2><p id="structure"></p><p id="anchor-alert" role="status"></p><p>默认只填空白。可选择工作表、修正表头行及字段;应用设置会清除旧预览和主体确认,请重新核验。</p><label>搜索字段 <input id="mapping-search" type="search" placeholder="按原表字段名称筛选"></label><p class="muted">未识别字段可手动选择;没有数据来源的字段选择“不填写”。规则只保存表头与映射,不含记录。</p><button id="mapping-save">保存字段规则</button><button id="mapping-load">复用字段规则</button><div id="mapping"></div><button id="configure" class="primary">应用字段设置</button><div id="mapping-next" hidden role="status"><p>设置已保存。下一步核验企业主体,确认填写范围后生成指令并发送,查询结果会进入填写预览。</p><button id="mapping-continue" class="primary">下一步:主体核验</button></div></div>
19
- <div class="card" data-pane="identity"><h2>主体核验与查询</h2><p id="connection"></p><p>候选不自动选择。查询返回歧义后,可选择返回候选或填写已核验的完整登记名称/信用代码;原表主体文字不覆盖,选择仅用于后续查询。</p><div id="entities"></div><button id="wizard-open">生成填写指令</button><p class="muted">查询不会自动发送;回填后可在原生对话框修改并发送。</p></div>
19
+ <div class="card" data-pane="identity"><h2>主体核验与查询</h2><p id="connection"></p><p>候选不自动选择。查询返回歧义后,可选择返回候选或填写已核验的完整登记名称/信用代码;原表主体文字不覆盖,选择仅用于后续查询。</p><div id="entities"></div><div id="direct-scope" role="status"></div><label id="direct-ack-label" hidden><input id="direct-ack" type="checkbox">确认仅填写已纳入的原表字段</label><textarea id="direct-command" aria-label="填写指令" readonly hidden></textarea><button id="wizard-open">生成填写指令</button><p class="muted">查询不会自动发送;回填后可在原生对话框修改并发送。</p></div>
20
20
  <div class="card" data-pane="preview"><h2>填写预览</h2><p id="summary"></p><details><summary>逐列填写结果</summary><p class="muted">按单元格统计;整行缺少主体标识的情况另见下方原因分类。</p><div class="scroll"><table><thead><tr><th>原表字段</th><th>映射字段</th><th>预览可填写格数</th><th>未完成项</th></tr></thead><tbody id="field-results"></tbody></table></div></details><p class="muted">每格显示原值、准备填写值和来源。取消勾选后点击“应用选择”,再确认新的预览。</p>
21
21
  <div class="toolbar"><label>筛选单元格 <input id="result-search" type="search" placeholder="位置、字段或值"></label><span id="result-count" class="muted"></span></div>
22
22
  <div class="scroll"><table><thead><tr><th>位置</th><th>字段</th><th>原值</th><th>准备填写</th><th>依据与来源</th><th>填写</th></tr></thead><tbody id="changes"></tbody></table></div><button id="selection">应用选择</button></div>
@@ -95,7 +95,7 @@ function renderWizard(){
95
95
  $('wizard-fields').replaceChildren();
96
96
  const available=new Set(current?.analysis.tables.flatMap(t=>t.mappings.map(m=>m.field))||[]);
97
97
  const fillable=new Set(current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!t.anchors.includes(m.field)).map(m=>m.field))||[]);
98
- for(const field of current?.catalog||[]){
98
+ for(const field of (current?.catalog||[]).filter(f=>available.has(f.key))){
99
99
  const label=document.createElement('label');label.hidden=!field.label.includes($('field-search').value.trim());const input=document.createElement('input');input.type='checkbox';const fixed=field.anchor||available.has(field.key)&&!fillable.has(field.key);input.checked=fixed||wizardFields.has(field.key);input.disabled=fixed||!available.has(field.key);input.dataset.field=field.key;
100
100
  input.onchange=()=>{if(input.checked)wizardFields.add(field.key);else wizardFields.delete(field.key);saveWizard();renderWizard()};
101
101
  label.append(input,document.createTextNode(field.label+(fixed?'(固定)':!available.has(field.key)?'(原表未映射)':'')));$('wizard-fields').append(label);
@@ -124,6 +124,24 @@ async function sendDraft(mode){
124
124
  controls(true);$('wizard-message').textContent='';
125
125
  try{await applyScope();saveWizard();parent.postMessage({type:'ff-draft',taskId:current.id,revision:current.revision,requirements:$('requirements').value,summary:businessSummary(),retryOnly:retryIntent,mode,expectedDraft},location.origin)}catch(e){$('wizard-message').textContent=e.message}finally{controls(false)}
126
126
  }
127
+ function directScope(){
128
+ initWizard();retryIntent=false;
129
+ wizardFields=new Set(current?.selectedFields||current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!t.anchors.includes(m.field)).map(m=>m.field))||[]);
130
+ const review=scopeReview(),signature=mappingVersion();
131
+ if($('direct-ack').dataset.signature!==signature){$('direct-ack').checked=false;$('direct-ack').dataset.signature=signature}
132
+ $('direct-scope').textContent=review.blocked?'缺少可用企业标识,请返回字段设置。':'将填写 '+wizardFields.size+' 个已确认字段。'+(review.omitted.length?'未纳入:'+review.omitted.join('、')+'。':'');
133
+ $('direct-ack-label').hidden=!review.omitted.length;
134
+ return review;
135
+ }
136
+ async function directDraft(){
137
+ if(busy||!current)return;
138
+ const review=directScope();
139
+ if(review.blocked||!wizardFields.size){$('status').textContent='请先完善企业标识和填写字段。';return}
140
+ if(review.omitted.length&&!$('direct-ack').checked){$('status').textContent='请确认本页未纳入的字段,或返回字段设置完善映射。';return}
141
+ $('scope-ack').checked=$('direct-ack').checked;
142
+ if(parent===window){$('direct-command').hidden=false;$('direct-command').value=command();$('direct-command').focus();$('direct-command').select();$('status').textContent='指令已生成,请复制到 DSH 对话框发送。';return}
143
+ $('status').textContent='正在回填到对话框…';await sendDraft();
144
+ }
127
145
  function filterResults(){const query=$('result-search').value.trim().toLowerCase(),rows=[...$('changes').rows];let shown=0;for(const tr of rows){tr.hidden=!!query&&!tr.textContent.toLowerCase().includes(query);if(!tr.hidden)shown++}const selected=rows.filter(tr=>tr.querySelector('input')?.checked).length;$('result-count').textContent=rows.length?'显示 '+shown+' / '+rows.length+' 格 · 已选 '+selected+' 格'+(!shown?' · 无匹配结果':''):'暂无可填写单元格'}
128
146
  function renderSettings(data){
129
147
  $('mapping').replaceChildren();$('entities').replaceChildren();
@@ -206,7 +224,7 @@ function renderSettings(data){
206
224
  }
207
225
  function render(data){
208
226
  $('mapping-next').hidden=true;
209
- current=data;filename=(data.filename||'已填表格.xlsx').replace(/\.xlsx$/i,'');location.hash='task='+data.id;
227
+ current=data;directScope();$('direct-command').hidden=true;filename=(data.filename||'已填表格.xlsx').replace(/\.xlsx$/i,'');location.hash='task='+data.id;
210
228
  if(gridTask!==data.id){gridTask=data.id;gridPageNumber=1;gridColumn=1;$('grid-sheet').replaceChildren();$('grid-query').value=''}$('grid').hidden=false;loadGrid();
211
229
  if(parent!==window)parent.postMessage({type:'ff-task',taskId:data.id},location.origin);
212
230
  $('changes').replaceChildren();$('incomplete').replaceChildren();$('downloads').replaceChildren();
@@ -226,7 +244,7 @@ initWizard();filterResults();syncActions();if($('wizard').open)renderWizard();
226
244
  $('anchor-alert').textContent=missing.length?'有 '+missing.length+' 行缺少可用主体标识。请把实际包含名称的列映射为“企业名称”,或提供非空信用代码;当前不会查询这些行。':'';
227
245
  }
228
246
  async function restore(silent=false){if(busy||!current&& !location.hash)return;const id=new URLSearchParams(location.hash.slice(1)).get('task');if(!id)return;try{const data=await api('/task/'+encodeURIComponent(id));if(!silent||data.revision!==current?.revision){render(data);if(!silent){const target=pendingNavigation||(data.confirmed?'download':'preview');pendingNavigation=undefined;if(target==='history')$('history-tab').click();else navigate(target)}}}catch(e){if(!silent)$('status').textContent=e.message}}
229
- async function preview(buffer,name,demo=false){controls(true);try{const bytes=new Uint8Array(buffer);if(bytes.length>8388608)throw Error('文件超过 8 MiB');let binary='';for(let i=0;i<bytes.length;i+=32768)binary+=String.fromCharCode(...bytes.subarray(i,i+32768));render(await api('/preview',{base64:btoa(binary),filename:name,sessionId,analyzeOnly:!demo}));navigate(demo?'preview':'rules');$('status').textContent='预览已准备好,请检查后确认。'}catch(e){$('status').textContent=e.message;$('wizard-message').textContent=e.message}finally{controls(false)}}
247
+ async function preview(buffer,name,demo=false){controls(true);try{const bytes=new Uint8Array(buffer);if(bytes.length>8388608)throw Error('文件超过 8 MiB');let binary='';for(let i=0;i<bytes.length;i+=32768)binary+=String.fromCharCode(...bytes.subarray(i,i+32768));$('grid').open=true;render(await api('/preview',{base64:btoa(binary),filename:name,sessionId,analyzeOnly:!demo}));navigate(demo?'preview':'rules');$('status').textContent='预览已准备好,请检查后确认。'}catch(e){$('status').textContent=e.message;$('wizard-message').textContent=e.message}finally{controls(false)}}
230
248
  document.querySelectorAll('[data-step]').forEach(b=>b.onclick=()=>navigate(b.dataset.step));
231
249
  $('upload').onclick=async()=>{const f=$('file').files[0];if(!f){$('status').textContent='请先选择 XLSX 文件';return}await preview(await f.arrayBuffer(),f.name)};
232
250
  function readMapping(){const configuration={sheets:[],headers:[],mappings:[]};for(const section of $('mapping').querySelectorAll('fieldset')){if(!section.querySelector('.sheet-enabled').checked)continue;const sheet=section.dataset.sheet;configuration.sheets.push(sheet);configuration.headers.push({sheet,row:Number(section.querySelector('.header-row').value)});for(const field of section.querySelectorAll('[data-column]'))configuration.mappings.push({sheet,column:Number(field.dataset.column),field:field.value||null})}return configuration}
@@ -241,7 +259,7 @@ $('refresh').onclick=()=>restore();
241
259
  $('discard').onclick=async()=>{if(!current||busy)return;controls(true);try{await api('/discard',{id:current.id});current=null;history.replaceState(null,'',location.pathname+location.search);$('preview').hidden=true;$('grid').hidden=true;gridTask=null;navigate('import');$('status').textContent='任务及保存的表格已删除。'}catch(e){$('status').textContent=e.message}finally{controls(false)}};
242
260
  $('history-tab').onclick=async()=>{try{const list=await api('/tasks');$('history-list').replaceChildren();for(const item of list){const div=document.createElement('div');div.className='history-item';const button=document.createElement('button');button.textContent=item.filename+' · '+item.state;button.onclick=()=>{location.hash='task='+item.id;restore()};div.append(button);$('history-list').append(div)}$('history').hidden=false;$('current').hidden=true;$('current-tab').setAttribute('aria-pressed','false');$('history-tab').setAttribute('aria-pressed','true');if(!list.length)$('history-list').textContent='暂无可恢复的任务。'}catch(e){$('status').textContent=e.message}};
243
261
  $('current-tab').onclick=()=>navigate(step);
244
- $('wizard-open').onclick=()=>openWizard();$('wizard-close').onclick=()=>closeWizard();$('wizard').addEventListener('cancel',e=>{e.preventDefault();closeWizard()});
262
+ $('wizard-open').onclick=()=>directDraft();$('wizard-close').onclick=()=>closeWizard();$('wizard').addEventListener('cancel',e=>{e.preventDefault();closeWizard()});
245
263
  document.querySelectorAll('[data-wizard-step]').forEach(b=>b.onclick=()=>{wizardStep=Number(b.dataset.wizardStep);saveWizard();renderWizard()});
246
264
  $('wizard-prev').onclick=()=>{wizardStep=Math.max(0,wizardStep-1);saveWizard();renderWizard()};$('wizard-next').onclick=()=>{wizardStep=Math.min(3,wizardStep+1);saveWizard();renderWizard()};
247
265
  $('wizard-file').onchange=async()=>{const file=$('wizard-file').files[0];if(file){await preview(await file.arrayBuffer(),file.name);renderWizard()}};
package/lib/workflow.js CHANGED
@@ -3,7 +3,12 @@ import { join, resolve, dirname, basename } from 'node:path';
3
3
  import { analyzeDocument, buildFillPlan, executePlan, applyChangeSet, serialize, FillError } from 'form-fill-core';
4
4
  import { createMockProvider, FIELD_CATALOG } from 'qcc-form-fill-provider';
5
5
 
6
- export async function previewBytes(bytes, { provider = createMockProvider(), maxCalls = 100, confirmPaidCalls = false, configuration = {}, selectedFields, signal, onProgress, previousChangeSet, retryOnly } = {}) {
6
+ export function configuredCallLimit(value = process.env.DSH_FORM_FILL_MAX_CALLS) {
7
+ if (value === undefined || value === '' || value === '0') return Infinity;
8
+ if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value))) throw new FillError('CALL_LIMIT_CONFIG', 'DSH_FORM_FILL_MAX_CALLS 必须为非负整数,0 表示不限次数');
9
+ return Number(value);
10
+ }
11
+ export async function previewBytes(bytes, { provider = createMockProvider(), maxCalls = configuredCallLimit(), confirmPaidCalls = false, configuration = {}, selectedFields, signal, onProgress, previousChangeSet, retryOnly } = {}) {
7
12
  if (provider.mode !== 'mock' && (provider.mode !== 'qcc' || confirmPaidCalls !== true)) throw new FillError('REAL_PROVIDER_DISABLED', '真实来源需要调用方明确授权');
8
13
  const { analysis } = analyzeDocument(bytes, FIELD_CATALOG, configuration);
9
14
  if (selectedFields !== undefined) {
@@ -12,6 +17,7 @@ export async function previewBytes(bytes, { provider = createMockProvider(), max
12
17
  analysis.opportunities=analysis.opportunities.filter(o=>selectedFields.includes(o.field));
13
18
  }
14
19
  const plan = buildFillPlan(analysis, provider.capabilities, provider.version);
20
+ if (plan.estimatedCalls > maxCalls) throw new FillError('BUDGET_EXCEEDED', '预计 '+plan.estimatedCalls+' 次调用,超过本地插件上限 '+maxCalls+';这不是企查查余额限制。请调整 DSH_FORM_FILL_MAX_CALLS(0 为不限)并重启 DSH;retry 不绕过此计划检查。');
15
21
  const changeSet = await executePlan(plan, provider, { maxCalls, confirmPaidCalls, signal, onProgress:onProgress?p=>onProgress({...p,analysis,plan}):undefined, previousChangeSet, retryOnly });
16
22
  return { analysis, plan, changeSet };
17
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-form-fill-agent",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "private": false,
5
5
  "description": "AI填表智能体:XLSX blank-cell preview, QCC enrichment and persistent tasks for DSH.",
6
6
  "repository": { "type": "git", "url": "git+https://github.com/duhu2000/dsh-form-fill-agent.git", "directory": "packages/dsh-form-fill-agent" },