dsh-pre-duediligence 0.1.30 → 0.1.31

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
@@ -187,3 +187,8 @@
187
187
  - 补齐中文产品首页、四阶段流程及局部可恢复标题,仅在访前尽调空白会话展示。
188
188
  - 侧栏展开请求支持新会话挂载前排队,修复连续切换时右侧工作台不显示。
189
189
  - 增加入口失败、会话归属、标题恢复和导航提交顺序回归测试。
190
+ # 0.1.31
191
+
192
+ - 修复 finalize/history 可选字段导致的 lossless JSON 错误,保留版本不可覆盖及相同正文幂等重试。
193
+ - 增加可追踪的公开工作阶段、嵌套经营资料摘要与明确的数据/摘要状态。
194
+ - 内置开源中文字体,PDF默认无需配置环境变量、无需联网。
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # dsh-pre-duediligence
2
2
 
3
- 0.1.30 新增继续尽调、报告版本、补充材料与交叉核验、PDF/DOCX衍生文件,并保留0.1.29图片导入和多企业计划。PDF需配置中文字体;导出为保留Markdown标记的正文版,真实上传尚未接通。详见 [发布说明](docs/RELEASE-0.1.30.md)。
3
+ 0.1.31 修复报告保存/历史读取的 JSON 返回错误,新增用户可见工作阶段与嵌套数据摘要;PDF 内置开源中文字体,可离线导出,无需默认配置环境变量。保留继续尽调、报告版本、补充材料、交叉核验、图片导入和多企业计划。导出为保留Markdown标记的正文版,真实上传尚未接通。详见 [发布说明](docs/RELEASE-0.1.31.md)。
4
4
 
5
5
  ## 安装与三分钟上手
6
6
 
7
7
  访前尽调智能体:面向拜访前调查,提供企业尽调、客户尽调、客户背景调查、工商核验与风险信息整理;使用企查查 MCP,提供固定路由连续调用、主体确认、动态进度和可下载报告。
8
8
 
9
9
  ```sh
10
- dsh plugin --profile web add dsh-pre-duediligence@0.1.30
10
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.31
11
11
  ```
12
12
 
13
13
  请先满足下文的 DSH 与连接器要求;Better Sidebar 仅在需要可视化工作台时选装。安装后完整停止并重启对应 Profile。
@@ -92,7 +92,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-pre-duediligenc
92
92
  安装脚本默认向 DSH Web profile 安装两个基础 Bundle:
93
93
 
94
94
  1. `dsh-mcp-connector@0.2.32`:提供通用 MCP 连接器与市场,通过“企查查·企业工商”完成 OAuth,并动态挂载企业、风险、知产、经营、历史和董监高 MCP。
95
- 2. `dsh-pre-duediligence@0.1.30`:提供左侧智能体入口、原生会话首页、提示词生成器、Host 执行门和 Skill。
95
+ 2. `dsh-pre-duediligence@0.1.31`:提供左侧智能体入口、原生会话首页、提示词生成器、Host 执行门和 Skill。
96
96
 
97
97
  默认不会安装 Better Sidebar。需要 Session 级可视化工作台时显式开启:
98
98
 
@@ -120,7 +120,7 @@ dsh web
120
120
 
121
121
  ~~~bash
122
122
  dsh plugin --profile web add dsh-mcp-connector@0.2.32
123
- dsh plugin --profile web add dsh-pre-duediligence@0.1.30 --allow-build=node-pty,dsh-pre-duediligence
123
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.31 --allow-build=node-pty,dsh-pre-duediligence
124
124
  dsh web
125
125
  ~~~
126
126
 
@@ -136,7 +136,7 @@ dsh web
136
136
  dsh plugin --profile web list --depth 0
137
137
  ~~~
138
138
 
