dsh-form-fill-agent 0.2.24 → 0.2.26
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 +23 -10
- package/lib/brand.css +3 -0
- package/lib/client.js +2 -2
- package/lib/http.js +3 -0
- package/lib/index.js +3 -0
- package/lib/preflight.js +9 -8
- package/lib/report.js +26 -0
- package/lib/ui.html +7 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AI填表智能体
|
|
2
2
|
|
|
3
|
-
> 0.2.
|
|
3
|
+
> 0.2.26:新增独立 XLSX 任务结果报告与页内预览;侧栏仍为可选依赖。
|
|
4
4
|
|
|
5
5
|
> 统一侧栏适配需要 Better Sidebar `>=0.17.1 <0.19.0`。五个流程入口打开同一 Session Tab;侧栏展开、收起和关闭由宿主管理。缺少依赖时会显示安装升级提示。
|
|
6
6
|
|
|
@@ -8,16 +8,27 @@
|
|
|
8
8
|
|
|
9
9
|
AI填表智能体:支持自动填表、表格填充、表格补全、Excel填表与 Excel回填;使用企查查 MCP 填写 XLSX 空白字段,预览确认后导出新副本。
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
### 基础安装(不需要侧栏)
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
dsh plugin --profile web add dsh-
|
|
15
|
-
dsh plugin --profile web add dsh-form-fill-agent@0.2.24
|
|
14
|
+
dsh plugin --profile web add dsh-form-fill-agent@0.2.26
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
只需 Node.js >=22、完整 DSH 与主包自动安装的内核/Provider。Better Sidebar 和 dsh-context 都是可选项,不会由基础安装强装。
|
|
18
|
+
|
|
19
|
+
无侧栏可使用原生会话和独立 `/form-fill/` 页面:上传 XLSX、确认映射、查看预览、确认并导出副本。点击会话中的工作台按钮会显示提示及“打开独立填表页面”链接,保留原生草稿和已保存任务。独立页面无法自动回填原生草稿,需复制指令到对话框发送。企查查查询仍需用户授权的 MCP 和模型;无侧栏不会授予额外权限。
|
|
20
|
+
|
|
21
|
+
### 可选:启用内嵌工作台
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
先运行 `dsh --version`,按实际宿主在同一 profile 加装:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
# DSH 0.1.2-rc.1
|
|
27
|
+
dsh plugin --profile web add dsh-better-sidebar@0.18.1
|
|
28
|
+
# 仅旧 DSH 0.1.1-rc.2 使用 0.17.1
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
旧宿主配 0.18.1 会缺少 `SessionLogOffset`,新宿主配 0.17.1 会缺少 `settingsNamespace`。侧栏是可选的,但已经安装的错误组合仍会阻断宿主,必须处理。不建议使用无上限的 `@latest`;可接受范围 `>=0.17.1 <0.19.0` 不等于每个版本均已验证。自定义 profile 请替换 `web`,安装后重启对应 profile。
|
|
21
32
|
|
|
22
33
|
### 升级前检查与回滚
|
|
23
34
|
|
|
@@ -26,10 +37,10 @@ dsh plugin --profile web add dsh-form-fill-agent@0.2.24
|
|
|
26
37
|
0.2.24 新增只读预检工具,在包安装目录运行:
|
|
27
38
|
|
|
28
39
|
```sh
|
|
29
|
-
node /path/to/dsh-form-fill-agent/lib/preflight.js --dsh-bin /path/to/dsh --profile-dir /path/to/profiles/web --
|
|
40
|
+
node /path/to/dsh-form-fill-agent/lib/preflight.js --dsh-bin /path/to/dsh --profile-dir /path/to/profiles/web --mode basic
|
|
30
41
|
```
|
|
31
42
|
|
|
32
|
-
`--sidebar-version` 是计划安装版本;省略则检查该 profile 已安装版本。若计划同时更新已有 context,可加 `--context-version 0.48.0`。预检读取版本元数据,不加载插件树、不读凭据、不安装或重启;已知不兼容退出码为 2,未知组合明确显示未验证。通过只代表版本组合,不代表 MCP 权限、费用或业务结果。
|
|
43
|
+
默认 `--mode basic` 允许缺少侧栏;启用内嵌工作台用 `--mode workbench --sidebar-version 0.18.1`。工作台模式会检查版本,运行时还会检查服务能力。已有不兼容侧栏在两种模式下都阻断。`--sidebar-version` 是计划安装版本;省略则检查该 profile 已安装版本。若计划同时更新已有 context,可加 `--context-version 0.48.0`。预检读取版本元数据,不加载插件树、不读凭据、不安装或重启;已知不兼容退出码为 2,未知组合明确显示未验证。通过只代表版本组合,不代表 MCP 权限、费用或业务结果。
|
|
33
44
|
|
|
34
45
|
升级前记录 `dsh --version` 和该 profile 的精确依赖版本,停止该 profile 并备份其配置及 form-fill-tasks。由宿主管理者升级完整 DSH,不能仅替换某个 SDK 包;如用 npm 管理全局 DSH,新组合的明确命令为 `npm install -g @deepseek-ai/dsh@0.1.2-rc.1`。随后按上文选择侧栏版本;仅当原本安装 context 时才处理它,例如 `dsh plugin --profile web add dsh-context@0.48.0`。
|
|
35
46
|
|
|
@@ -71,7 +82,7 @@ dsh plugin --profile web add dsh-form-fill-agent@latest
|
|
|
71
82
|
2. 首次分析仅识别表格结构、主体锚点和空白字段,不发起企查查查询。
|
|
72
83
|
3. 点击“提示词生成”,依次核对数据来源、填写规则、填写字段和任务描述。回填时可替换或追加已有草稿;随后手动发送,由模型调用 form_fill_enrich 查询。
|
|
73
84
|
4. 在“填写预览”检查各单元格的事实值、来源及未完成项,可排除不需要填写的单元格。
|
|
74
|
-
5. 确认后下载新 XLSX
|
|
85
|
+
5. 确认后下载新 XLSX 副本和独立 XLSX 任务结果报告。原文件不覆盖,已有内容默认保留。
|
|
75
86
|
|
|
76
87
|
同源独立入口为 /form-fill/。工作台附带客户台账、供应商准入表和合同主体信息表三套合成演示,不使用真实客户数据。
|
|
77
88
|
|
|
@@ -91,7 +102,7 @@ dsh plugin --profile web add dsh-form-fill-agent@latest
|
|
|
91
102
|
|
|
92
103
|
## 使用边界与数据保存
|
|
93
104
|
|
|
94
|
-
补充要求为模型可读文本;字段或工作表范围请通过向导/工作台修改,不能仅改文字后假定执行范围已同步。任务 schema 5 可读取 schema 1–5;回退旧版本请使用升级前目录副本或独立任务目录。主包为 0.2.
|
|
105
|
+
补充要求为模型可读文本;字段或工作表范围请通过向导/工作台修改,不能仅改文字后假定执行范围已同步。任务 schema 5 可读取 schema 1–5;回退旧版本请使用升级前目录副本或独立任务目录。主包为 0.2.26,共享内核为 0.2.10,Provider 为 0.2.2,共享字段契约为 0.1.0。
|
|
95
106
|
|
|
96
107
|
支持企业完整登记名称或 18 位信用代码;简称、主体不一致或多候选不会自动猜选。仅有信用代码的表格也可作为查询锚点。连接候选检索工具时可检索简称,仍需人工确认;缺少工具时降级人工输入。字段映射采用确定性规则及人工确认,不让模型编造事实。支持普通内部公式、固定区域下拉、基础条件格式和普通表格对象;复杂 Excel 结构和 Word 暂不支持,保真边界见下方安全说明。
|
|
97
108
|
|
|
@@ -112,3 +123,5 @@ dsh plugin --profile web add dsh-form-fill-agent@latest
|
|
|
112
123
|
- [问题反馈](https://github.com/duhu2000/dsh-form-fill-agent/issues)
|
|
113
124
|
|
|
114
125
|
MIT License.
|
|
126
|
+
|
|
127
|
+
任务结果报告与客户副本分开下载,采用中文状态和已记录的来源。新查询保留实际 MCP server/工具;旧记录缺少 server 时明确提示,不补造。原 JSON 制品接口继续兼容,页面默认提供 XLSX。
|
package/lib/brand.css
CHANGED
|
@@ -71,3 +71,6 @@
|
|
|
71
71
|
.ff-ui .ff-tab-head h2{font-size:16px;margin:0}
|
|
72
72
|
.ff-ui.ff-tab-content iframe{width:100%;border:0;flex:1;min-height:0}
|
|
73
73
|
.ff-ui .ff-sidebar-help{color:var(--error);padding:8px 12px;overflow-wrap:anywhere}
|
|
74
|
+
|
|
75
|
+
/* Spreadsheet previews: compact rows, stationary location column. */
|
|
76
|
+
.ff-ui .scroll td{max-width:420px;overflow:hidden;text-overflow:ellipsis;padding:8px 12px}.ff-ui .scroll th:first-child,.ff-ui .scroll td:first-child{position:sticky;left:0;background:var(--head);z-index:2}.ff-ui .scroll th:first-child{z-index:3}.ff-ui #downloads{display:block}.ff-ui #downloads>.toolbar{justify-content:flex-start;margin:12px 0}
|
package/lib/client.js
CHANGED
|
@@ -2,7 +2,7 @@ window.__ModuleLoader__.load({
|
|
|
2
2
|
id: 'dsh-form-fill-agent',
|
|
3
3
|
factory(require) {
|
|
4
4
|
const SIDEBAR_TAB='dsh-form-fill-agent:workbench';
|
|
5
|
-
const SIDEBAR_HELP='
|
|
5
|
+
const SIDEBAR_HELP='内嵌工作台暂不可用。原生对话与独立填表页面仍可使用,输入和已保存任务不会清除。启用内嵌工作台需与宿主匹配的 Better Sidebar(>=0.17.1 <0.19.0),请按安装说明选择版本。';
|
|
6
6
|
// The only adapter allowed to reveal host panels. No geometry or business writes.
|
|
7
7
|
function createSidebarAdapter(service,component,icon){
|
|
8
8
|
const version=/^0\.(17|18)\.(\d+)$/.exec(service?.version||'');
|
|
@@ -128,7 +128,7 @@ window.__ModuleLoader__.load({
|
|
|
128
128
|
if(!owned(sessionId))return null;
|
|
129
129
|
const content=h('div',{className:'ff-ui','data-ff-theme':theme()},h('nav',{className:'ff-shortcuts','aria-label':'AI填表快捷菜单'},...[
|
|
130
130
|
['导入表格','import','upload'],['字段设置','rules','check'],['主体核验','identity','search'],['填写预览','preview','table'],['任务历史','history','history']
|
|
131
|
-
].map(([label,step,symbol])=>h('button',{key:step,type:'button',onClick:()=>openPanel(sessionId,step)},icon(symbol),h('span',{className:'ff-shortcut-label'},label)))),error?h('
|
|
131
|
+
].map(([label,step,symbol])=>h('button',{key:step,type:'button',onClick:()=>openPanel(sessionId,step)},icon(symbol),h('span',{className:'ff-shortcut-label'},label)))),error?h('div',{className:'ff-sidebar-help',role:'alert'},h('p',null,error),h('a',{href:'/form-fill/?session='+encodeURIComponent(sessionId)+(sessionTasks.has(sessionId)?'#task='+sessionTasks.get(sessionId):''),target:'_blank',rel:'noopener noreferrer'},'打开独立填表页面'),h('p',null,'独立页面生成的指令请复制到对话框后发送。')):null);
|
|
132
132
|
return h('div',{ref,'data-form-fill-session':sessionId},h(Hero,{sessionId}),mount?portal(content,mount):content);
|
|
133
133
|
}
|
|
134
134
|
function WorkbenchTab({scope,store,tab,visible}){
|
package/lib/http.js
CHANGED
|
@@ -5,6 +5,7 @@ import { FIELD_CATALOG, QCC_FIELD_CATALOG, ACTUAL_CONTROLLER_GROUP, SNAPSHOT_GRO
|
|
|
5
5
|
import { previewBytes } from './workflow.js';
|
|
6
6
|
import { createTaskStore } from './task-store.js';
|
|
7
7
|
import { allCandidates, selectCandidates } from './task-model.js';
|
|
8
|
+
import {reportWorkbook, reportRows} from './report.js';
|
|
8
9
|
import { gridPage } from './grid.js';
|
|
9
10
|
import { diagnostics } from './diagnostics.js';
|
|
10
11
|
import { taskPresentation, isResultExplanation } from './task-presentation.js';
|
|
@@ -79,6 +80,8 @@ export function createFormFillHandler({ basePath = '', getPort, now = Date.now,
|
|
|
79
80
|
if (request.method === 'GET' && path.startsWith('/download/')) {
|
|
80
81
|
const parts = path.split('/'), [, , id, kind] = parts, task = tasks.get(id);
|
|
81
82
|
if (parts.length !== 4 || !visible(task, owner) || !task?.result) return send(404, { message: '请先确认写回,或预览已过期' });
|
|
83
|
+
if(kind==='report')return send(200,reportWorkbook(task),XLSX_TYPE);
|
|
84
|
+
if(kind==='report-preview')return send(200,{rows:reportRows(task)});
|
|
82
85
|
if (kind === 'xlsx') return send(200, task.result.bytes, XLSX_TYPE);
|
|
83
86
|
if (kind === 'changes') return send(200, { kind: 'WritebackReport', changeSet: task.preview.changeSet, appliedChanges: task.result.changes, outputChecksum: task.result.checksum });
|
|
84
87
|
if (kind === 'incomplete') return send(200, task.result.incomplete);
|
package/lib/index.js
CHANGED
|
@@ -24,14 +24,17 @@ export function apply(ctx, config = {}) {
|
|
|
24
24
|
},
|
|
25
25
|
async execute(args, execution) {
|
|
26
26
|
if (!execution?.agent || !execution?.token) throw Error('需要 Agent-owned 工具执行上下文');
|
|
27
|
+
const runtimeSources=new Map();
|
|
27
28
|
const provider = createCatalogProvider({ availableTools:Object.keys(CATALOG_TOOL_DOMAINS).filter(name=>runtimeToolNames(name).some(n=>tools.get(n))), enableEntitySearch: ['mcp__qcc-company__','mcp__company__','mcp__qcc_company__'].some(p=>tools.get(p+ENTITY_TOOL)), callTool: async (name, arguments_, { signal }) => {
|
|
28
29
|
if (!Object.hasOwn(CATALOG_TOOL_DOMAINS,name)) throw Error('不支持的 QCC 工具');
|
|
29
30
|
const names = runtimeToolNames(name);
|
|
30
31
|
const selected = names.find(candidate => tools.get(candidate));
|
|
31
32
|
if (!selected) throw Error('请先连接企查查企业数据 MCP');
|
|
33
|
+
runtimeSources.set(name,selected);
|
|
32
34
|
const result = await tools.execute({ name: selected, arguments: arguments_, signal, callId: randomUUID(), rootCallId: execution.rootCallId, parent: execution.token, agent: execution.agent });
|
|
33
35
|
return result?.isError ? { isError: true } : result?.value;
|
|
34
36
|
} });
|
|
37
|
+
const lookup=provider.lookup.bind(provider);provider.lookup=async(...input)=>{const result=await lookup(...input);for(const value of Object.values(result.values||{})){const match=/^qcc:\/\/([^/]+)\//.exec(value.source||'');if(match&&runtimeSources.has(match[1]))value.source=value.source.replace('qcc://'+match[1]+'/', 'qcc://'+runtimeSources.get(match[1])+'/')}return result};
|
|
35
38
|
return service.enrich(args.taskId, provider, args.expectedRevision,{retryOnly:args.mode==='retry'});
|
|
36
39
|
},
|
|
37
40
|
});
|
package/lib/preflight.js
CHANGED
|
@@ -6,24 +6,25 @@ import {join, resolve} from 'node:path';
|
|
|
6
6
|
import {fileURLToPath} from 'node:url';
|
|
7
7
|
import {execFileSync} from 'node:child_process';
|
|
8
8
|
|
|
9
|
-
export function assessCompatibility({nodeVersion,hostVersion,sidebarVersion,contextVersion}) {
|
|
9
|
+
export function assessCompatibility({nodeVersion,hostVersion,sidebarVersion,contextVersion,mode='basic'}) {
|
|
10
|
+
if(!['basic','workbench'].includes(mode))throw Error('mode 仅支持 basic 或 workbench');
|
|
10
11
|
const issues=[];
|
|
11
12
|
const add=(severity,code,message)=>issues.push({severity,code,message});
|
|
12
13
|
if(Number(String(nodeVersion).replace(/^v/,'').split('.')[0])<22)add('blocked','NODE_TOO_OLD','AI 填表需要 Node.js >=22;请先更新运行该 DSH 的 Node.js。');
|
|
13
14
|
if(!hostVersion)add('blocked','HOST_UNKNOWN','无法读取实际 DSH 版本;请用 --dsh-bin 指定实际 dsh 可执行文件。');
|
|
14
|
-
if(!sidebarVersion)add('blocked','SIDEBAR_MISSING','
|
|
15
|
+
if(!sidebarVersion)add(mode==='workbench'?'blocked':'info','SIDEBAR_MISSING','未安装侧栏:基础会话与独立 /form-fill/ 页面可用;内嵌工作台及自动草稿回填需安装与宿主匹配的 Better Sidebar。');
|
|
15
16
|
if(sidebarVersion&&!/^0\.(?:17|18)\.\d+$/.test(sidebarVersion)||sidebarVersion==='0.17.0')add('blocked','SIDEBAR_RANGE','侧栏必须满足 >=0.17.1 <0.19.0;请使用已验证的固定版本,不要直接使用 latest。');
|
|
16
17
|
if(hostVersion==='0.1.1-rc.2'&&/^0\.18\./.test(sidebarVersion||''))add('blocked','HOST_SIDEBAR','旧 DSH 缺少 SessionLogOffset,不能加载该侧栏。保留 dsh-better-sidebar@0.17.1,或由宿主管理者升级完整 DSH 到 0.1.2-rc.1 后安装 0.18.1;不要只升级单个 SDK 包。');
|
|
17
18
|
if(hostVersion==='0.1.2-rc.1'&&sidebarVersion==='0.17.1')add('blocked','SIDEBAR_LEGACY','Better Sidebar 0.17.1 使用旧 settingsNamespace,不能加载到 DSH 0.1.2-rc.1。请在同一 profile 安装 dsh-better-sidebar@0.18.1。');
|
|
18
19
|
if(hostVersion==='0.1.2-rc.1'&&contextVersion==='0.36.0')add('blocked','CONTEXT_LEGACY','已安装 dsh-context@0.36.0 使用旧 settingsNamespace,会阻断新宿主。升级前备份 profile,将已安装的 context 升级到 0.48.0;例如 dsh plugin --profile <名称> add dsh-context@0.48.0。context 不是 AI 填表必装项。');
|
|
19
20
|
if(contextVersion&&!(hostVersion==='0.1.2-rc.1'&&['0.48.0','0.36.0'].includes(contextVersion)))add('warning','CONTEXT_UNVERIFIED','此宿主与 context 组合未纳入当前共存验证;不要据此推断兼容。');
|
|
20
|
-
const known=hostVersion==='0.1.1-rc.2'&&sidebarVersion==='0.17.1'||hostVersion==='0.1.2-rc.1'&&sidebarVersion==='0.18.1';
|
|
21
|
+
const known=(!sidebarVersion&&['0.1.1-rc.2','0.1.2-rc.1'].includes(hostVersion))||hostVersion==='0.1.1-rc.2'&&sidebarVersion==='0.17.1'||hostVersion==='0.1.2-rc.1'&&sidebarVersion==='0.18.1';
|
|
21
22
|
if(!known)add('warning','COMBINATION_UNVERIFIED','此宿主与侧栏组合未验证;版本范围可接受不等于已通过业务验收。');
|
|
22
23
|
const blocked=issues.some(i=>i.severity==='blocked');
|
|
23
|
-
return {status:blocked?'blocked':issues.
|
|
24
|
+
return {status:blocked?'blocked':issues.some(i=>i.severity==='warning')?'unverified':'verified-combination',mode,nodeVersion,hostVersion,sidebarVersion:sidebarVersion||null,contextVersion:contextVersion||null,issues,scope:'版本预检;不代表模型、MCP 权限或全部业务验收通过。不会自动安装、升级、重启。'};
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
export async function inspectInstallation({dshBin,profileDir,sidebarVersion,contextVersion}) {
|
|
27
|
+
export async function inspectInstallation({dshBin,profileDir,sidebarVersion,contextVersion,mode='basic'}) {
|
|
27
28
|
if(!(await stat(profileDir)).isDirectory())throw Error('指定 profile 路径不是目录,无法核对已安装插件。');
|
|
28
29
|
const installed=async name=>{try{return JSON.parse(await readFile(join(profileDir,'node_modules',name,'package.json'),'utf8')).version}catch(e){if(e.code==='ENOENT')return undefined;throw e}};
|
|
29
30
|
const installedSidebar=await installed('dsh-better-sidebar'),installedContext=await installed('dsh-context');
|
|
@@ -31,15 +32,15 @@ export async function inspectInstallation({dshBin,profileDir,sidebarVersion,cont
|
|
|
31
32
|
const isolatedHome=await mkdtemp(join(tmpdir(),'form-fill-preflight-'));let raw;
|
|
32
33
|
try{raw=execFileSync(dshBin.endsWith('.js')?process.execPath:dshBin,[...(dshBin.endsWith('.js')?[dshBin]:[]),'--version'],{cwd:isolatedHome,encoding:'utf8',timeout:15000,env:{PATH:process.env.PATH,HOME:isolatedHome,DSH_HOME:isolatedHome,NO_COLOR:'1'}}).trim()}finally{await rm(isolatedHome,{recursive:true,force:true})}
|
|
33
34
|
const hostVersion=raw.match(/\b\d+\.\d+\.\d+(?:-[\w.]+)?\b/)?.[0];
|
|
34
|
-
return {...assessCompatibility({nodeVersion:process.version,hostVersion,sidebarVersion:sidebarVersion||installedSidebar,contextVersion:contextVersion||installedContext}),installed:{sidebar:installedSidebar||null,context:installedContext||null},proposed:{sidebar:sidebarVersion||null,context:contextVersion||null}};
|
|
35
|
+
return {...assessCompatibility({nodeVersion:process.version,hostVersion,sidebarVersion:sidebarVersion||installedSidebar,contextVersion:contextVersion||installedContext,mode}),installed:{sidebar:installedSidebar||null,context:installedContext||null},proposed:{sidebar:sidebarVersion||null,context:contextVersion||null}};
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
if(process.argv[1]&&await realpath(process.argv[1]).catch(()=>resolve(process.argv[1]))===fileURLToPath(import.meta.url)) {
|
|
38
39
|
try {
|
|
39
40
|
const args=process.argv.slice(2),options={};
|
|
40
|
-
if(args.includes('--help')){console.log('form-fill-preflight --dsh-bin /absolute/path/to/dsh --profile-dir /path/to/profiles/web [--sidebar-version 0.18.1] [--context-version 0.48.0]\n只读当前版本;可预检计划安装版本。阻断退出码 2,未验证组合会显式提示。');}
|
|
41
|
+
if(args.includes('--help')){console.log('form-fill-preflight --dsh-bin /absolute/path/to/dsh --profile-dir /path/to/profiles/web [--mode basic|workbench] [--sidebar-version 0.18.1] [--context-version 0.48.0]\n只读当前版本;可预检计划安装版本。阻断退出码 2,未验证组合会显式提示。');}
|
|
41
42
|
else {
|
|
42
|
-
const keys={'--dsh-bin':'dshBin','--profile-dir':'profileDir','--sidebar-version':'sidebarVersion','--context-version':'contextVersion'};
|
|
43
|
+
const keys={'--mode':'mode','--dsh-bin':'dshBin','--profile-dir':'profileDir','--sidebar-version':'sidebarVersion','--context-version':'contextVersion'};
|
|
43
44
|
for(let i=0;i<args.length;i+=2){if(!keys[args[i]]||!args[i+1]||args[i+1].startsWith('--'))throw Error('无效参数;使用 --help 查看用法');options[keys[args[i]]]=args[i+1]}
|
|
44
45
|
if(!options.dshBin||!options.profileDir)throw Error('必须指定 --dsh-bin 和 --profile-dir;不默认读取生产 profile。');
|
|
45
46
|
const result=await inspectInstallation(options);console.log(JSON.stringify(result,null,2));if(result.status==='blocked')process.exitCode=2;
|
package/lib/report.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {writeZip} from 'form-fill-core/zip';
|
|
2
|
+
import {REASON_LABELS} from './diagnostics.js';
|
|
3
|
+
import {isResultExplanation} from './task-presentation.js';
|
|
4
|
+
const escape=value=>String(value??'').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g,'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
5
|
+
export function sourceLabel(source){
|
|
6
|
+
if(String(source).startsWith('mock://'))return '合成示例(非真实查询)';
|
|
7
|
+
const match=/^qcc:\/\/(mcp__([^/]+?)__([^/]+))\/(.*)$/.exec(source||'');
|
|
8
|
+
if(match)return `企查查 MCP · ${match[2]} · ${match[3]} · ${match[4]}`;
|
|
9
|
+
if(String(source).startsWith('qcc://'))return '企查查 · '+source.slice(6)+'(历史记录未保存 MCP server)';
|
|
10
|
+
return '未记录来源';
|
|
11
|
+
}
|
|
12
|
+
export function reportRows(task){return [
|
|
13
|
+
...task.result.changes.map(c=>[c.sheet,c.cell,c.label,'已填写',c.oldValue??'',c.newValue??'',sourceLabel(c.source),c.acquiredAt||'','']),
|
|
14
|
+
...task.result.incomplete.map(i=>[i.sheet,i.cell||`第 ${i.row} 行`,i.label||'',isResultExplanation(i)?'保留原值 / 无数据':'待核验 / 失败','','','', '',REASON_LABELS[i.reason]||'需要人工核验'])
|
|
15
|
+
]}
|
|
16
|
+
export function reportWorkbook(task){
|
|
17
|
+
const rows=[['工作表','位置','原表字段','结果状态','原值','填写值','数据来源','获取时间','结果说明'],...reportRows(task)];
|
|
18
|
+
const sheet='<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetViews><sheetView workbookViewId="0"><pane ySplit="1" topLeftCell="A2" state="frozen"/></sheetView></sheetViews><sheetData>'+rows.map((r,n)=>'<row r="'+(n+1)+'">'+r.map((v,c)=>'<c r="'+String.fromCharCode(65+c)+(n+1)+'" t="inlineStr"><is><t xml:space="preserve">'+escape(v)+'</t></is></c>').join('')+'</row>').join('')+'</sheetData></worksheet>';
|
|
19
|
+
return writeZip(new Map(Object.entries({
|
|
20
|
+
'[Content_Types].xml':'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/></Types>',
|
|
21
|
+
'_rels/.rels':'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>',
|
|
22
|
+
'xl/workbook.xml':'<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="任务结果报告" sheetId="1" r:id="rId1"/></sheets></workbook>',
|
|
23
|
+
'xl/_rels/workbook.xml.rels':'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/></Relationships>',
|
|
24
|
+
'xl/worksheets/sheet1.xml':sheet
|
|
25
|
+
}).map(([name,value])=>[name,Buffer.from(value)])));
|
|
26
|
+
}
|
package/lib/ui.html
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<div class="toolbar"><label>筛选单元格 <input id="result-search" type="search" placeholder="位置、字段或值"></label><span id="result-count" class="muted"></span></div>
|
|
24
24
|
<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>
|
|
25
25
|
<div class="card" data-pane="preview"><h2>待核验与失败项</h2><p id="reason-summary"></p><div class="scroll"><table><thead><tr><th>位置</th><th>字段</th><th>原因</th></tr></thead><tbody id="incomplete"></tbody></table></div></div>
|
|
26
|
-
<div class="card" data-pane="download"><h2>确认并下载</h2><p>原文件不会被覆盖。下载包含全部已确认填写及完整清单,不受当前筛选影响。</p><button id="confirm" class="primary">确认这些填写,生成新副本</button><div id="downloads"></div></div>
|
|
26
|
+
<div class="card" data-pane="download"><h2>确认并下载</h2><p>原文件不会被覆盖。下载包含全部已确认填写及完整清单,不受当前筛选影响。</p><button id="confirm" class="primary">确认这些填写,生成新副本</button><div id="downloads"></div><section id="report-preview" hidden><h3>任务结果报告</h3><div class="scroll"><table><thead><tr><th>工作表</th><th>位置</th><th>原表字段</th><th>结果状态</th><th>原值</th><th>填写值</th><th>数据来源</th><th>获取时间</th><th>结果说明</th></tr></thead><tbody id="report-body"></tbody></table></div></section></div>
|
|
27
27
|
<details class="ff-task-more"><summary>更多任务操作</summary><div class="controls"><button id="refresh" class="secondary">刷新任务</button><button id="discard" class="secondary">删除任务</button></div></details><div class="ff-next-action" data-pane="preview" hidden><span>核验填充值后继续,下一步生成副本。</span><button id="preview-continue" class="primary">下一步:确认下载</button></div>
|
|
28
28
|
</section></main>
|
|
29
29
|
<dialog id="wizard" aria-labelledby="wizard-title"><header class="dialog-head"><div><h2 id="wizard-title">生成 AI填表任务</h2><p class="muted">上传原表、确认填写范围,使用企查查 MCP 核验企业信息并填写空白单元格。</p></div><button id="wizard-close" aria-label="关闭提示词向导">×</button></header>
|
|
@@ -54,7 +54,7 @@ async function loadGrid(){
|
|
|
54
54
|
const data=await api('/grid/'+id+'?'+p);if(request!==gridRequest||id!==current?.id)return;
|
|
55
55
|
$('grid-sheet').replaceChildren(...data.sheets.map(s=>{const o=document.createElement('option');o.value=s.name;o.textContent=s.name+(s.hidden?'(隐藏)':'');o.selected=s.name===data.sheet;return o}));
|
|
56
56
|
$('grid-head').replaceChildren();const tr=document.createElement('tr');for(const label of ['行号',...data.columns.map(c=>c.label+(c.hidden?'(隐藏)':''))]){const th=document.createElement('th');th.textContent=label;tr.append(th)}$('grid-head').append(tr);
|
|
57
|
-
$('grid-body').replaceChildren();for(const r of data.rows){const row=document.createElement('tr');const no=document.createElement('td');no.textContent=r.number+(r.hidden?'(隐藏)':'');row.append(no);for(const c of r.cells){const td=document.createElement('td');td.textContent=c.recalculationRequired?'待 Excel 重算':c.value;td.title=c.ref+(c.formula?' · 公式单元格':'')+(c.changed?' · 准备填写':'');if(c.changed)td.className='grid-changed';row.append(td)}$('grid-body').append(row)}
|
|
57
|
+
$('grid-body').replaceChildren();for(const r of data.rows){const row=document.createElement('tr');const no=document.createElement('td');no.textContent=r.number+(r.hidden?'(隐藏)':'');row.append(no);for(const c of r.cells){const td=document.createElement('td');td.textContent=c.recalculationRequired?'待 Excel 重算':c.value;td.title=String(c.value??'')+' · '+c.ref+(c.formula?' · 公式单元格':'')+(c.changed?' · 准备填写':'');if(c.changed)td.className='grid-changed';row.append(td)}$('grid-body').append(row)}
|
|
58
58
|
$('grid-status').textContent='共 '+data.totalRows+' 行 / '+data.totalColumns+' 列,匹配 '+data.matchingRows+' 行;第 '+data.page+' / '+Math.max(1,Math.ceil(data.matchingRows/data.pageSize))+' 页;列 '+data.columnStart+'–'+Math.min(data.totalColumns,data.columnStart+data.columnCount-1);
|
|
59
59
|
$('grid-prev').disabled=data.page<=1;$('grid-next').disabled=data.page*data.pageSize>=data.matchingRows;$('grid-left').disabled=gridColumn===1;$('grid-right').disabled=gridColumn+16>data.totalColumns;
|
|
60
60
|
}catch(e){$('grid-status').textContent=e.message}
|
|
@@ -65,7 +65,7 @@ for(const [id,delta] of [['grid-prev',-1],['grid-next',1]])$(id).onclick=()=>{gr
|
|
|
65
65
|
for(const [id,delta] of [['grid-left',-16],['grid-right',16]])$(id).onclick=()=>{gridColumn=Math.max(1,gridColumn+delta);loadGrid()};
|
|
66
66
|
const reasons={'unknown-field':'暂无可用字段映射','no-capability':'尚未查询或此字段暂无来源','provider-unavailable':'尚未查询或此字段暂无来源','no-match':'未匹配到企业','candidate-review-required':'主体需要人工确认','validation-conflict':'返回值不在原表下拉选项中,请人工核验','protected-source-range':'下拉来源、表格表头或汇总区域,保持原样','sheet-excluded':'工作表未选择','no-data':'该字段未返回','provider-error':'查询失败或未连接,请稍后重试','cancelled':'查询已取消,可显式重试','not-started':'尚未执行,可显式重试','user-excluded':'已按你的选择排除','unsafe-value':'值不安全,已阻止填写','placeholder-needs-confirmation':'已有占位内容,保持原样'};
|
|
67
67
|
function controls(value){busy=value;document.querySelectorAll('button').forEach(b=>b.disabled=value);if($('wizard').open)renderWizard();syncActions();if(!value)loadGrid()}
|
|
68
|
-
function row(target,values,labels){const tr=document.createElement('tr');values.forEach((v,i)=>{const td=document.createElement('td');td.textContent=v;td.dataset.label=labels[i];tr.append(td)});$(target).append(tr);return tr}
|
|
68
|
+
function row(target,values,labels){const tr=document.createElement('tr');values.forEach((v,i)=>{const td=document.createElement('td');td.textContent=v;td.title=String(v??'');td.dataset.label=labels[i];tr.append(td)});$(target).append(tr);return tr}
|
|
69
69
|
function syncActions(){
|
|
70
70
|
const running=current?.state==='enriching';
|
|
71
71
|
const p=current?.presentation;$('progress-panel').hidden=!p||(!p.running&&!p.ended);if(p){$('task-progress').value=p.percent;$('task-progress').dataset.ended=String(p.ended);$('progress-label').textContent=(p.running?'处理中':p.ended?'处理结束':'待处理')+' · '+p.percent+'% · '+p.completed+' / '+p.total+' 查询计划项';$('progress-label').dataset.ended=String(p.ended);$('review-count').textContent='待核验 '+p.review+' 项';$('failure-count').textContent='失败 '+p.failures+' 项';}
|
|
@@ -79,7 +79,7 @@ $('cancel-run').onclick=async()=>{try{await api('/cancel',{id:current.id});$('st
|
|
|
79
79
|
$('retry-run').onclick=()=>{openWizard(true);wizardStep=3;renderWizard()};
|
|
80
80
|
async function api(path,body){if(current&&body?.id===current.id)body={...body,expectedRevision:current.revision};const response=await fetch(basePath+path,{...(body===undefined?{}:{method:'POST',body:JSON.stringify(body)}),headers:{'Content-Type':'application/json','X-Form-Fill-Owner':owner}});const data=await response.json();if(!response.ok)throw Error(data.message);return data}
|
|
81
81
|
function navigate(target){if(target==='wizard'){openWizard();return}if(target!=='import'&&!current){$('status').textContent='请先导入表格。';return}step=target;if(parent!==window)parent.postMessage({type:'ff-view',step:target},location.origin);$('history').hidden=true;$('current').hidden=false;$('current-tab').setAttribute('aria-pressed','true');$('history-tab').setAttribute('aria-pressed','false');document.querySelectorAll('[data-pane]').forEach(p=>p.hidden=p.dataset.pane!==target);document.querySelectorAll('[data-step]').forEach(b=>b.setAttribute('aria-current',String(b.dataset.step===target)))}
|
|
82
|
-
function downloads(){ $('downloads').replaceChildren();for(const [kind,label,suffix] of [['xlsx','下载已填副本','-已填副本.xlsx'],['
|
|
82
|
+
function downloads(){ $('report-preview').hidden=true;$('downloads').replaceChildren();for(const [kind,label,suffix] of [['xlsx','下载已填副本 XLSX','-已填副本.xlsx'],['report','下载任务结果报告 XLSX','-任务结果报告.xlsx']]){const group=document.createElement('div');group.className='toolbar';const preview=document.createElement('button');preview.textContent=kind==='xlsx'?'预览已填副本':'预览任务结果报告';preview.onclick=async()=>{if(kind==='xlsx'){navigate('preview');$('grid').open=true;$('grid-view').value='result';await loadGrid();$('grid').scrollIntoView({block:'start'})}else{try{const data=await api('/download/'+current.id+'/report-preview');$('report-body').replaceChildren();for(const values of data.rows)row('report-body',values,[]);$('report-preview').hidden=false;$('report-preview').scrollIntoView({block:'start'})}catch(e){$('status').textContent=e.message}}};const a=document.createElement('a');Object.assign(a,{className:'download',href:basePath+'/download/'+current.id+'/'+kind,download:filename+suffix,textContent:label});group.append(preview,a);$('downloads').append(group)}}
|
|
83
83
|
function businessSummary(){return current?(retryIntent?'仅重试失败、取消或未执行的部分,保留成功结果。':'')+'填写文件“'+current.filename+'”中的空白字段。共 '+current.analysis.tables.length+' 个工作表,'+wizardFields.size+' 个填写字段,'+current.analysis.opportunities.filter(p=>wizardFields.has(p.field)).length+' 个待补空位置。字段范围以本次保存的映射为准。保留已有值、公式与未确认位置,核验预览后生成新副本。':'请先上传 XLSX 文件。'}
|
|
84
84
|
function command(){return current?businessSummary()+'\n'+$('requirements').value+'\n执行关联:调用 form_fill_enrich:taskId='+current.id+',expectedRevision='+current.revision+(retryIntent?',mode=retry':'')+'。字段范围以工作台确认设置为准。':''}
|
|
85
85
|
function mappingVersion(){return JSON.stringify({tables:current?.analysis.tables.map(t=>({sheet:t.sheet,headerRow:t.headerRow,anchors:t.anchors,mappings:t.mappings.map(m=>[m.column,m.field,m.role,m.evidence])})),scope:current?.selectedFields??null})}
|
|
@@ -274,7 +274,7 @@ function render(data){
|
|
|
274
274
|
current=data;directScope();$('direct-command').hidden=true;filename=(data.filename||'已填表格.xlsx').replace(/\.xlsx$/i,'');location.hash='task='+data.id;
|
|
275
275
|
if(gridTask!==data.id){gridTask=data.id;gridPageNumber=1;gridColumn=1;$('grid-sheet').replaceChildren();$('grid-query').value=''}$('grid').hidden=false;loadGrid();
|
|
276
276
|
if(parent!==window)parent.postMessage({type:'ff-task',taskId:data.id},location.origin);
|
|
277
|
-
$('incomplete').closest('.card').style.display=(data.exceptions||data.changeSet.incomplete).length?'':'none';$('changes').replaceChildren();$('incomplete').replaceChildren();$('downloads').replaceChildren();
|
|
277
|
+
$('incomplete').closest('.card').style.display=(data.exceptions||data.changeSet.incomplete).length?'':'none';$('changes').replaceChildren();$('incomplete').replaceChildren();$('report-preview').hidden=true;$('downloads').replaceChildren();
|
|
278
278
|
$('task-meta').dataset.state=data.state;
|
|
279
279
|
$('task-meta').textContent=(data.presentation?.title||data.filename)+' · '+(data.presentation?.status||data.state);$('task-details').textContent='任务 ID:'+data.id+' · 有效期至 '+new Date(data.expiresAt).toLocaleString();
|
|
280
280
|
$('summary').textContent='可填写 '+data.changeSet.changes.length+' 格 · '+(data.plan.paidCalls?'企查查来源':'本地分析 / 合成示例 · 费用 0')+' · 未完成 '+data.changeSet.incomplete.length+' 项';
|
|
@@ -330,5 +330,6 @@ window.addEventListener('pagehide',saveMappingDraft);
|
|
|
330
330
|
for(const event of ['change','click'])$('mapping').addEventListener(event,saveMappingDraft);
|
|
331
331
|
$('mapping').addEventListener('change',()=>{wizardMappingApplied=false;$('mapping-next').hidden=true;highlightMappingConflicts()});
|
|
332
332
|
window.addEventListener('hashchange',()=>{if(new URLSearchParams(location.hash.slice(1)).get('task')!==current?.id)restore()});
|
|
333
|
-
|
|
333
|
+
async function restoreSessionTask(){if(location.hash||!sessionId){restore();return}try{const list=await api('/tasks');if(current||location.hash)return;const task=list.filter(item=>item.sessionId===sessionId).sort((a,b)=>(b.updatedAt||b.created)-(a.updatedAt||a.created))[0];if(task){location.hash='task='+task.id;await restore()}}catch(e){$('status').textContent='恢复任务失败,可在任务历史中重试:'+e.message}}
|
|
334
|
+
navigate('import');restoreSessionTask();setInterval(()=>{if(!document.hidden&¤t&&!busy&&!$('wizard').open)restore(true)},2000);
|
|
334
335
|
</script></html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-form-fill-agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.26",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "AI form filling and spreadsheet autofill for XLSX in DeepSeek Harness, using authorized Qichacha MCP facts with cell previews, user confirmation and export to a new copy.",
|
|
6
6
|
"repository": {
|