139
- 应始终看到所选基线的 Connector 和 `dsh-pre-duediligence@0.1.30`;仅在显式启用或原本已安装时看到 Sidebar。重启 DSH 并连接“企查查·企业工商”后,在左侧菜单点击“访前尽调”进入初始原生会话。无 Sidebar 时可继续生成提示词、回填/发送草稿并在会话中阅读报告;五项流程按钮会提示安装可选工作台。安装 Sidebar 时,右侧工作台初始仍关闭;点击业务按钮可主动打开,正式发送任务被接纳后会自动展开一次。仅回填不会展开,收起后轮询不会重新打开。其右侧“+”菜单不会列出该入口,普通会话保持 DSH 原样。
139
+ 应始终看到所选基线的 Connector 和 `dsh-pre-duediligence@0.1.31`;仅在显式启用或原本已安装时看到 Sidebar。重启 DSH 并连接“企查查·企业工商”后,在左侧菜单点击“访前尽调”进入初始原生会话。无 Sidebar 时可继续生成提示词、回填/发送草稿并在会话中阅读报告;五项流程按钮会提示安装可选工作台。安装 Sidebar 时,右侧工作台初始仍关闭;点击业务按钮可主动打开,正式发送任务被接纳后会自动展开一次。仅回填不会展开,收起后轮询不会重新打开。其右侧“+”菜单不会列出该入口,普通会话保持 DSH 原样。
140
140
 
141
141
  ### 无 Sidebar 功能边界
142
142
 
@@ -21,3 +21,8 @@ Copyright (c) 2026 dsh-data-cleaning-agent plugin contributors
21
21
  https://github.com/duhu2000/dsh-data-cleaning-agent
22
22
 
23
23
  No source code from that project is bundled in this package.
24
+ # Noto Sans CJK SC
25
+
26
+ The unmodified Noto Sans CJK SC Regular font (© 2014-2021 Adobe) is distributed
27
+ under SIL Open Font License 1.1, not the package's MIT license.
28
+ See assets/fonts/LICENSE.txt and assets/fonts/README.md.
@@ -0,0 +1,92 @@
1
+ This Font Software is licensed under the SIL Open Font License,
2
+ Version 1.1.
3
+
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+ -----------------------------------------------------------
8
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9
+ -----------------------------------------------------------
10
+
11
+ PREAMBLE
12
+ The goals of the Open Font License (OFL) are to stimulate worldwide
13
+ development of collaborative font projects, to support the font
14
+ creation efforts of academic and linguistic communities, and to
15
+ provide a free and open framework in which fonts may be shared and
16
+ improved in partnership with others.
17
+
18
+ The OFL allows the licensed fonts to be used, studied, modified and
19
+ redistributed freely as long as they are not sold by themselves. The
20
+ fonts, including any derivative works, can be bundled, embedded,
21
+ redistributed and/or sold with any software provided that any reserved
22
+ names are not used by derivative works. The fonts and derivatives,
23
+ however, cannot be released under any other type of license. The
24
+ requirement for fonts to remain under this license does not apply to
25
+ any document created using the fonts or their derivatives.
26
+
27
+ DEFINITIONS
28
+ "Font Software" refers to the set of files released by the Copyright
29
+ Holder(s) under this license and clearly marked as such. This may
30
+ include source files, build scripts and documentation.
31
+
32
+ "Reserved Font Name" refers to any names specified as such after the
33
+ copyright statement(s).
34
+
35
+ "Original Version" refers to the collection of Font Software
36
+ components as distributed by the Copyright Holder(s).
37
+
38
+ "Modified Version" refers to any derivative made by adding to,
39
+ deleting, or substituting -- in part or in whole -- any of the
40
+ components of the Original Version, by changing formats or by porting
41
+ the Font Software to a new environment.
42
+
43
+ "Author" refers to any designer, engineer, programmer, technical
44
+ writer or other person who contributed to the Font Software.
45
+
46
+ PERMISSION & CONDITIONS
47
+ Permission is hereby granted, free of charge, to any person obtaining
48
+ a copy of the Font Software, to use, study, copy, merge, embed,
49
+ modify, redistribute, and sell modified and unmodified copies of the
50
+ Font Software, subject to the following conditions:
51
+
52
+ 1) Neither the Font Software nor any of its individual components, in
53
+ Original or Modified Versions, may be sold by itself.
54
+
55
+ 2) Original or Modified Versions of the Font Software may be bundled,
56
+ redistributed and/or sold with any software, provided that each copy
57
+ contains the above copyright notice and this license. These can be
58
+ included either as stand-alone text files, human-readable headers or
59
+ in the appropriate machine-readable metadata fields within text or
60
+ binary files as long as those fields can be easily viewed by the user.
61
+
62
+ 3) No Modified Version of the Font Software may use the Reserved Font
63
+ Name(s) unless explicit written permission is granted by the
64
+ corresponding Copyright Holder. This restriction only applies to the
65
+ primary font name as presented to the users.
66
+
67
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
68
+ Software shall not be used to promote, endorse or advertise any
69
+ Modified Version, except to acknowledge the contribution(s) of the
70
+ Copyright Holder(s) and the Author(s) or with their explicit written
71
+ permission.
72
+
73
+ 5) The Font Software, modified or unmodified, in part or in whole,
74
+ must be distributed entirely under this license, and must not be
75
+ distributed under any other license. The requirement for fonts to
76
+ remain under this license does not apply to any document created using
77
+ the Font Software.
78
+
79
+ TERMINATION
80
+ This license becomes null and void if any of the above conditions are
81
+ not met.
82
+
83
+ DISCLAIMER
84
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
88
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,12 @@
1
+ # Bundled Chinese PDF font
2
+
3
+ Noto Sans CJK SC Regular, version 2.004. Unmodified OpenType font.
4
+ Copyright © 2014-2021 Adobe (http://www.adobe.com/).
5
+ Licensed under SIL Open Font License 1.1; see LICENSE.txt.
6
+
7
+ Upstream: https://github.com/notofonts/noto-cjk/tree/main/Sans/OTF/SimplifiedChinese
8
+ Upstream Git blob SHA-1: dc15562470b4f842321894787a0d066879ccff8b
9
+ Size: 16437364 bytes. The downloaded asset was matched to GitHub's blob identity.
10
+
11
+ The package includes this font for offline PDF export; it is not loaded into the
12
+ browser bundle. No runtime font download or OS font configuration is required.
@@ -0,0 +1,9 @@
1
+ # 0.1.31 — 工具结果可靠性与可见进展
2
+
3
+ - 工具出口递归省略可选对象字段,拒绝JSON会静默破坏的非JSON值;工具测试在宿主边界执行严格往返校验。
4
+ - 首版 finalize / history 不再返回 undefined;相同正文重试幂等,不同正文仍不得覆盖已发布版本。遇到不确定保存结果应先读取历史确认,补充内容创建新版本。
5
+ - 新增 previsit_progress,保存用户可见的整理事实、风险核对、撰写报告工作摘要。查询事件、停止和同步失败优先;旧摘要标明时间,不展示内部思考或虚构百分比。单次长模型生成不能保证定时更新。
6
+ - 有界提取嵌套中文业务字段、记录计数和数组样本,支持JSON文本包装,排除凭据字段;查询状态与摘要缺失分开展示。老任务仅保存了旧摘要时不补造数据。
7
+ - PDF随包内置未修改的 Noto Sans CJK SC Regular 2.004(SIL OFL 1.1),默认离线导出。保留字体环境变量覆盖;只生成正文版,不声称复刻HTML排版。字体使安装包增大约16MB。
8
+
9
+ 验收:类型检查、单元/集成回归、构建及发布流水线浏览器门禁。合成中文PDF已生成、提取文本并逐页检查。本机无头Chrome启动退出,浏览器回归以GitHub Linux门禁结果为准;未调用真实企业MCP或改写用户历史报告。
@@ -6,7 +6,7 @@
6
6
 
7
7
  使用 docx 生成 OOXML ZIP、PDFKit 生成 PDF,不把 HTML 改后缀冒充 Word/PDF。当前为保真文本版:正文和 Markdown 标记原样保留,非 HTML 富排版复刻,不载入远程图片、链接资源或脚本。文件含主体、版本、任务标识;采用 Letter 纵向页面和自动换行。
8
8
 
9
- PDF 需管理员在启动 DSH 前设置 `DSH_PREVISIT_PDF_FONT` 为具有中文覆盖的独立 TTF/OTF 文件绝对路径。字体不随 npm 包分发,使用者须确认授权;未配置或无法读取时导出失败,无成功文件记录。须以实际部署字体进行中文及特殊字符视觉验收。
9
+ 自0.1.31起,PDF默认使用随包分发的 Noto Sans CJK SC Regular(SIL OFL 1.1),无需联网或管理员配置。可选 `DSH_PREVISIT_PDF_FONT` 指定具有中文覆盖的独立 TTF/OTF;显式配置不可读或字体无效时仍报错,不伪造成功记录。覆盖字体的授权及特殊字符显示需部署方确认。以下验收记录为三期初版历史记录。
10
10
 
11
11
  Word 默认指定 Arial Unicode MS,可用 `DSH_PREVISIT_DOCX_FONT` 更换为部署环境已安装的中文字体家族;DOCX 不嵌入字体,打开端须有该字体或有效替代字体。当前导出不是富排版PDF/Word模板引擎。
12
12
 
package/install.sh CHANGED
@@ -5,7 +5,7 @@ set -euo pipefail
5
5
  readonly PROFILE_NAME="${DSH_PROFILE:-web}"
6
6
  readonly BASELINE="${DSH_PREVISIT_BASELINE:-stable}"
7
7
  readonly WORKBENCH_MODE="${DSH_PREVISIT_WORKBENCH:-off}"
8
- readonly PREVISIT_SPEC="${DSH_PRE_DUEDILIGENCE_SPEC:-dsh-pre-duediligence@0.1.30}"
8
+ readonly PREVISIT_SPEC="${DSH_PRE_DUEDILIGENCE_SPEC:-dsh-pre-duediligence@0.1.31}"
9
9
  readonly LEGACY_PREVISIT_NAME="qcc-previsit-dsh"
10
10
  readonly LEGACY_QCC_OAUTH_NAME="qcc-dsh-mcp-oauth"
11
11
 
package/lib/client.js CHANGED
@@ -1183,6 +1183,11 @@ function processingStatus(input) {
1183
1183
  if (input.waiting) return { busy: false, title: "\u7B49\u5F85\u4E3B\u4F53\u786E\u8BA4 / \u7EE7\u7EED", detail: "\u8BF7\u5728\u4F1A\u8BDD\u4E2D\u786E\u8BA4\u4F01\u4E1A\u540E\u7EE7\u7EED\u3002" };
1184
1184
  if (input.running === false) return { busy: false, title: "\u672C\u8F6E\u4F1A\u8BDD\u5DF2\u505C\u6B62\uFF0C\u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210", detail: "\u8BF7\u67E5\u770B\u4F1A\u8BDD\u4E2D\u7684\u786E\u8BA4\u8BF7\u6C42\u3001\u505C\u6B62\u6216\u9519\u8BEF\u63D0\u793A\u3002" };
1185
1185
  if (input.querying) return { busy: input.running === true, title: input.running === true ? "\u6B63\u5728\u67E5\u8BE2\u8D44\u6599" : "\u67E5\u8BE2\u7ED3\u679C\u5F85\u66F4\u65B0", detail: "\u5C1A\u672A\u6536\u5230\u8BE5\u67E5\u8BE2\u7684\u5B8C\u6210\u8BB0\u5F55\uFF0C\u7ED3\u679C\u8FD4\u56DE\u540E\u81EA\u52A8\u66F4\u65B0\u3002" };
1186
+ if (input.activity && Date.parse(input.activity.updatedAt) >= (input.lastResultAt ?? 0)) {
1187
+ const age = Math.max(0, Math.floor(((input.now ?? Date.now()) - Date.parse(input.activity.updatedAt)) / 1e3));
1188
+ const phase = { analysis: "\u6574\u7406\u7ECF\u8425\u4E8B\u5B9E", verification: "\u6838\u5BF9\u98CE\u9669\u4E0E\u8BC1\u636E", writing: "\u64B0\u5199\u5C3D\u8C03\u62A5\u544A" }[input.activity.phase];
1189
+ return { busy: input.running === true, title: `\u6700\u8FD1\u5DE5\u4F5C\u9636\u6BB5\uFF1A${phase}`, detail: `${input.activity.summary}\uFF08${age} \u79D2\u524D\u4E0A\u62A5${age >= 120 ? "\uFF0C\u9636\u6BB5\u4FE1\u606F\u53EF\u80FD\u5DF2\u8FC7\u65F6\uFF1B\u7B49\u5F85\u65B0\u7684\u5DE5\u4F5C\u6458\u8981" : ""}\uFF09\u3002${input.running === void 0 ? "\u5F53\u524D\u8FD0\u884C\u72B6\u6001\u5C1A\u672A\u786E\u8BA4\u3002" : ""}` };
1190
+ }
1186
1191
  return { busy: input.running === true, title: input.running === true ? "\u5BBF\u4E3B\u663E\u793A\u4ECD\u5728\u5904\u7406 \xB7 \u6B63\u5728\u5206\u6790 / \u6574\u7406\u6750\u6599" : "\u7B49\u5F85\u4E0B\u4E00\u6B21\u4EFB\u52A1\u66F4\u65B0", detail: `\u8DDD\u6700\u8FD1\u4EFB\u52A1\u7ED3\u679C ${Math.max(0, Math.floor(input.secondsSinceResult))} \u79D2\u3002${input.secondsSinceResult >= 120 ? "\u7B49\u5F85\u65F6\u95F4\u8F83\u957F\uFF0C\u53EF\u68C0\u67E5\u4F1A\u8BDD\u8FD0\u884C\u72B6\u6001\uFF1B\u8BF7\u52FF\u91CD\u590D\u542F\u52A8\u4EFB\u52A1\u3002" : "\u5206\u6790\u9636\u6BB5\u53EF\u80FD\u6682\u65F6\u6CA1\u6709\u65B0\u7684\u5DE5\u5177\u7ED3\u679C\u3002"} \u4E0D\u63D0\u4F9B\u865A\u6784\u767E\u5206\u6BD4\u6216\u5185\u90E8\u601D\u8003\u5185\u5BB9\u3002` };
1187
1192
  }
1188
1193
 
@@ -3095,7 +3100,8 @@ function ExecutionProgress(props) {
3095
3100
  }, [props.task?.id, props.status]);
3096
3101
  if (props.task === null || props.status === "ready") return null;
3097
3102
  const progress = hostedLiveProgress(props.task);
3098
- const activity = processingStatus({ running: props.modelRunning, error: props.syncError, waiting: props.task.state === "needs-entity-confirmation", querying: progress.current !== null, secondsSinceResult: (now - Date.parse(props.task.updatedAt)) / 1e3 });
3103
+ const lastResultAt = Math.max(Date.parse(props.task.createdAt), ...props.task.runs.map((run) => Date.parse(run.completedAt ?? run.startedAt)));
3104
+ const activity = processingStatus({ running: props.modelRunning, error: props.syncError, waiting: props.task.state === "needs-entity-confirmation", querying: progress.current !== null, secondsSinceResult: (now - lastResultAt) / 1e3, ...props.task.activity ? { activity: props.task.activity } : {}, now, lastResultAt });
3099
3105
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "qccPwLiveProgress", "data-running": activity.busy, children: [
3100
3106
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "qccPwLivePulse", "aria-hidden": "true" }),
3101
3107
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "qccPwLiveCopy", role: "status", children: [
@@ -3304,10 +3310,12 @@ function OpportunityPanel(props) {
3304
3310
  ] }) : insights.stateUndetermined ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "qccPwMode", "data-tone": "review", children: "\u72B6\u6001\u672A\u5B9A" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "qccPwMode", "data-tone": running2 ? void 0 : "neutral", children: running2 ? "\u6B63\u5728\u7814\u5224" : finished ? "\u672A\u8BC6\u522B\u7ED3\u8BBA" : "\u5F85\u7814\u5224" })
3305
3311
  ] }),
3306
3312
  insights.state ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { children: insights.state }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: "\u5C1A\u672A\u5F62\u6210\u7ECF\u8425\u7814\u5224\uFF1B\u5148\u5C55\u793A\u5DF2\u8FD4\u56DE\u7684\u7ECF\u8425\u4E8B\u5B9E\uFF0C\u4E0D\u4EE5\u67E5\u8BE2\u6210\u529F\u63A8\u65AD\u7ECF\u8425\u826F\u597D\u3002" }),
3307
- Array.from(new Map((props.hostedTask?.runs ?? []).filter((run) => run.result && (run.result.summary || run.result.facts.length) && ["registration", "profile", "annual_reports", "changes", "financing", "bidding", "recruitment", "investments"].includes(run.dimension)).map((run) => [run.dimension, run])).values()).map((run) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3313
+ Array.from(new Map((props.hostedTask?.runs ?? []).filter((run) => ["registration", "profile", "annual_reports", "changes", "financing", "bidding", "recruitment", "investments"].includes(run.dimension)).map((run) => [run.dimension, run])).values()).map((run) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3308
3314
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h4", { children: dimensionLabel(run.dimension) }),
3315
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "qccPwNote", children: run.status === "done" ? "\u6570\u636E\u5DF2\u53D6\u5F97 \xB7 \u4EE5\u4E0B\u4E3A\u6765\u6E90\u6458\u8981\u4E0E\u6837\u672C\u5B57\u6BB5\uFF0C\u4E0D\u4EE3\u8868\u5168\u91CF\u660E\u7EC6\u6216\u6700\u7EC8\u7814\u5224" : run.status === "no-data" ? "\u67E5\u8BE2\u6210\u529F\uFF0C\u672A\u53D1\u73B0\u8BB0\u5F55" : `\u67E5\u8BE2\u72B6\u6001\uFF1A${run.status}` }),
3309
3316
  run.result?.summary ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: run.result.summary }) : null,
3310
- run.result?.facts.map((fact) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: fact }, fact))
3317
+ run.result?.facts.map((fact) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: fact }, fact)),
3318
+ run.status === "done" && !run.result?.summary && !run.result?.facts.some((fact) => !fact.startsWith("\u4F01\u4E1A\u540D\u79F0\uFF1A")) ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "qccPwNote", children: "\u6570\u636E\u5DF2\u8FD4\u56DE\uFF0C\u6682\u672A\u63D0\u53D6\u5230\u4E1A\u52A1\u6458\u8981\uFF1B\u4E0D\u4EE3\u8868\u65E0\u6570\u636E\uFF0C\u53EF\u5728\u539F\u4F1A\u8BDD\u67E5\u770B\u5DE5\u5177\u7ED3\u679C\u3002" }) : null
3311
3319
  ] }, run.id)),
3312
3320
  insights.stateUndetermined ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "qccPwNote", children: "\u72B6\u6001\u672A\u5B9A\uFF1A\u516C\u5F00\u8BC1\u636E\u4E0D\u8DB3\uFF0C\u672C\u6B21\u964D\u7EA7\u4E3A\u6E05\u5355\u5F0F\u7B80\u62A5\u3002" }) : null,
3313
3321
  insights.industryLink === null ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { className: "qccPwNote", children: [
package/lib/index.js CHANGED
@@ -335,8 +335,7 @@ async function renderReportFile(task, format, fontPath = process.env.DSH_PREVISI
335
335
  children: [new Paragraph({ heading: HeadingLevel.TITLE, children: [new TextRun({ text: task.entity?.fullName ?? task.query, font: fontName, size: 32, color: "000000" })] }), new Paragraph({ children: [new TextRun({ text: `V${task.reportVersion ?? 1} \xB7 ${task.id}`, font: fontName, size: 20, color: "000000" })] }), ...lines.map((line) => new Paragraph({ children: [new TextRun({ text: line, font: fontName, size: 24, color: "000000" })] }))]
336
336
  }] }));
337
337
  }
338
- if (!fontPath) throw new Error("PDF \u5BFC\u51FA\u9700\u8981\u7BA1\u7406\u5458\u914D\u7F6E DSH_PREVISIT_PDF_FONT \u4E2D\u6587\u5B57\u4F53\u6587\u4EF6\uFF1B\u672A\u751F\u6210\u6587\u4EF6");
339
- const font = await readFile(fontPath);
338
+ const font = await readFile(fontPath || new URL("../assets/fonts/NotoSansCJKsc-Regular.otf", import.meta.url));
340
339
  if (font.length > 4e7) throw new Error("\u5B57\u4F53\u6587\u4EF6\u8FC7\u5927");
341
340
  return new Promise((resolve, reject) => {
342
341
  const pdf = new PDFDocument({ size: "LETTER", margin: 54, info: { Title: identity, Creator: "\u8BBF\u524D\u5C3D\u8C03" } });
@@ -1013,6 +1012,15 @@ var PrevisitWorkflowStore = class {
1013
1012
  });
1014
1013
  }
1015
1014
  finalizationQueue = Promise.resolve();
1015
+ reportProgress(id, activity) {
1016
+ const operation = this.finalizationQueue.then(() => this.update(id, (current) => {
1017
+ assertTaskOpen(current);
1018
+ return { ...current, activity };
1019
+ }));
1020
+ this.finalizationQueue = operation.catch(() => {
1021
+ });
1022
+ return operation;
1023
+ }
1016
1024
  /** Serialize evidence writes with publication: no late mutation of a published version. */
1017
1025
  addEvidence(id, kind, input) {
1018
1026
  const operation = this.finalizationQueue.then(() => this.update(id, (record) => {
@@ -1251,11 +1259,17 @@ import { randomUUID as randomUUID4 } from "node:crypto";
1251
1259
  // src/result-summary.ts
1252
1260
  function summarizeResult(value) {
1253
1261
  const result = { summary: "", facts: [], factors: [] };
1262
+ const seen = /* @__PURE__ */ new Set();
1263
+ const fields = /^(经营状态|登记状态|主营业务|经营范围|所属行业|行业|注册资本|成立日期|参保人数|企业名称|产业链|产业链环节|核心业务|产品名称|产品介绍|业务介绍|企业简介|简介|行业标签|融资轮次|轮次|融资金额|融资日期|投资方|投资机构|项目名称|招标项目名称|中标项目名称|中标金额|发布日期|公告日期|招标人|中标人|职位名称|招聘职位|工作地点|薪资|招聘人数|年度|年份|年报年度|变更日期|变更项目|变更前|变更后|日期|时间|总数|记录总数|记录数|总条数|total|totalCount|count)$/;
1264
+ const fact = (value2) => {
1265
+ if (result.facts.length < 18 && !result.facts.includes(value2)) result.facts.push(value2);
1266
+ };
1254
1267
  let visited = 0;
1255
- const visit = (item, depth) => {
1256
- if (depth > 7 || ++visited > 1e3 || item === null || typeof item !== "object") return;
1268
+ const visit = (item, depth, path = "") => {
1269
+ if (depth > 7 || ++visited > 1e3 || item === null || typeof item !== "object" || seen.has(item)) return;
1270
+ seen.add(item);
1257
1271
  if (Array.isArray(item)) {
1258
- item.slice(0, 80).forEach((child) => visit(child, depth + 1));
1272
+ item.slice(0, 80).forEach((child, index) => visit(child, depth + 1, `${path}[${index + 1}]`));
1259
1273
  return;
1260
1274
  }
1261
1275
  const row = item;
@@ -1265,14 +1279,37 @@ function summarizeResult(value) {
1265
1279
  if (typeof name === "string" && Number.isSafeInteger(count) && count >= 0 && !result.factors.some((f) => f.name === name)) result.factors.push({ name: name.slice(0, 100), count });
1266
1280
  for (const [key, child] of Object.entries(row)) {
1267
1281
  if ((key === "\u6458\u8981" || key === "summary") && typeof child === "string" && !result.summary) result.summary = child.slice(0, 1800);
1268
- if (depth <= 2 && /^(经营状态|登记状态|主营业务|经营范围|所属行业|行业|注册资本|成立日期|参保人数|企业名称)$/.test(key) && (typeof child === "string" || typeof child === "number") && result.facts.length < 12) result.facts.push(`${key}\uFF1A${String(child).slice(0, 500)}`);
1269
- visit(child, depth + 1);
1282
+ if (/token|secret|password|authorization|凭据|密钥|密码|身份证|证件号码|银行账号/i.test(key)) continue;
1283
+ const businessLabel = /^[\u3400-\u9fff][\u3400-\u9fffA-Za-z0-9()()、/ _-]{0,39}$/.test(key);
1284
+ if ((fields.test(key) || businessLabel) && (typeof child === "string" || typeof child === "number")) fact(`${path.includes("[") ? `${path.slice(0, 120)} \xB7 ` : ""}${key}\uFF1A${String(child).slice(0, 500)}`);
1285
+ if (key === "text" && typeof child === "string" && child.length < 2e5) {
1286
+ try {
1287
+ visit(JSON.parse(child), depth + 1, path);
1288
+ } catch {
1289
+ }
1290
+ }
1291
+ visit(child, depth + 1, path ? `${path}.${key}` : key);
1270
1292
  }
1271
1293
  };
1272
1294
  visit(value, 0);
1273
1295
  return result;
1274
1296
  }
1275
1297
 
1298
+ // src/tool-json.ts
1299
+ function toolJson(value, seen = /* @__PURE__ */ new Set()) {
1300
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
1301
+ if (typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0)) return value;
1302
+ if (typeof value !== "object" || seen.has(value)) throw new Error("\u5DE5\u5177\u7ED3\u679C\u5305\u542B\u4E0D\u53EF\u5E8F\u5217\u5316\u7684\u503C");
1303
+ if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new Error("\u5DE5\u5177\u7ED3\u679C\u5FC5\u987B\u4E3A\u666E\u901A JSON \u5BF9\u8C61");
1304
+ seen.add(value);
1305
+ try {
1306
+ if (Array.isArray(value)) return Array.from(value, (item) => toolJson(item, seen));
1307
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => [key, toolJson(item, seen)]));
1308
+ } finally {
1309
+ seen.delete(value);
1310
+ }
1311
+ }
1312
+
1276
1313
  // src/previsit-tools.ts
1277
1314
  var QUERY_ROUTES = {
1278
1315
  entity_search: ["company", "get_company_by_query"],
@@ -1417,7 +1454,7 @@ function registerPrevisitTools(ctx, workflow = new PrevisitWorkflowStore(), opti
1417
1454
  };
1418
1455
  try {
1419
1456
  exec.signal.throwIfAborted();
1420
- return await execute(object(args), exec, execution.agent);
1457
+ return toolJson(await execute(object(args), exec, execution.agent));
1421
1458
  } finally {
1422
1459
  controllers.delete(controller);
1423
1460
  }
@@ -1521,6 +1558,19 @@ function registerPrevisitTools(ctx, workflow = new PrevisitWorkflowStore(), opti
1521
1558
  beginning.delete(owner);
1522
1559
  }
1523
1560
  });
1561
+ register("previsit_progress", "\u4E0A\u62A5\u7B80\u77ED\u7684\u7528\u6237\u53EF\u89C1\u5DE5\u4F5C\u6458\u8981\u3002\u5728\u8D44\u6599\u8FD4\u56DE\u540E\u3001\u98CE\u9669\u6838\u5BF9\u53CA\u62A5\u544A\u64B0\u5199\u5F00\u59CB\u65F6\u8C03\u7528\u3002\u53EA\u9648\u8FF0\u5F53\u524D\u5DE5\u4F5C\uFF0C\u4E0D\u5305\u542B\u5185\u90E8\u601D\u8003\u3001\u63A8\u7406\u8349\u7A3F\u6216\u865A\u6784\u8FDB\u5EA6\u3002", {
1562
+ taskId: { type: "string" },
1563
+ phase: { type: "string", enum: ["analysis", "verification", "writing"] },
1564
+ summary: { type: "string", maxLength: 240 }
1565
+ }, ["taskId", "phase", "summary"], async (args, _exec, agent) => {
1566
+ const task = requireTask(args, agent);
1567
+ const phase = args.phase;
1568
+ if (phase !== "analysis" && phase !== "verification" && phase !== "writing") throw new Error("\u672A\u77E5\u5DE5\u4F5C\u9636\u6BB5");
1569
+ const summary = string(args.summary);
1570
+ if (!summary || summary.length > 240) throw new Error("\u5DE5\u4F5C\u6458\u8981\u987B\u4E3A1\u81F3240\u5B57");
1571
+ const record = await workflow.reportProgress(task.id, { phase, summary, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
1572
+ return { taskId: task.id, activity: record.activity };
1573
+ });
1524
1574
  const requireTask = (args, agent) => {
1525
1575
  const owner = ownerOf(agent);
1526
1576
  if (beginning.has(owner)) throw new Error("\u65B0\u4EFB\u52A1\u4ECD\u5728\u521D\u59CB\u5316\uFF0C\u8BF7\u4E32\u884C\u6267\u884C");
@@ -1788,7 +1838,7 @@ function loadBundledSkill() {
1788
1838
  },
1789
1839
  metadata: {
1790
1840
  author: "QCC",
1791
- version: "0.1.30",
1841
+ version: "0.1.31",
1792
1842
  industry: "enterprise-services",
1793
1843
  mcpServers: ["qcc-company", "qcc-risk", "qcc-ipr", "qcc-operation", "qcc-executive"]
1794
1844
  }