dsh-pre-duediligence 0.1.23 → 0.1.24

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
@@ -1,6 +1,12 @@
1
1
  # 更新日志
2
2
  ## [Unreleased]
3
3
 
4
+ ## [0.1.24] - 2026-09-13
5
+
6
+ - 左侧菜单在创建专属 Session 期间保持固定名称“访前尽调”,加载或失败状态不再替换菜单文案。
7
+ - 初始页标题桥接适配新版 Host 的 composer sibling slot,稳定恢复“访前尽调一页纸智能体”、企查查蓝 LOGO,并在宿主重渲染后继续同步。
8
+ - 任务历史卡片支持按来源 Session 打开只读详情、内嵌查看与下载已保存报告;缺少来源的旧记录仍明确保持只读摘要。
9
+
4
10
  ## [0.1.23] - 2026-09-13
5
11
 
6
12
  - Host 存储兼容缺少 Workspace / Session 来源元数据的旧记录:记录继续进入 Profile 历史,但绝不匹配或重绑为当前 Session 任务。
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # dsh-pre-duediligence
2
2
 
3
- 0.1.23 补齐旧 Host 记录兼容:缺少 Workspace / Session 来源元数据的记录继续进入 Profile 历史,并明确显示“未记录(旧记录)”,但绝不匹配或重绑为当前 Session 任务;显式 TaskScope 契约固化当前任务隔离与跨 Session 历史汇总边界。已发布版本与验证边界见 [发布说明](docs/RELEASE-0.1.23.md)、[作用域契约](docs/TASK-SCOPE-HISTORY-CONTRACT.md)、[采用记录](docs/DSH-UX-001-ADOPTION.md)和[兼容矩阵](docs/COMPATIBILITY.md)。
3
+ 0.1.24 修复原生首页与历史交互:左侧菜单名称在打开期间保持“访前尽调”,新版 Host 布局下首页稳定显示“访前尽调一页纸智能体”,历史任务可按来源 Session 打开详情、查看及下载已保存报告。已发布版本与验证边界见 [发布说明](docs/RELEASE-0.1.24.md)、[作用域契约](docs/TASK-SCOPE-HISTORY-CONTRACT.md)、[采用记录](docs/DSH-UX-001-ADOPTION.md)和[兼容矩阵](docs/COMPATIBILITY.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.23
10
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.24
11
11
  ```
12
12
 
13
13
  请先满足下文的 DSH 与连接器要求;Better Sidebar 仅在需要可视化工作台时选装。安装后完整停止并重启对应 Profile。
@@ -23,7 +23,7 @@ dsh plugin --profile web add dsh-pre-duediligence@0.1.23
23
23
  相关智能体:[数据清洗补全](https://github.com/duhu2000/dsh-data-cleaning-agent) · [AI填表](https://github.com/duhu2000/dsh-form-fill-agent) · [访前尽调](https://github.com/duhu2000/dsh-pre-duediligence) · [招投标](https://github.com/duhu2000/dsh-tender-workbench)
24
24
 
25
25
 
26
- 版本:**0.1.23**。本版保持 DSH-UX-001 v1.5.2 的 Workspace / Session 隔离、工作台初始关闭和核验完成门;当前任务、阶段进度、报告回写及下载仍绑定当前 Session,任务历史则从 Host 持久库汇总当前 Profile 的全部访前 Session。每条历史记录显示来源 Workspace / Session;旧记录缺少来源时明确显示“未记录(旧记录)”,只进入 Profile 历史且不会被当前 Session 认领。完整消息与证据仍保留在来源原生会话。本轮自动化使用合成 Host 记录,不声明生产账号全链路验收通过。详见 [更新日志](CHANGELOG.md)、[作用域契约](docs/TASK-SCOPE-HISTORY-CONTRACT.md)、[采用记录](docs/DSH-UX-001-ADOPTION.md)、[兼容与验收矩阵](docs/COMPATIBILITY.md)和[市场投稿登记](docs/MARKETPLACE.md)。
26
+ 版本:**0.1.24**。本版保持 DSH-UX-001 v1.5.2 的 Workspace / Session 隔离、工作台初始关闭和核验完成门,并修复 Host 布局变化后的首页标题定位、菜单加载态文案以及历史详情与报告下载。当前任务、阶段进度、报告回写及下载仍绑定当前 Session;历史详情只读取记录所属 Session,不重新调用企查查。旧记录缺少来源时继续明确显示“未记录(旧记录)”,只进入 Profile 历史且不会被当前 Session 认领。本轮自动化使用合成 Host 记录,不声明生产账号全链路验收通过。详见 [更新日志](CHANGELOG.md)、[作用域契约](docs/TASK-SCOPE-HISTORY-CONTRACT.md)、[采用记录](docs/DSH-UX-001-ADOPTION.md)、[兼容与验收矩阵](docs/COMPATIBILITY.md)和[市场投稿登记](docs/MARKETPLACE.md)。
27
27
 
28
28
  面向 DeepSeek Harness 的 Session 级访前尽调智能体。业务人员从左侧菜单进入,在会话级工作台定义一次拜访;Agent 调用企查查五类 MCP,使用机会与风险双引擎完成经营状态识别、假设与反证、风险核验,最终交付可追溯的访前尽调报告。
29
29
 
@@ -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.23`:提供左侧智能体入口、原生会话首页、提示词生成器、Host 执行门和 Skill。
95
+ 2. `dsh-pre-duediligence@0.1.24`:提供左侧智能体入口、原生会话首页、提示词生成器、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.23 --allow-build=node-pty,dsh-pre-duediligence
123
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.24 --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.23`;仅在显式启用或原本已安装时看到 Sidebar。重启 DSH 并连接“企查查·企业工商”后,在左侧菜单点击“访前尽调”进入初始原生会话。无 Sidebar 时可继续生成提示词、回填/发送草稿并在会话中阅读报告;五项流程按钮会提示安装可选工作台。安装 Sidebar 时,右侧工作台仍默认关闭,点击业务按钮后才打开对应视图;其右侧“+”菜单不会列出该入口,普通会话保持 DSH 原样。
139
+ 应始终看到所选基线的 Connector 和 `dsh-pre-duediligence@0.1.24`;仅在显式启用或原本已安装时看到 Sidebar。重启 DSH 并连接“企查查·企业工商”后,在左侧菜单点击“访前尽调”进入初始原生会话。无 Sidebar 时可继续生成提示词、回填/发送草稿并在会话中阅读报告;五项流程按钮会提示安装可选工作台。安装 Sidebar 时,右侧工作台仍默认关闭,点击业务按钮后才打开对应视图;其右侧“+”菜单不会列出该入口,普通会话保持 DSH 原样。
140
140
 
141
141
  ### 无 Sidebar 功能边界
142
142
 
@@ -1,5 +1,15 @@
1
1
  # 访前尽调兼容与验收记录
2
2
 
3
+ ## 0.1.24:Host 首页布局与历史详情(2026-09-13)
4
+
5
+ | 实测问题 | 修复契约 | 验证边界 |
6
+ | --- | --- | --- |
7
+ | 菜单点击后显示“正在打开…” | 可见菜单名固定为“访前尽调”,加载状态只进入无障碍与控件状态 | 重复点击仍由 busy guard 阻止,不创建重复 Session |
8
+ | 首页退回“探索未至之境 预览版” | 兼容 marker 与 Hero 为兄弟分支的 Host DOM,监听同一页面范围内的宿主重渲染 | 只改写本插件专属空白会话,卸载时恢复原文案与标记 |
9
+ | 历史清单不能进入任务与下载报告 | 以历史记录自身的来源 Session 读取详情和报告;返回清单不改变当前任务 | 来源缺失的旧记录仍只能看摘要,不猜测 Session |
10
+
11
+ 本地 Node 24.19.0 门禁覆盖严格类型检查、全量自动化测试、Host/Client 构建,以及浅色/深色 × 桌面/窄屏 4 个隔离 Chrome 场景。真实 DSH 与生产企查查 Provider 仍以安装后的用户验收为准。
12
+
3
13
  ## 0.1.23:旧记录来源与显式 TaskScope(2026-09-13)
4
14
 
5
15
  | 实测问题 | 修复契约 | 验证边界 |
@@ -1,5 +1,15 @@
1
1
  # DSH-UX-001 采纳与验收记录
2
2
 
3
+ ## 0.1.24:稳定入口、首页品牌与历史详情
4
+
5
+ 日期:2026-09-13。
6
+
7
+ | 交互要求 | 当前实现 |
8
+ | --- | --- |
9
+ | 菜单名称不能被加载或错误状态替换 | 左侧入口始终显示“访前尽调”;处理中仅使用 `aria-busy` 与禁用态,错误通过标题提示 |
10
+ | Host 调整 Hero 与 composer slot 层级后仍要显示业务首页 | 从本插件 composer seat 向上定位同一首页标题,宿主重渲染后继续同步“访前尽调一页纸智能体”、业务 LOGO 与预览标记 |
11
+ | 历史任务必须能打开和下载 | 携带该记录自身来源 Session 读取只读详情、内嵌报告与下载制品;不会重新查询或污染当前任务 |
12
+
3
13
  ## 0.1.23:旧记录兼容与 TaskScope 契约
4
14
 
5
15
  日期:2026-09-13。
@@ -7,8 +7,8 @@
7
7
  | 项目 | 已核验值 |
8
8
  | --- | --- |
9
9
  | 仓库 | `https://github.com/duhu2000/dsh-pre-duediligence`,公开、未归档 |
10
- | npm | `dsh-pre-duediligence@0.1.23`,发布后核验 `latest=0.1.23` |
11
- | Release | `https://github.com/duhu2000/dsh-pre-duediligence/releases/tag/v0.1.23` |
10
+ | npm | `dsh-pre-duediligence@0.1.24`,发布后核验 `latest=0.1.24` |
11
+ | Release | `https://github.com/duhu2000/dsh-pre-duediligence/releases/tag/v0.1.24` |
12
12
  | 许可证 | MIT |
13
13
  | Topic | `dsh-plugin` |
14
14
  | Manifest | `package.json` 的 `dsh.bundle.patch` 指向 `./cordis.patch.yml` |
@@ -28,7 +28,7 @@
28
28
  - 企查查数据采用 BYO QCC:用户需要自行安装并配置兼容的 MCP Connector,使用自己的企查查账号、产品权限和可用额度完成 OAuth;本插件不附带账号、数据权限或查询额度。
29
29
  - 多候选时需要用户确认唯一法律实体;用户发送任务即表示同意固定路由内连续执行,不再二次询问,也不受插件调用次数硬上限阻断。
30
30
  - 零记录、无权限、额度不足和调用失败是不同状态;未执行或失败的维度不得写成“零风险”。
31
- - 0.1.23 已补齐缺少来源元数据的旧 Host 记录兼容,以显式 TaskScope 保持当前任务严格按 Session 隔离,并完成 Session A/B/legacy 契约、构建和隔离 UI 回归;生产账号端到端复测仍以用户验收为准,市场描述不把待验项写成已通过。
31
+ - 0.1.24 保持显式 TaskScope 与旧记录只读边界,并补齐稳定菜单名、Host 新版首页标题定位及历史详情/报告下载;生产账号端到端复测仍以用户验收为准,市场描述不把待验项写成已通过。
32
32
 
33
33
  ## 安装、卸载与截图证据
34
34
 
@@ -0,0 +1,22 @@
1
+ # v0.1.24 发布说明
2
+
3
+ 本版修复访前尽调入口、原生首页与历史详情在新版 Host 布局中的交互问题。
4
+
5
+ ## 修复
6
+
7
+ - 左侧入口在创建专属 Session 期间始终显示“访前尽调”;不再改成“正在打开…”或错误文案。
8
+ - 原生首页兼容业务 marker 与 Hero 标题位于 composer sibling 分支的 Host DOM,稳定替换为“访前尽调一页纸智能体”、企查查蓝建筑 LOGO,并可在宿主重渲染后恢复。
9
+ - 历史任务卡片可打开只读详情,内嵌查看已保存报告,并使用该记录自己的来源 Session 下载 HTML 制品。
10
+ - 缺少来源 Workspace / Session 的旧记录继续保留清单摘要,不猜测来源、不绑定当前任务。
11
+
12
+ ## 验证
13
+
14
+ - 菜单文案、Hero sibling slot、宿主重渲染与可逆清理均有回归覆盖。
15
+ - 历史任务详情验证读取记录所属 Session,并覆盖详情、下载与返回清单交互。
16
+ - 严格类型检查、全量单元/集成测试、Host/Client 构建与隔离 Chrome UI 门禁纳入发布流程。
17
+
18
+ 本轮使用合成 Host 记录和隔离浏览器页面验证,没有读取或修改生产 DSH Profile,也没有调用真实企查查 Provider;生产账号结果仍以安装后的用户验收为准。
19
+
20
+ ```sh
21
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.24
22
+ ```
@@ -12,14 +12,14 @@ npm keywords 已在包清单内更新。GitHub description 建议使用上述英
12
12
 
13
13
  验收:使用实时完整目录和原版搜索函数对比登记描述更新前后;记录语言、命中位置、结果数与版本。独立目录缺失条目必须标为未上线,不能把模拟添加的结果当作上线结果。
14
14
 
15
- 发布清单:0.1.23 已获得发布授权;完成仓库 check、README 与包清单差异审核后,以新的不可变 tag 和 npm 版本发布,不覆盖旧版本或移动旧 tag。仅登记 YAML 更新仍无需重复发布 npm。
15
+ 发布清单:0.1.24 已获得发布授权;完成仓库 check、README 与包清单差异审核后,以新的不可变 tag 和 npm 版本发布,不覆盖旧版本或移动旧 tag。仅登记 YAML 更新仍无需重复发布 npm。
16
16
 
17
17
  ## 安装与三分钟上手
18
18
 
19
19
  访前尽调智能体:面向拜访前调查,提供企业尽调、客户尽调、客户背景调查、工商核验与风险信息整理;使用企查查 MCP,提供固定路由连续调用、主体确认、动态进度和可下载报告。
20
20
 
21
21
  ```sh
22
- dsh plugin --profile web add dsh-pre-duediligence@0.1.23
22
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.24
23
23
  ```
24
24
 
25
25
  请先满足下文的 DSH 与连接器要求;Better Sidebar 只在需要可视化工作台时选装。安装后完整停止并重启对应 Profile。
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.23}"
8
+ readonly PREVISIT_SPEC="${DSH_PRE_DUEDILIGENCE_SPEC:-dsh-pre-duediligence@0.1.24}"
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
@@ -1233,7 +1233,7 @@ function LeftSidebarEntry(props) {
1233
1233
  },
1234
1234
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "qccPrevisitLauncherContent", children: [
1235
1235
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PrevisitLogo, { size: 18 }),
1236
- wide ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: busy ? "\u6B63\u5728\u6253\u5F00\u2026" : error === void 0 ? "\u8BBF\u524D\u5C3D\u8C03" : "\u6253\u5F00\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5" }) : null
1236
+ wide ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "\u8BBF\u524D\u5C3D\u8C03" }) : null
1237
1237
  ] })
1238
1238
  }
1239
1239
  );
@@ -1334,42 +1334,82 @@ function installCapabilityMount(marker, onMount) {
1334
1334
  for (const node of owned) node.remove();
1335
1335
  };
1336
1336
  }
1337
- function setPrevisitHeadline(anchor) {
1338
- const hero = anchor.closest('[data-phase="hero"]');
1339
- const title = hero?.querySelector('[class*="headlineText"]');
1340
- if (title === null || title === void 0) return () => {
1337
+ function resolvePrevisitHeroChrome(anchor) {
1338
+ const direct = anchor.closest('[data-phase="hero"]');
1339
+ const findTitle = (scope2) => {
1340
+ const spans = [...scope2.querySelectorAll("span")];
1341
+ return spans.find((node) => node.dataset.previsitHeroTitle === "true") ?? spans.find((node) => ["\u63A2\u7D22\u672A\u81F3\u4E4B\u5883", "Into the Unknown"].includes(node.textContent?.trim() ?? "")) ?? scope2.querySelector('[class*="headlineText"]');
1341
1342
  };
1342
- const originalTitle = title.textContent;
1343
- const row = title.parentElement;
1344
- const nativeMark = row?.querySelector('[class*="fishHitbox"]');
1345
- const badge = hero === null || hero === void 0 ? void 0 : [...hero.querySelectorAll("span")].find((node) => ["\u9884\u89C8\u7248", "Preview"].includes(node.textContent?.trim() ?? ""));
1346
- const originalMarkDisplay = nativeMark?.style.display;
1347
- const originalBadgeDisplay = badge?.style.display;
1348
- const originalRowFlag = row?.getAttribute("data-previsit-hero-row") ?? null;
1349
- let logo = null;
1350
- title.textContent = PREVISIT_HOME_TITLE;
1351
- title.dataset.previsitHeroTitle = "true";
1352
- if (row !== null && row !== void 0 && nativeMark !== null && nativeMark !== void 0 && typeof document !== "undefined") {
1353
- nativeMark.style.display = "none";
1354
- row.dataset.previsitHeroRow = "true";
1355
- logo = document.createElement("span");
1356
- logo.className = "qccPrevisitHeroLogo";
1357
- logo.dataset.previsitOwned = "true";
1358
- logo.setAttribute("aria-hidden", "true");
1359
- logo.innerHTML = `<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="${PREVISIT_LOGO_PATH}"></path></svg>`;
1360
- row.insertBefore(logo, title);
1343
+ if (direct !== null) {
1344
+ const title = findTitle(direct);
1345
+ if (title !== null) return { scope: direct, title };
1346
+ }
1347
+ let scope = anchor.closest("[data-composer-seat]") ?? anchor.parentElement;
1348
+ while (scope !== null) {
1349
+ const title = findTitle(scope);
1350
+ if (title !== null) return { scope, title };
1351
+ scope = scope.parentElement;
1361
1352
  }
1362
- if (badge !== void 0) badge.style.display = "none";
1353
+ return null;
1354
+ }
1355
+ function setPrevisitHeadline(anchor) {
1356
+ const originalTitles = /* @__PURE__ */ new Map();
1357
+ const originalMarks = /* @__PURE__ */ new Map();
1358
+ const originalBadges = /* @__PURE__ */ new Map();
1359
+ const originalRows = /* @__PURE__ */ new Map();
1360
+ const logos = /* @__PURE__ */ new Map();
1361
+ let observer = null;
1362
+ const sync = () => {
1363
+ const chrome = resolvePrevisitHeroChrome(anchor);
1364
+ if (chrome === null) return;
1365
+ const { scope, title } = chrome;
1366
+ if (!originalTitles.has(title)) originalTitles.set(title, title.textContent);
1367
+ title.dataset.previsitHeroTitle = "true";
1368
+ if (title.textContent !== PREVISIT_HOME_TITLE) title.textContent = PREVISIT_HOME_TITLE;
1369
+ const row = title.parentElement;
1370
+ const nativeMark = row?.querySelector('[class*="fishHitbox"]');
1371
+ if (row !== null && row !== void 0 && nativeMark !== null && nativeMark !== void 0 && typeof document !== "undefined") {
1372
+ if (!originalRows.has(row)) originalRows.set(row, row.getAttribute("data-previsit-hero-row"));
1373
+ if (!originalMarks.has(nativeMark)) originalMarks.set(nativeMark, nativeMark.style.display);
1374
+ nativeMark.style.display = "none";
1375
+ row.dataset.previsitHeroRow = "true";
1376
+ if (!logos.get(row)?.isConnected) {
1377
+ const logo = document.createElement("span");
1378
+ logo.className = "qccPrevisitHeroLogo";
1379
+ logo.dataset.previsitOwned = "true";
1380
+ logo.setAttribute("aria-hidden", "true");
1381
+ logo.innerHTML = `<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="${PREVISIT_LOGO_PATH}"></path></svg>`;
1382
+ row.insertBefore(logo, title);
1383
+ logos.set(row, logo);
1384
+ }
1385
+ }
1386
+ const badge = [...scope.querySelectorAll("span")].find((node) => node.dataset.previsitHeroBadge === "true" || ["\u9884\u89C8\u7248", "Preview"].includes(node.textContent?.trim() ?? ""));
1387
+ if (badge !== void 0) {
1388
+ if (!originalBadges.has(badge)) originalBadges.set(badge, badge.style.display);
1389
+ badge.dataset.previsitHeroBadge = "true";
1390
+ badge.style.display = "none";
1391
+ }
1392
+ };
1393
+ sync();
1394
+ const observationRoot = resolvePrevisitHeroChrome(anchor)?.scope ?? (typeof document === "undefined" ? null : document.body) ?? anchor.parentElement;
1395
+ observer = typeof MutationObserver === "function" ? new MutationObserver(sync) : null;
1396
+ if (observationRoot !== null) observer?.observe(observationRoot, { childList: true, subtree: true });
1363
1397
  return () => {
1364
- logo?.remove();
1365
- if (nativeMark !== null && nativeMark !== void 0) nativeMark.style.display = originalMarkDisplay ?? "";
1366
- if (badge !== void 0) badge.style.display = originalBadgeDisplay ?? "";
1367
- if (row !== null && row !== void 0) {
1368
- if (originalRowFlag === null) row.removeAttribute("data-previsit-hero-row");
1369
- else row.setAttribute("data-previsit-hero-row", originalRowFlag);
1398
+ observer?.disconnect();
1399
+ for (const logo of logos.values()) logo.remove();
1400
+ for (const [mark, display] of originalMarks) mark.style.display = display;
1401
+ for (const [badge, display] of originalBadges) {
1402
+ badge.style.display = display;
1403
+ delete badge.dataset.previsitHeroBadge;
1404
+ }
1405
+ for (const [row, flag] of originalRows) {
1406
+ if (flag === null) row.removeAttribute("data-previsit-hero-row");
1407
+ else row.setAttribute("data-previsit-hero-row", flag);
1408
+ }
1409
+ for (const [title, text] of originalTitles) {
1410
+ if (title.textContent === PREVISIT_HOME_TITLE) title.textContent = text;
1411
+ delete title.dataset.previsitHeroTitle;
1370
1412
  }
1371
- if (title.textContent === PREVISIT_HOME_TITLE) title.textContent = originalTitle;
1372
- delete title.dataset.previsitHeroTitle;
1373
1413
  };
1374
1414
  }
1375
1415
  function PrevisitHome({ sessionId, useSession, openWorkbench: openWorkbench2 }) {
@@ -1899,6 +1939,8 @@ html[data-theme="dark"] .qccPrevisitHeroLogo,html.dark .qccPrevisitHeroLogo{colo
1899
1939
  .qccPwEmpty,.qccPwNote{margin:0;color:var(--qcc-secondary);font-size:12px;line-height:1.65}.qccPwNote{padding:10px 12px;border-left:3px solid var(--qcc-brand);background:var(--qcc-selected)}
1900
1940
  .qccPwHistorySource{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1901
1941
  .qccPwHistorySource[data-complete="false"]{font-style:italic}
1942
+ .qccPwHistoryCard{display:block;width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer}.qccPwHistoryCard:hover:not(:disabled){border-color:var(--qcc-brand);box-shadow:0 2px 8px color-mix(in srgb,var(--qcc-brand) 10%,transparent)}.qccPwHistoryCard:focus-visible{outline:3px solid var(--qcc-brand);outline-offset:2px}.qccPwHistoryCard:disabled{cursor:not-allowed;opacity:.68}.qccPwHistoryOpen{display:block;margin-top:9px;color:var(--qcc-action);font-size:11px;font-weight:650;text-align:right}.qccPwHistoryCard:disabled .qccPwHistoryOpen{color:var(--qcc-secondary)}
1943
+ .qccPwHistoryHeading{align-items:center}.qccPwHistoryActions{display:flex;justify-content:flex-end;margin-top:12px}.qccPwHistoryActions .qccPwPrimary:disabled{border-color:var(--qcc-border);background:var(--qcc-page);color:var(--qcc-secondary);cursor:not-allowed}
1902
1944
  .qccPwStateStrip{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.qccPwState{display:grid;gap:3px;padding:7px;border:1px solid var(--qcc-border);border-radius:7px;background:var(--qcc-surface);color:var(--qcc-secondary);text-align:center}.qccPwState b{font-size:10px;font-weight:600}.qccPwState small{font-size:8px;color:var(--qcc-secondary)}.qccPwState[data-state="pending"]{border-style:dashed}.qccPwState[data-state="selected"]{border-color:var(--qcc-brand);background:var(--qcc-selected);color:var(--qcc-action)}.qccPwState[data-state="selected"] b{font-weight:700}.qccPwState[data-state="selected"] small{color:var(--qcc-action)}.qccPwState[data-state="excluded"]{background:var(--qcc-page)}.qccPwState[data-state="undetermined"]{border-color:var(--qcc-review);background:var(--qcc-review-bg);color:var(--qcc-review)}.qccPwState[data-state="undetermined"] small{color:var(--qcc-review)}
1903
1945
  .qccPwHypos{display:grid;gap:8px;margin:0;padding:0;list-style:none}.qccPwHypos li{display:grid;grid-template-columns:auto auto 1fr;align-items:start;gap:8px;padding:9px;border-radius:8px;background:var(--qcc-page);font-size:11px;line-height:1.55}.qccPwPri{padding:2px 5px;border-radius:5px;background:var(--qcc-table-head);color:var(--qcc-secondary)}.qccPwPri[data-p="P0"]{background:var(--qcc-danger-bg);color:var(--qcc-danger)}.qccPwPri[data-p="P1"]{background:var(--qcc-review-bg);color:var(--qcc-review)}
1904
1946
  .qccPwRiskTiles{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.qccPwRiskTile{padding:11px;border:1px solid var(--qcc-border);border-radius:9px;background:var(--qcc-page)}.qccPwRiskTileTop{display:flex;justify-content:space-between;gap:8px}.qccPwRiskTileTop b,.qccPwRiskTileTop strong{font-size:12px}.qccPwRiskTile p{margin:6px 0 0;color:var(--qcc-secondary);font-size:10px;line-height:1.5}.qccPwRiskTile[data-clear="true"]{border-color:var(--qcc-success);background:var(--qcc-success-bg)}.qccPwRiskTile[data-clear="true"] .qccPwRiskTileTop{color:var(--qcc-success)}.qccPwRiskTile[data-level="红线"]:not([data-empty="true"]){border-color:var(--qcc-danger);background:var(--qcc-danger-bg)}.qccPwRiskTile[data-level="关注"]:not([data-empty="true"]){border-color:var(--qcc-review);background:var(--qcc-review-bg)}.qccPwRiskTile[data-level="信息"]:not([data-empty="true"]){border-color:var(--qcc-brand);background:var(--qcc-selected)}.qccPwRiskBoundary{grid-column:1/-1;margin:0;color:var(--qcc-secondary);font-size:10px;line-height:1.5}
@@ -2527,24 +2569,29 @@ function DeliveryPanel(props) {
2527
2569
  ] });
2528
2570
  }
2529
2571
  function HistoryPanel(props) {
2530
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "qccPwPanel", children: [
2531
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("header", { className: "qccPwPageHeading", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2532
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwEyebrow", children: "HISTORY" }),
2533
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { children: "\u4EFB\u52A1\u5386\u53F2" }),
2534
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "\u6C47\u603B\u5F53\u524D DSH Profile \u4E2D\u6240\u6709\u8BBF\u524D\u5C3D\u8C03 Session\uFF1B\u4EFB\u52A1\u72B6\u6001\u4E0E\u62A5\u544A\u5236\u54C1\u7531 Host \u4FDD\u5B58\u3002" })
2535
- ] }) }),
2536
- props.hosted.length > 0 ? props.hosted.map((item) => {
2537
- const status = hostedStatus(item);
2538
- const origin = hostedTaskOrigin(item);
2539
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwCard", children: [
2572
+ const selectedReport = props.selected?.reportMarkdown?.trim();
2573
+ if (props.selected !== null) {
2574
+ const item = props.selected;
2575
+ const status = hostedStatus(item);
2576
+ const origin = hostedTaskOrigin(item);
2577
+ const reportHtml = selectedReport === void 0 || selectedReport === "" ? null : buildPrevisitReportHtml(selectedReport);
2578
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "qccPwPanel", children: [
2579
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("header", { className: "qccPwPageHeading qccPwHistoryHeading", children: [
2580
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2581
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwEyebrow", children: "HISTORY DETAIL" }),
2582
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { children: item.entity?.fullName ?? item.query }),
2583
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { children: [
2584
+ item.id,
2585
+ " \xB7 ",
2586
+ new Date(item.createdAt).toLocaleString("zh-CN")
2587
+ ] })
2588
+ ] }),
2589
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "qccPwSecondary", onClick: props.onBack, children: "\u8FD4\u56DE\u6E05\u5355" })
2590
+ ] }),
2591
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwCard", children: [
2540
2592
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwCardHeader", children: [
2541
2593
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2542
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: item.entity?.fullName ?? item.query }),
2543
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { children: [
2544
- item.id,
2545
- " \xB7 ",
2546
- new Date(item.createdAt).toLocaleString("zh-CN")
2547
- ] }),
2594
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: "\u4EFB\u52A1\u8BE6\u60C5" }),
2548
2595
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwHistorySource", "data-complete": origin.complete, title: origin.label, children: origin.label })
2549
2596
  ] }),
2550
2597
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "qccPwStatus", "data-status": status, children: STATUS_LABELS[status] })
@@ -2556,8 +2603,67 @@ function HistoryPanel(props) {
2556
2603
  item.runs.filter((run) => run.status === "failed").length,
2557
2604
  " \u4E2A\u9519\u8BEF",
2558
2605
  item.completedAt === void 0 ? "" : ` \xB7 \u5B8C\u6210\u4E8E ${new Date(item.completedAt).toLocaleString("zh-CN")}`
2559
- ] })
2560
- ] }, item.id);
2606
+ ] }),
2607
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "qccPwHistoryActions", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2608
+ "button",
2609
+ {
2610
+ type: "button",
2611
+ className: "qccPwPrimary",
2612
+ disabled: !item.reportReady || !origin.complete || props.downloadingTaskId === item.id,
2613
+ title: !origin.complete ? "\u65E7\u8BB0\u5F55\u7F3A\u5C11\u6765\u6E90 Session\uFF0C\u65E0\u6CD5\u5B89\u5168\u4E0B\u8F7D" : item.reportReady ? "\u4E0B\u8F7D\u5DF2\u4FDD\u5B58\u7684 HTML \u62A5\u544A" : "\u62A5\u544A\u5C1A\u672A\u751F\u6210",
2614
+ onClick: () => props.onDownload(item),
2615
+ children: props.downloadingTaskId === item.id ? "\u6B63\u5728\u4E0B\u8F7D\u2026" : "\u4E0B\u8F7D\u62A5\u544A \u2193"
2616
+ }
2617
+ ) })
2618
+ ] }),
2619
+ reportHtml === null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Feedback, { tone: "notice", title: item.reportReady ? "\u6B63\u5728\u8BFB\u53D6\u62A5\u544A" : "\u62A5\u544A\u5C1A\u672A\u751F\u6210", children: item.reportReady ? "\u5DF2\u627E\u5230\u62A5\u544A\u5236\u54C1\uFF0C\u4F46\u6B63\u6587\u6682\u672A\u8FD4\u56DE\uFF1B\u8BF7\u8FD4\u56DE\u6E05\u5355\u540E\u91CD\u8BD5\u3002" : "\u4EFB\u52A1\u8BE6\u60C5\u5DF2\u6062\u590D\uFF0C\u62A5\u544A\u751F\u6210\u540E\u53EF\u5728\u8FD9\u91CC\u67E5\u770B\u5E76\u4E0B\u8F7D\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "qccPwCard qccPwReportCard", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ReportViewer, { html: reportHtml }) }),
2620
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwNote", children: "\u5386\u53F2\u8BE6\u60C5\u53EA\u8BFB\u53D6\u8BE5\u4EFB\u52A1\u6240\u5C5E Session \u7684 Host \u5236\u54C1\uFF0C\u4E0D\u4F1A\u91CD\u65B0\u8C03\u7528\u4F01\u67E5\u67E5\uFF0C\u4E5F\u4E0D\u4F1A\u8986\u76D6\u5F53\u524D\u4F1A\u8BDD\u4EFB\u52A1\u3002" })
2621
+ ] });
2622
+ }
2623
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "qccPwPanel", children: [
2624
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("header", { className: "qccPwPageHeading", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2625
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwEyebrow", children: "HISTORY" }),
2626
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { children: "\u4EFB\u52A1\u5386\u53F2" }),
2627
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "\u6C47\u603B\u5F53\u524D DSH Profile \u4E2D\u6240\u6709\u8BBF\u524D\u5C3D\u8C03 Session\uFF1B\u4EFB\u52A1\u72B6\u6001\u4E0E\u62A5\u544A\u5236\u54C1\u7531 Host \u4FDD\u5B58\u3002" })
2628
+ ] }) }),
2629
+ props.hosted.length > 0 ? props.hosted.map((item) => {
2630
+ const status = hostedStatus(item);
2631
+ const origin = hostedTaskOrigin(item);
2632
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2633
+ "button",
2634
+ {
2635
+ type: "button",
2636
+ className: "qccPwCard qccPwHistoryCard",
2637
+ disabled: !origin.complete || props.loadingTaskId === item.id,
2638
+ title: origin.complete ? "\u6253\u5F00\u4EFB\u52A1\u8BE6\u60C5\u4E0E\u5DF2\u4FDD\u5B58\u62A5\u544A" : "\u65E7\u8BB0\u5F55\u7F3A\u5C11\u6765\u6E90 Session\uFF0C\u53EA\u80FD\u67E5\u770B\u6E05\u5355\u6458\u8981",
2639
+ "aria-label": `\u67E5\u770B${item.entity?.fullName ?? item.query}\u7684\u4EFB\u52A1\u8BE6\u60C5`,
2640
+ onClick: () => props.onOpen(item),
2641
+ children: [
2642
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwCardHeader", children: [
2643
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2644
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: item.entity?.fullName ?? item.query }),
2645
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { children: [
2646
+ item.id,
2647
+ " \xB7 ",
2648
+ new Date(item.createdAt).toLocaleString("zh-CN")
2649
+ ] }),
2650
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwHistorySource", "data-complete": origin.complete, title: origin.label, children: origin.label })
2651
+ ] }),
2652
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "qccPwStatus", "data-status": status, children: STATUS_LABELS[status] })
2653
+ ] }),
2654
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "qccPwNote", children: [
2655
+ "\u4F01\u67E5\u67E5\u67E5\u8BE2 ",
2656
+ item.used,
2657
+ " \u6B21 \xB7 ",
2658
+ item.runs.filter((run) => run.status === "failed").length,
2659
+ " \u4E2A\u9519\u8BEF",
2660
+ item.completedAt === void 0 ? "" : ` \xB7 \u5B8C\u6210\u4E8E ${new Date(item.completedAt).toLocaleString("zh-CN")}`
2661
+ ] }),
2662
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "qccPwHistoryOpen", children: props.loadingTaskId === item.id ? "\u6B63\u5728\u6253\u5F00\u2026" : origin.complete ? "\u67E5\u770B\u8BE6\u60C5 \u2192" : "\u4EC5\u6458\u8981" })
2663
+ ]
2664
+ },
2665
+ item.id
2666
+ );
2561
2667
  }) : props.task === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Feedback, { tone: "notice", title: "\u6682\u65E0\u5386\u53F2\u4EFB\u52A1", children: "\u4ECE\u63D0\u793A\u8BCD\u751F\u6210\u5668\u56DE\u586B\u5E76\u53D1\u9001\uFF0C\u6216\u5728\u4EFB\u4E00\u8BBF\u524D\u4F1A\u8BDD\u4E2D\u76F4\u63A5\u53D1\u8D77\u5C3D\u8C03\u540E\uFF0C\u8FD9\u91CC\u4F1A\u6C47\u603B\u663E\u793A\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwCard", children: [
2562
2668
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwCardHeader", children: [
2563
2669
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
@@ -2568,7 +2674,7 @@ function HistoryPanel(props) {
2568
2674
  ] }),
2569
2675
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { className: "qccPwPrompt", children: props.task.prompt })
2570
2676
  ] }),
2571
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwNote", children: "\u8FD9\u91CC\u8DE8\u8BBF\u524D Session \u6C47\u603B Host \u4EFB\u52A1\u8BB0\u5F55\uFF1B\u5B8C\u6574\u5BF9\u8BDD\u548C\u8BC1\u636E\u5F15\u7528\u4ECD\u4FDD\u7559\u5728\u5404\u81EA\u7684 DSH \u539F\u751F\u4F1A\u8BDD\u4E2D\u3002" })
2677
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwNote", children: "\u70B9\u51FB\u4EFB\u52A1\u53EF\u6062\u590D\u8BE6\u60C5\u5E76\u67E5\u770B\u3001\u4E0B\u8F7D\u5DF2\u751F\u6210\u62A5\u544A\uFF1B\u4E0D\u4F1A\u91CD\u65B0\u6267\u884C\u67E5\u8BE2\u3002\u5B8C\u6574\u5BF9\u8BDD\u548C\u8BC1\u636E\u5F15\u7528\u4ECD\u4FDD\u7559\u5728\u5404\u81EA\u7684 DSH \u539F\u751F\u4F1A\u8BDD\u4E2D\u3002" })
2572
2678
  ] });
2573
2679
  }
2574
2680
  function PrevisitWorkbenchTab(props) {
@@ -2584,6 +2690,9 @@ function PrevisitWorkbenchTab(props) {
2584
2690
  const [runtime, setRuntime] = (0, import_react6.useState)(EMPTY_RUNTIME);
2585
2691
  const [hostedTask, setHostedTask] = (0, import_react6.useState)(null);
2586
2692
  const [hostedHistory, setHostedHistory] = (0, import_react6.useState)([]);
2693
+ const [selectedHistory, setSelectedHistory] = (0, import_react6.useState)(null);
2694
+ const [loadingHistoryTaskId, setLoadingHistoryTaskId] = (0, import_react6.useState)();
2695
+ const [downloadingTaskId, setDownloadingTaskId] = (0, import_react6.useState)();
2587
2696
  const [hostError, setHostError] = (0, import_react6.useState)();
2588
2697
  const reconcilingReports = (0, import_react6.useRef)(/* @__PURE__ */ new Set());
2589
2698
  const lastHostedLocation = (0, import_react6.useRef)();
@@ -2654,6 +2763,12 @@ function PrevisitWorkbenchTab(props) {
2654
2763
  if (timer !== void 0) clearTimeout(timer);
2655
2764
  };
2656
2765
  }, [props.visible, shared.view, sessionId]);
2766
+ (0, import_react6.useEffect)(() => {
2767
+ if (shared.view !== "history") {
2768
+ setSelectedHistory(null);
2769
+ setLoadingHistoryTaskId(void 0);
2770
+ }
2771
+ }, [shared.view]);
2657
2772
  (0, import_react6.useEffect)(() => {
2658
2773
  const face = props.ctx.sessions.binding?.(sessionId)?.session;
2659
2774
  if (face === void 0) {
@@ -2747,6 +2862,58 @@ function PrevisitWorkbenchTab(props) {
2747
2862
  setCapturedReport(null);
2748
2863
  setHostedTask(null);
2749
2864
  };
2865
+ const saveReportResponse = async (record, ownerSessionId) => {
2866
+ const response = await fetch(`/previsit/api/tasks/${encodeURIComponent(record.id)}/report?sessionId=${encodeURIComponent(ownerSessionId)}`, { headers: { accept: "text/html" } });
2867
+ if (!response.ok) {
2868
+ let message = `\u62A5\u544A\u4E0B\u8F7D\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`;
2869
+ try {
2870
+ const payload = await response.json();
2871
+ if (payload.message !== void 0) message = payload.message;
2872
+ } catch {
2873
+ }
2874
+ throw new Error(message);
2875
+ }
2876
+ const blob = await response.blob();
2877
+ const url = URL.createObjectURL(blob);
2878
+ const anchor = document.createElement("a");
2879
+ anchor.href = url;
2880
+ anchor.download = record.artifact?.fileName ?? `\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A_${record.entity?.fullName ?? record.query}.html`;
2881
+ document.body.append(anchor);
2882
+ anchor.click();
2883
+ anchor.remove();
2884
+ setTimeout(() => URL.revokeObjectURL(url), 0);
2885
+ };
2886
+ const openHistoryTask = async (record) => {
2887
+ const origin = hostedTaskOrigin(record);
2888
+ if (!origin.complete) {
2889
+ setDownloadNote("\u8BE5\u65E7\u8BB0\u5F55\u7F3A\u5C11 Workspace / Session \u6765\u6E90\uFF0C\u53EA\u80FD\u67E5\u770B\u6E05\u5355\u6458\u8981\uFF0C\u65E0\u6CD5\u5B89\u5168\u8BFB\u53D6\u8BE6\u60C5\u3002");
2890
+ return;
2891
+ }
2892
+ setLoadingHistoryTaskId(record.id);
2893
+ setDownloadNote(void 0);
2894
+ try {
2895
+ const detail = await fetchHostedTask(record.id, record.sessionId);
2896
+ if (detail === null) throw new Error("\u8BE5\u5386\u53F2\u4EFB\u52A1\u5DF2\u4E0D\u5B58\u5728\u6216\u5F53\u524D Profile \u65E0\u6743\u8BBF\u95EE\u3002");
2897
+ setSelectedHistory(detail);
2898
+ } catch (error) {
2899
+ setDownloadNote(error instanceof Error ? error.message : "\u5386\u53F2\u4EFB\u52A1\u8BE6\u60C5\u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002");
2900
+ } finally {
2901
+ setLoadingHistoryTaskId(void 0);
2902
+ }
2903
+ };
2904
+ const downloadHistoryReport = async (record) => {
2905
+ const origin = hostedTaskOrigin(record);
2906
+ if (!record.reportReady || !origin.complete || downloadingTaskId !== void 0) return;
2907
+ setDownloadingTaskId(record.id);
2908
+ setDownloadNote(void 0);
2909
+ try {
2910
+ await saveReportResponse(record, record.sessionId);
2911
+ } catch (error) {
2912
+ setDownloadNote(error instanceof Error ? error.message : "\u5386\u53F2\u62A5\u544A\u4E0B\u8F7D\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002");
2913
+ } finally {
2914
+ setDownloadingTaskId(void 0);
2915
+ }
2916
+ };
2750
2917
  const downloadReport = async () => {
2751
2918
  if (cardText === null || status !== "ready") {
2752
2919
  setDownloadNote("\u5F53\u524D\u4EFB\u52A1\u7684\u62A5\u544A\u5C1A\u672A\u5C31\u7EEA\uFF0C\u8BF7\u7B49\u5F85\u4F1A\u8BDD\u751F\u6210\u7B26\u5408\u8F93\u51FA\u7ED3\u6784\u7684\u62A5\u544A\u3002");
@@ -2757,10 +2924,8 @@ function PrevisitWorkbenchTab(props) {
2757
2924
  let blob;
2758
2925
  let fileName;
2759
2926
  if (hostedTask?.artifact !== void 0) {
2760
- const response = await fetch(`/previsit/api/tasks/${encodeURIComponent(hostedTask.id)}/report?sessionId=${encodeURIComponent(sessionId)}`, { headers: { accept: "text/html" } });
2761
- if (!response.ok) throw new Error(`\u62A5\u544A\u4E0B\u8F7D\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
2762
- blob = await response.blob();
2763
- fileName = hostedTask.artifact.fileName;
2927
+ await saveReportResponse(hostedTask, sessionId);
2928
+ return;
2764
2929
  } else {
2765
2930
  const html = buildPrevisitReportHtml(cardText);
2766
2931
  const company = /(?:访前尽调报告|拜访作战卡)\s*·\s*([^\n((锚|]+)/.exec(cardText)?.[1]?.trim() || "\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A";
@@ -2818,7 +2983,27 @@ function PrevisitWorkbenchTab(props) {
2818
2983
  shared.view === "collect" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(OpportunityPanel, { task, hostedTask, status, events: effectiveEvents, insights }) : null,
2819
2984
  shared.view === "verify" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RiskPanel, { task, hostedTask, status, events: effectiveEvents, insights }) : null,
2820
2985
  shared.view === "output" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DeliveryPanel, { task, hostedTask, status, toolCount: hostedTask?.used ?? runtime.toolNames.length, failedToolCount: hostedTask?.runs.filter((run) => run.status === "failed").length ?? runtime.failedToolCount, cardCaptured: cardText !== null, reportHtml }) : null,
2821
- shared.view === "history" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HistoryPanel, { task, status, hosted: hostedHistory }) : null
2986
+ shared.view === "history" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2987
+ HistoryPanel,
2988
+ {
2989
+ task,
2990
+ status,
2991
+ hosted: hostedHistory,
2992
+ selected: selectedHistory,
2993
+ loadingTaskId: loadingHistoryTaskId,
2994
+ downloadingTaskId,
2995
+ onOpen: (record) => {
2996
+ void openHistoryTask(record);
2997
+ },
2998
+ onBack: () => {
2999
+ setSelectedHistory(null);
3000
+ setDownloadNote(void 0);
3001
+ },
3002
+ onDownload: (record) => {
3003
+ void downloadHistoryReport(record);
3004
+ }
3005
+ }
3006
+ ) : null
2822
3007
  ] }),
2823
3008
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("footer", { className: "qccPwFooter", children: [
2824
3009
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "qccPwFooterHint", "data-tone": downloadNote === void 0 && hostError === void 0 ? void 0 : "error", children: downloadNote ?? hostError ?? "\u5BBF\u4E3B\u6536\u8D77\u4FA7\u62C9\u6216\u5173\u95ED\u672C Tab \u4E0D\u4F1A\u53D6\u6D88\u4EFB\u52A1\uFF0C\u4E5F\u4E0D\u4F1A\u5220\u9664\u5386\u53F2\u6216\u5236\u54C1\u3002" }),
package/lib/index.js CHANGED
@@ -988,7 +988,7 @@ function loadBundledSkill() {
988
988
  },
989
989
  metadata: {
990
990
  author: "QCC",
991
- version: "0.1.23",
991
+ version: "0.1.24",
992
992
  industry: "enterprise-services",
993
993
  mcpServers: ["qcc-company", "qcc-risk", "qcc-ipr", "qcc-operation", "qcc-executive"]
994
994
  }
package/lib/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/skill-file.ts", "../src/previsit-session.ts", "../src/report-export.ts", "../src/previsit-workflow.ts", "../src/previsit-web.ts", "../src/previsit-tools.ts", "../src/tool-outcome.ts"],
4
- "sourcesContent": ["import { readFileSync } from \"node:fs\"\nimport { fileURLToPath } from \"node:url\"\n\nimport { parseSkillFile } from \"./skill-file.js\"\nimport { mountPrevisitWebRoutes, type WebServer } from \"./previsit-web.js\"\nimport { registerPrevisitTools, type ToolHost } from \"./previsit-tools.js\"\nimport { PrevisitWorkflowStore, type StorageDomain } from \"./previsit-workflow.js\"\n\nexport const inject = [\"skills\", \"tools\"] as const\n\nexport const SKILL_NAME = \"qcc-previsit-onepager\"\nexport const SKILL_DESCRIPTION =\n \"\u8C03\u7528\u4F01\u67E5\u67E5\u4E94\u7C7B MCP \u6267\u884C\u4F01\u4E1A\u8BBF\u524D\u5C3D\u8C03\uFF0C\u4EE5\u673A\u4F1A\u4E0E\u98CE\u9669\u53CC\u5F15\u64CE\u8BC6\u522B\u7ECF\u8425\u72B6\u6001\u3001\u5EFA\u7ACB\u5E76\u53CD\u8BC1\u4E1A\u52A1\u5047\u8BBE\uFF0C\u4EA4\u4ED8\u53EF\u8FFD\u6EAF\u7684\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\u3002\"\n\ntype SkillRegistration = {\n name: string\n description: string\n whenToUse: string\n content: string\n source: \"bundled\"\n resourceBase: {\n kind: \"directory\"\n path: string\n }\n metadata: Readonly<Record<string, unknown>>\n}\n\nexport type HostContext = ToolHost & {\n effect(setup: () => void | (() => void)): unknown\n inject?(deps: string[], setup: (ctx: HostContext & { webServer: WebServer; storageDomain: StorageDomain }) => void): unknown\n logger?: { info?(message: string): void; warn?(message: string): void }\n skills: {\n register(skill: SkillRegistration): () => void\n }\n}\n\nexport function loadBundledSkill(): SkillRegistration {\n const skillDirectoryUrl = new URL(\"../skills/qcc-previsit-onepager/\", import.meta.url)\n const skillSource = readFileSync(new URL(\"SKILL.md\", skillDirectoryUrl), \"utf8\")\n const { content } = parseSkillFile(skillSource)\n\n return {\n name: SKILL_NAME,\n description: SKILL_DESCRIPTION,\n whenToUse: \"\u7528\u6237\u8981\u6C42\u51C6\u5907\u5BA2\u6237\u62DC\u8BBF\u3001\u8BBF\u524D\u5C3D\u8C03\u3001\u4E00\u9875\u7EB8\u7B80\u62A5\u3001\u6388\u4FE1\u9762\u8C08\u3001\u5546\u52A1\u8C08\u5224\u3001\u7B7E\u7EA6\u6838\u67E5\u3001\u590D\u8BBF\u66F4\u65B0\u3001\u89E6\u8FBE\u8DEF\u5F84\u6216\u5F53\u9762\u63D0\u95EE\u6E05\u5355\u65F6\u4F7F\u7528\u3002\",\n content,\n source: \"bundled\",\n resourceBase: {\n kind: \"directory\",\n path: fileURLToPath(skillDirectoryUrl),\n },\n metadata: {\n author: \"QCC\",\n version: \"0.1.23\",\n industry: \"enterprise-services\",\n mcpServers: [\"qcc-company\", \"qcc-risk\", \"qcc-ipr\", \"qcc-operation\", \"qcc-executive\"],\n },\n }\n}\n\nexport function apply(ctx: HostContext): void {\n const workflow = new PrevisitWorkflowStore()\n ctx.effect(() => registerPrevisitTools(ctx, workflow))\n ctx.skills.register(loadBundledSkill())\n try {\n ctx.inject?.([\"webServer\", \"storageDomain\"], webCtx => {\n void workflow.attach(webCtx.storageDomain, ctx.logger)\n webCtx.effect(() => mountPrevisitWebRoutes(webCtx.webServer, workflow))\n })\n } catch (error) {\n ctx.logger?.warn?.(`[dsh-pre-duediligence] Host task routes unavailable: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n", "export type ParsedSkillFile = {\n content: string\n frontmatter: string | null\n}\n\nexport function parseSkillFile(source: string): ParsedSkillFile {\n const normalized = source.replaceAll(\"\\r\\n\", \"\\n\")\n\n if (!normalized.startsWith(\"---\\n\")) {\n return { content: normalized.trim(), frontmatter: null }\n }\n\n const closingMarker = normalized.indexOf(\"\\n---\\n\", 4)\n\n if (closingMarker === -1) {\n throw new Error(\"SKILL.md \u7684 YAML frontmatter \u7F3A\u5C11\u7ED3\u675F\u6807\u8BB0\")\n }\n\n return {\n frontmatter: normalized.slice(4, closingMarker).trim(),\n content: normalized.slice(closingMarker + 5).trim(),\n }\n}\n", "export const PREVISIT_SESSION_ID_PREFIX = \"session-dsh-pre-duediligence-\"\n\nexport function isPrevisitSession(sessionId: string): boolean {\n return /^session-dsh-pre-duediligence-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(sessionId)\n}\n\nexport type PrevisitSessionHost = {\n sessions: {\n list?: { getSnapshot(): { current?: string } }\n create?(options: { workspaceId: string; sessionId: string }): Promise<string>\n open?(sessionId: string): void\n }\n workspaces?: {\n list?: { getSnapshot(): {\n items?: Array<{ workspaceId: string; path?: string; sessionIds?: string[] }>\n recentWorkspaceId?: string\n } }\n }\n}\n\nexport function resolvePrevisitWorkspaceId(ctx: PrevisitSessionHost): string | undefined {\n const workspace = ctx.workspaces?.list?.getSnapshot()\n const current = ctx.sessions.list?.getSnapshot().current\n const items = workspace?.items ?? []\n return items.find(item => current !== undefined && item.sessionIds?.includes(current))?.workspaceId\n ?? items.find(item => item.workspaceId === workspace?.recentWorkspaceId)?.workspaceId\n ?? items[0]?.workspaceId\n}\n\nexport async function createPrevisitSession(ctx: PrevisitSessionHost): Promise<string> {\n const workspaceId = resolvePrevisitWorkspaceId(ctx)\n if (!workspaceId) throw new Error(\"\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u518D\u6253\u5F00\u8BBF\u524D\u5C3D\u8C03\")\n if (typeof ctx.sessions.create !== \"function\" || typeof ctx.sessions.open !== \"function\") {\n throw new Error(\"\u5F53\u524D DSH \u7248\u672C\u6CA1\u6709\u53EF\u7528\u7684\u4F1A\u8BDD\u521B\u5EFA\u80FD\u529B\")\n }\n if (typeof globalThis.crypto?.randomUUID !== \"function\") {\n throw new Error(\"\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u5B89\u5168\u4F1A\u8BDD\u6807\u8BC6\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u6700\u65B0\u7248\u6D4F\u89C8\u5668\")\n }\n const requested = PREVISIT_SESSION_ID_PREFIX + globalThis.crypto.randomUUID()\n // Preserve the runtime receiver: create() uses this.manager.\n const created = await ctx.sessions.create({ workspaceId, sessionId: requested })\n if (created !== requested) throw new Error(\"\u8BBF\u524D\u5C3D\u8C03\u4F1A\u8BDD\u6807\u8BC6\u4E0D\u5339\u914D\uFF0C\u8BF7\u91CD\u8BD5\")\n return created\n}\n", "// \u4F5C\u6218\u5361 Markdown \u2192 \u53EF\u4E0B\u8F7D\u7684\u81EA\u5305\u542B\u6837\u5F0F\u5316 HTML \u62A5\u544A\u3002\n// \u914D\u8272\u3001\u5B57\u53F7\u3001\u6807\u9898\u89C4\u8303\u5168\u90E8\u9501\u5B9A\u4E3A\u7528\u6237\u63D0\u4F9B\u7684 demo \u8BBE\u8BA1\uFF0C\u6A21\u578B/\u524D\u7AEF\u90FD\u4E0D\u518D\u81EA\u884C\u914D\u8272\u3002\n// \u7EAF\u51FD\u6570\uFF0C\u65E0 DOM \u4F9D\u8D56\uFF0C\u4FBF\u4E8E\u5355\u6D4B\u3002\nimport { isPrevisitSession } from \"./previsit-session.js\"\n\nexport type ReportMeta = {\n company?: string\n generatedAt?: string\n}\n\n// \u4F1A\u8BDD\u8282\u70B9\u7684\u6700\u5C0F\u7ED3\u6784\uFF08DSH \u8282\u70B9\u5177\u4F53\u5F62\u6001\u672A\u77E5\uFF0C\u5B57\u6BB5\u5168\u90E8\u53EF\u9009\uFF09\nexport type CardNode = {\n id?: string\n seq?: number\n interrupted?: boolean\n blocks?: Array<{ kind: string; text?: string }>\n kind?: string\n role?: string\n text?: string\n content?: unknown\n message?: { role?: string; content?: unknown } | null\n parts?: Array<{ text?: string; type?: string } | string>\n call?: { name?: string } | null\n}\n\nexport type CardSnapshot = { nodes?: CardNode[]; running?: boolean }\n\nfunction contentText(value: unknown): string {\n if (typeof value === \"string\") return value\n if (!Array.isArray(value)) return \"\"\n return value.map(block => {\n if (typeof block === \"string\") return block\n if (block === null || typeof block !== \"object\") return \"\"\n const typed = block as { type?: unknown; kind?: unknown; text?: unknown }\n if ((typeof typed.type === \"string\" && typed.type !== \"text\") || (typeof typed.kind === \"string\" && typed.kind !== \"text\")) return \"\"\n return typeof typed.text === \"string\" ? typed.text : \"\"\n }).join(\"\")\n}\n\n// \u9632\u5FA1\u5F0F\u63D0\u53D6\u52A9\u624B\u8F93\u51FA\u7684\u4F5C\u6218\u5361\u6B63\u6587\uFF1ADSH \u4F1A\u8BDD\u8282\u70B9\u5F62\u6001\u672A\u77E5\uFF0C\u9010\u79CD\u53EF\u80FD\u5B57\u6BB5\u5C1D\u8BD5\u3002\nfunction nodeText(node: CardNode): string {\n if (Array.isArray(node.blocks)) return node.blocks.filter(b => b.kind === \"text\").map(b => b.text ?? \"\").join(\"\\n\")\n if (typeof node.text === \"string\") return node.text\n if (typeof node.content === \"string\") return node.content\n if (node.message) { const text = contentText(node.message.content); if (text !== \"\") return text }\n if (Array.isArray(node.content)) return contentText(node.content)\n if (Array.isArray(node.parts)) {\n return node.parts.map(pt => (typeof pt === \"string\" ? pt : (pt && typeof pt.text === \"string\" ? pt.text : \"\"))).join(\"\")\n }\n return \"\"\n}\n\n// \u4F5C\u6218\u5361\u8D77\u70B9\uFF1A\u4E00\u7EA7\u6807\u9898\u300C\u62DC\u8BBF\u4F5C\u6218\u5361 \u00B7 \u4F01\u4E1A\u540D\u300D\u6216\u9996\u6BB5\u300C\u2460 \u6838\u5FC3\u7814\u5224\u300D\nconst CARD_START = /^#{1,3}\\s*(\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361)[^\\n]*$|^#{1,3}\\s*[\u24601]?\\s*\u6838\u5FC3\u7814\u5224[^\\n]*$/m\n// \u5361\u7247\u7ED3\u6784\u7279\u5F81\uFF1A\u81F3\u5C11\u6709\u6838\u5FC3\u7814\u5224 + \u73B0\u573A\u5FC5\u95EE/\u8986\u76D6\u8BF4\u660E\u4E24\u7C7B\u6BB5\u843D\nconst CARD_SHAPE = (text: string): boolean => /\u6838\u5FC3\u7814\u5224/.test(text) && /\u73B0\u573A\u5FC5\u95EE|\u8986\u76D6\u8BF4\u660E|\u8986\u76D6\u5EA6/.test(text)\n// \u6CE8\u5165\u5185\u5BB9\uFF08\u6280\u80FD\u6E05\u5355\u3001\u7CFB\u7EDF\u63D0\u9192\u7B49\uFF09\u7EDD\u4E0D\u8FDB\u62A5\u544A\nconst JUNK = /<system-reminder>|<available_skills>|<command-name>|<\\/?antml/i\nconst isTool = (node: CardNode): boolean => node.kind === \"tool-result\" || node.kind === \"tool-call\"\nconst nodeRole = (node: CardNode): string => node.role ?? node.message?.role ?? node.kind ?? \"\"\nconst isUserish = (node: CardNode): boolean => /user|human|system|context|steering/i.test(`${nodeRole(node)} ${node.kind ?? \"\"}`) || node.interrupted === true\n\n// \u4ECE\u4F1A\u8BDD\u91CC\u8BA4\u9886\u4EFB\u52A1\uFF1A\u627E\u6700\u540E\u4E00\u6761\u5E26\u201C\u8BBF\u524D\u4EFB\u52A1 ID\uFF1APV-xxx\u201D\u7684\u6D88\u606F\uFF08\u5DE5\u4F5C\u53F0/\u8BBE\u5B9A\u6761\u53D1\u51FA\u7684\u6B63\u5F0F\u63D0\u4EA4\uFF09\uFF1B\n// \u6CA1\u6709\u6807\u8BB0\u4F46\u5DF2\u51FA\u73B0\u4F01\u67E5\u67E5\u5DE5\u5177\u8C03\u7528\u65F6\uFF0C\u89C6\u4E3A\u201C\u4F1A\u8BDD\u5185\u76F4\u63A5\u53D1\u8D77\u7684\u5C3D\u8C03\u201D\uFF0C\u4ECE\u5934\u8DDF\u8E2A\u3002\nexport function adoptTaskFromSnapshot(snapshot: CardSnapshot, sessionId: string, minimumBaseline = 0): { id: string; prompt: string; nodeBaseline: number } | null {\n if (!isPrevisitSession(sessionId)) return null\n const nodes = snapshot.nodes ?? []\n for (let idx = nodes.length - 1; idx >= minimumBaseline; idx--) {\n const node = nodes[idx]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) continue\n const text = nodeText(node)\n const m = /\u8BBF\u524D\u4EFB\u52A1 ID[\uFF1A:]\\s*(PV-[A-Z0-9-]+)/.exec(text)\n if (m !== null && m[1] !== undefined) return { id: m[1], prompt: text.trim(), nodeBaseline: idx }\n }\n // Native composer submissions inherit the explicitly owned Session. Never infer\n // ownership from QCC calls (shared by cleaning, form-fill and tender plugins).\n for (let idx = nodes.length - 1; idx >= minimumBaseline; idx--) {\n const node = nodes[idx]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) continue\n const prompt = nodeText(node).trim()\n if (prompt !== \"\" && !JUNK.test(prompt)) return { id: `turn:${node.seq ?? node.id ?? idx}`, prompt, nodeBaseline: idx }\n }\n return null\n}\n\nconst FULL_REPORT_SECTIONS = [\"\u6838\u5FC3\u7814\u5224\", \"\u4EA7\u4E1A\u5B9A\u4F4D\", \"\u8FD1\u671F\u52A8\u6001\", \"\u4E1A\u52A1\u5047\u8BBE\", \"\u7EA2\u7EBF\u63D0\u793A\", \"\u73B0\u573A\u5FC5\u95EE\", \"\u89E6\u8FBE\u5F00\u573A\", \"\u8986\u76D6\u8BF4\u660E\"]\nexport function captureTaskReport(snapshot: CardSnapshot, sessionId: string, task: { id: string; nodeBaseline: number; prompt: string }): string | null {\n if (!isPrevisitSession(sessionId) || snapshot.running === true) return null\n const node = snapshot.nodes?.[task.nodeBaseline]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) return null\n const adopted = adoptTaskFromSnapshot({ nodes: (snapshot.nodes ?? []).slice(0, task.nodeBaseline + 1) }, sessionId, task.nodeBaseline)\n if (adopted?.id !== task.id) return null\n // A later explicitly submitted task is a hard boundary, including after reload.\n const nodes = snapshot.nodes ?? []\n const next = nodes.findIndex((n, i) => i > task.nodeBaseline && /^(user|human)$/i.test(nodeRole(n)) && /\u8BBF\u524D\u4EFB\u52A1 ID[\uFF1A:]\\s*PV-/.test(nodeText(n)))\n const text = extractCardText({ nodes: next === -1 ? nodes : nodes.slice(0, next) }, task.nodeBaseline + 1)\n if (text === null) return null\n const sections = [...text.matchAll(/^#{1,4}\\s+(.+)$/gm)].map(m => normalizeHeading(m[1] ?? \"\").replace(/^\\d+\u3001\\s*/, \"\").replace(/\\*\\*/g, \"\").trim())\n return FULL_REPORT_SECTIONS.every(s => sections.some(title => title === s || title.startsWith(s + \"\uFF08\") || title.startsWith(s + \"\uFF1A\"))) ? text : null\n}\n\nexport function extractCardText(snapshot: CardSnapshot, baseline: number): string | null {\n const nodes = (snapshot.nodes ?? []).slice(baseline)\n // \u4ECE\u672B\u5C3E\u627E\u6700\u540E\u4E00\u4E2A\u5305\u542B\u4F5C\u6218\u5361\u8D77\u70B9\u7684\u8282\u70B9\uFF0C\u4ECE\u8D77\u70B9\u5207\u5230\u8BE5\u8282\u70B9\u672B\u5C3E\n for (let idx = nodes.length - 1; idx >= 0; idx--) {\n const node = nodes[idx]\n if (node === undefined || isTool(node) || isUserish(node)) continue\n const text = nodeText(node)\n if (JUNK.test(text)) continue\n const m = CARD_START.exec(text)\n if (m === null || m.index === undefined) continue\n let card = text.slice(m.index).trim()\n // \u540E\u7EED\u7D27\u90BB\u8282\u70B9\u82E5\u662F\u540C\u4E00\u5F20\u5361\u7684\u5EF6\u7EED\uFF08\u4EE5 Markdown \u6BB5\u6807\u9898\u5F00\u5934\u3001\u65E0\u6CE8\u5165\u5185\u5BB9\uFF09\uFF0C\u62FC\u63A5\n for (let j = idx + 1; j < nodes.length; j++) {\n const next = nodes[j]\n if (next === undefined || isTool(next) || isUserish(next)) break\n const t = nodeText(next).trim()\n if (t === \"\" || JUNK.test(t) || !/^(#{2,4}\\s|\\||-\\s|\\d+\\.\\s|\\*\\*)/.test(t)) break\n card += \"\\n\\n\" + t\n }\n if (CARD_SHAPE(card)) return card\n }\n // \u515C\u5E95\uFF1A\u5355\u8282\u70B9\u6574\u4F53\u7B26\u5408\u5361\u7247\u7ED3\u6784\n for (let idx = nodes.length - 1; idx >= 0; idx--) {\n const node = nodes[idx]\n if (node === undefined || isTool(node) || isUserish(node)) continue\n const text = nodeText(node).trim()\n if (text.length > 40 && !JUNK.test(text) && CARD_SHAPE(text)) return text\n }\n return null\n}\n\nconst esc = (s: string): string =>\n s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\n\n// \u8BFB\u8005\u89C6\u89D2\u7684\u6E05\u6D17\uFF1A\u4E8B\u5B9E\u7F16\u53F7 [F-013] \u4E0E\u8BC1\u636E\u5C42\u7EA7\u4EE3\u53F7 L1/L2 \u53EA\u5BF9\u6A21\u578B\u8FFD\u6EAF\u6709\u7528\uFF0C\u62A5\u544A\u91CC\u7FFB\u8BD1\u6210\u4EBA\u8BDD\u6216\u53BB\u6389\nconst TIER_WORDS: Record<string, string> = { L1: \"\u8D22\u62A5\u62AB\u9732\", L2: \"\u516C\u5F00\u8BB0\u5F55\", L3: \"\u5173\u8054\u5173\u7CFB\", L4: \"\u884C\u4E1A\u63A8\u65AD\", L5: \"\u73B0\u573A\u6838\u5B9E\" }\nexport function humanizeCell(text: string): string {\n const t = text.trim()\n if (/^L[1-5](\\s*[\\/\uFF0F\u3001]\\s*L[1-5])*$/.test(t)) {\n return t.split(/\\s*[\\/\uFF0F\u3001]\\s*/).map(x => TIER_WORDS[x] ?? x).join(\" / \")\n }\n if (t === \"\u6765\u6E90\u5C42\u7EA7\") return \"\u6765\u6E90\"\n return text\n}\nexport function stripFactIds(text: string): string {\n // \u652F\u6301 [F-013]\u3001[F-008/F-009/F-013]\u3001[F-002\u3001F-008]\u3001[F-1/F-2] \u7B49\u5199\u6CD5\n return text\n .replace(/\\s*\\[F-?\\d{1,4}(?:\\s*[\\/\u3001,\uFF0C;\uFF1B|]\\s*F-?\\d{1,4})*\\]/g, \"\")\n .replace(/[ \\t]+([\uFF0C\u3002\uFF1B\uFF1A\uFF09])/g, \"$1\")\n}\n// \u5047\u8BBE\u7F16\u53F7\u4E0E\u4F18\u5148\u7EA7\uFF1AH2 \u00B7 P1 \u2192 \u5047\u8BBE 2\uFF08P1\uFF09\uFF1B\u6B63\u6587\u91CC\u201C\u56DE\u6307 H2\u201D\u2192\u201C\u56DE\u6307\u5047\u8BBE 2\u201D\nconst PRIORITY_WORDS: Record<string, string> = { P0: \"\u4F18\u5148\", P1: \"\u91CD\u8981\", P2: \"\u5907\u9009\" }\nexport function humanizeHypothesis(text: string): string {\n return text\n .replace(/^\\s*H(\\d+)\\s*[\u00B7\u30FB:\uFF1A\\-\u2013]\\s*(P[012])\\s*[\u00B7\u30FB:\uFF1A\\-\u2013]\\s*/, (_m, n: string, p: string) => `\u5047\u8BBE ${n}\uFF08${PRIORITY_WORDS[p] ?? p}\uFF09`)\n .replace(/(?<![A-Za-z0-9])H(\\d{1,2})(?![A-Za-z0-9])/g, \"\u5047\u8BBE $1\")\n}\n\n// \u884C\u5185\uFF1A**\u52A0\u7C97**\uFF1B\u4E8B\u5B9E\u7F16\u53F7\u53BB\u6389\nfunction inline(text: string): string {\n let html = esc(humanizeHypothesis(stripFactIds(text)))\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, \"<strong>$1</strong>\")\n return html\n}\n\nconst CIRCLED: Record<string, string> = { \"\u2460\": \"1\", \"\u2461\": \"2\", \"\u2462\": \"3\", \"\u2463\": \"4\", \"\u2464\": \"5\", \"\u2465\": \"6\", \"\u2466\": \"7\", \"\u2467\": \"8\", \"\u2468\": \"9\", \"\u2469\": \"10\" }\nexport function normalizeHeading(text: string): string {\n return text.trim().replace(/^([\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469])\\s*/, (_m, c: string) => `${CIRCLED[c] ?? c}\u3001`).replace(/^(\\d+)[.\uFF0E]\\s*/, \"$1\u3001\")\n}\n\n// \u62A5\u544A Markdown \u5B50\u96C6 \u2192 HTML \u4E3B\u4F53\u3002\u652F\u6301\uFF1A# ## ### \u6807\u9898\u3001| \u8868\u683C |\u3001- \u5217\u8868\u30011. \u6709\u5E8F\u3001--- \u5206\u9694\u3001\u6BB5\u843D\u3002\nexport function renderCardMarkdown(md: string): string {\n const lines = md.replace(/\\r\\n/g, \"\\n\").split(\"\\n\")\n const out: string[] = []\n let i = 0\n const flushList = (items: string[], ordered: boolean) => {\n if (!items.length) return\n const tag = ordered ? \"ol\" : \"ul\"\n out.push(`<${tag}>${items.map(it => `<li>${inline(it)}</li>`).join(\"\")}</${tag}>`)\n }\n while (i < lines.length) {\n const line = lines[i] ?? \"\"\n const t = line.trim()\n if (t === \"\") { i++; continue }\n if (/^---+$/.test(t)) { out.push(\"<hr/>\"); i++; continue }\n // \u8868\u683C\uFF1A\u8FDE\u7EED\u4EE5 | \u5F00\u5934\u7684\u884C\uFF0C\u7B2C\u4E8C\u884C\u662F\u5206\u9694\u7B26\n const sep = lines[i + 1] ?? \"\"\n if (t.startsWith(\"|\") && /^\\|[\\s:|-]+\\|?$/.test(sep.trim())) {\n const header = t.split(\"|\").slice(1, -1).map(c => humanizeCell(c.trim()))\n i += 2\n const rows: string[][] = []\n while (i < lines.length && (lines[i] ?? \"\").trim().startsWith(\"|\")) {\n rows.push((lines[i] ?? \"\").trim().split(\"|\").slice(1, -1).map(c => humanizeCell(c.trim())))\n i++\n }\n const thead = `<thead><tr>${header.map(h => `<th>${inline(h)}</th>`).join(\"\")}</tr></thead>`\n const tbody = `<tbody>${rows.map(r => `<tr>${r.map(c => `<td>${inline(c)}</td>`).join(\"\")}</tr>`).join(\"\")}</tbody>`\n out.push(`<div class=\"tw\"><table>${thead}${tbody}</table></div>`)\n continue\n }\n // \u6807\u9898\uFF1A\u5708\u53F7 \u2460\u2461 \u7EDF\u4E00\u6210\u201C1\u3001\u201D\u683C\u5F0F\n const h = /^(#{1,4})\\s+(.*)$/.exec(t)\n if (h) {\n const level = (h[1] ?? \"##\").length\n out.push(`<h${level}>${inline(normalizeHeading(h[2] ?? \"\"))}</h${level}>`)\n i++\n continue\n }\n // \u6807\u9898\u4E0B\u7684\u5C0F\u8BF4\u660E\uFF08> \u8BF4\u660E\uFF1A\u2026\uFF09\n if (t.startsWith(\">\")) {\n out.push(`<p class=\"note\">${inline(t.replace(/^>\\s?/, \"\"))}</p>`)\n i++\n continue\n }\n // \u65E0\u5E8F\u5217\u8868\n if (/^[-*]\\s+/.test(t)) {\n const items: string[] = []\n while (i < lines.length && /^\\s*[-*]\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").trim().replace(/^[-*]\\s+/, \"\"))\n i++\n }\n flushList(items, false)\n continue\n }\n // \u6709\u5E8F\u5217\u8868\n if (/^\\d+\\.\\s+/.test(t)) {\n const items: string[] = []\n while (i < lines.length && /^\\s*\\d+\\.\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").trim().replace(/^\\d+\\.\\s+/, \"\"))\n i++\n }\n flushList(items, true)\n continue\n }\n // \u6BB5\u843D\n out.push(`<p>${inline(t)}</p>`)\n i++\n }\n return out.join(\"\\n\")\n}\n\n// demo \u9501\u5B9A\u914D\u8272\u4E0E\u5B57\u53F7\uFF1B\u6807\u9898\u514B\u5236\u3001\u56DB\u5B57\u4F18\u5148\uFF0C\u6807\u9898\u4E0B\u53EF\u5E26\u5C0F\u8BF4\u660E\u6A21\u5757\u3002\nconst REPORT_CSS = `\n:root{\n --blue:#128BED;--blue-dark:#0875D1;--ink:#202C3B;--body:#3D4A5C;--muted:#626F80;\n --line:#DCE4EC;--line-blue:#CDE9FC;--hero:#F2F9FC;\n --ok:#16a34a;--ok-bg:#f0fdf4;--warn:#d97706;--warn-bg:#fff7ed;--risk:#ef4444;--risk-bg:#fef2f2;\n}\n*{box-sizing:border-box}\nbody{margin:0;background:#f1f5f9;color:var(--body);\n font:15px/1.75 -apple-system,BlinkMacSystemFont,\"PingFang SC\",\"Microsoft YaHei\",\"Noto Sans SC\",sans-serif;\n -webkit-font-smoothing:antialiased}\n.sheet{max-width:860px;margin:24px auto;background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden;\n box-shadow:0 1px 2px rgba(15,23,42,.04),0 12px 32px -20px rgba(15,23,42,.25)}\n.hero{background:var(--hero);border-bottom:1px solid var(--line-blue);padding:22px 26px 20px}\n.hero .eyebrow{font-size:11px;font-weight:800;letter-spacing:.12em;color:var(--blue);margin:0 0 6px}\n.hero h1{margin:0;color:var(--ink);font-size:22px;font-weight:900;line-height:1.4}\n.hero .meta{margin:8px 0 0;color:var(--muted);font-size:12.5px;line-height:1.7}\n.body{padding:8px 26px 26px}\n/* \u5927\u6807\u9898\uFF1A\u84DD\u8272 + \u6807\u9898\u4E0B\u77ED\u6A2A\u7EBF\uFF1B\u5C0F\u6807\u9898\uFF1A\u6D45\u7070\u5E95 + \u5DE6\u4FA7\u84DD\u7AD6\u7EBF */\n.body h2{position:relative;margin:26px 0 12px;padding:0 0 8px;color:var(--blue-dark);font-size:16px;font-weight:800;letter-spacing:.01em}\n.body h2::after{content:\"\";position:absolute;left:0;bottom:0;width:32px;height:3px;border-radius:2px;background:var(--blue)}\n.body h2:first-of-type{margin-top:10px}\n.body h3{margin:16px 0 8px;padding:6px 10px;border-left:3px solid var(--blue);border-radius:0 4px 4px 0;background:#f5f7fa;color:var(--ink);font-size:13.5px;font-weight:800}\n.body h4{margin:12px 0 4px;color:var(--ink);font-size:13px;font-weight:700}\n.body h1{display:none}\n.note{margin:-4px 0 10px;color:var(--muted);font-size:12px;line-height:1.7}\nblockquote{margin:-4px 0 10px;padding:0;border:0;color:var(--muted);font-size:12px;line-height:1.7}\nblockquote p{margin:0}\np{margin:6px 0}\nstrong{color:var(--ink)}\n.fid{color:var(--blue);font:600 10px ui-monospace,monospace;margin-left:2px}\nul,ol{margin:6px 0;padding-left:20px}\nli{margin:3px 0;line-height:1.7}\nhr{border:0;border-top:1px solid var(--line);margin:16px 0}\n.tw{overflow-x:auto;margin:8px 0}\ntable{border-collapse:collapse;width:100%;font-size:13px}\nth,td{border:1px solid var(--line);padding:8px 11px;text-align:left;vertical-align:top;line-height:1.6;word-break:break-word}\nth{background:#f8fafc;color:var(--muted);font-weight:700;font-size:12px;white-space:nowrap}\n/* \u9996\u5217\u591A\u4E3A\u201C\u7B49\u7EA7/\u65E5\u671F/\u63A8\u8350\u7EA7\u201D\u8FD9\u7C7B\u77ED\u6807\u7B7E\uFF1A\u4E0D\u6362\u884C\u3001\u4E0D\u88AB\u6324\u6241\uFF1B\u957F\u6587\u672C\u5217\u81EA\u9002\u5E94 */\ntd:first-child,th:first-child{white-space:nowrap;width:1%}\ntd:nth-child(2){min-width:180px}\n/* \u72B6\u6001\u8272\uFF1A\u51FA\u73B0\u7EA2\u7EBF/\u5173\u6CE8/\u786E\u8BA4\u7B49\u8BCD\u81EA\u52A8\u67D3\u8272\u7531\u5185\u5BB9\u51B3\u5B9A\uFF0C\u8FD9\u91CC\u7ED9\u51FA\u53EF\u7528\u7C7B */\n.tag-ok{color:var(--ok);background:var(--ok-bg)}\n.tag-warn{color:var(--warn);background:var(--warn-bg)}\n.tag-risk{color:var(--risk);background:var(--risk-bg)}\nfooter{padding:14px 26px 20px;border-top:1px solid var(--line);color:var(--muted);font-size:11.5px;line-height:1.7}\n@media print{body{background:#fff}.sheet{border:0;box-shadow:none;margin:0}}\n`\n\n// \u4ECE\u4F5C\u6218\u5361\u9996\u884C/\u951A\u5B9A\u884C\u63D0\u53D6\u4F01\u4E1A\u540D\u4E0E\u751F\u6210\u65F6\u95F4\u505A hero\nfunction deriveMeta(md: string, meta?: ReportMeta): { company: string; anchor: string; when: string } {\n const first = /^#?\\s*(?:\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361)\\s*\u00B7\\s*([^\\n\u951A]+)/m.exec(md)\n const anchorLine = /\u951A\u5B9A\u4E3B\u4F53\uFF1A([^\\n]+?)(?:\\s*\u751F\u6210\u65F6\u95F4|$)/m.exec(md)\n const whenLine = /\u751F\u6210\u65F6\u95F4[:\uFF1A]\\s*([0-9-]+)/.exec(md)\n return {\n company: meta?.company || (first?.[1]?.trim() ?? \"\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\"),\n anchor: (anchorLine?.[1] ?? \"\").trim().replace(/[\uFF5C|\u00B7\\s]+$/, \"\"),\n when: meta?.generatedAt || (whenLine?.[1] ?? \"\"),\n }\n}\n\nexport type ReportHero = { company: string; anchor: string; when: string }\n\n// \u7EDF\u4E00\u5916\u58F3\uFF1Ahero + body + footer\nexport function wrapPrevisitReport(bodyHtml: string, m: ReportHero): string {\n const heroMeta = [m.anchor, m.when ? `\u751F\u6210\u65F6\u95F4\uFF1A${m.when}` : \"\"].filter(Boolean).join(\"\u3000\u00B7\u3000\")\n return `<!doctype html><html lang=\"zh\"><head><meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A \u00B7 ${esc(m.company)}</title><style>${REPORT_CSS}</style></head>\n<body><div class=\"sheet\">\n<div class=\"hero\"><p class=\"eyebrow\">\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A</p><h1>${esc(m.company)}</h1>${heroMeta ? `<p class=\"meta\">${esc(heroMeta)}</p>` : \"\"}</div>\n<div class=\"body\">${bodyHtml}</div>\n<footer>\u6570\u636E\u6765\u6E90\uFF1A\u4F01\u67E5\u67E5\u3002\u5404\u7EF4\u5EA6\u65F6\u6548\u4E0D\u540C\uFF0C\u4E0D\u627F\u8BFA\u5B9E\u65F6\u3002\u672C\u62A5\u544A\u4E3A\u8BBF\u524D\u8F85\u52A9\u6750\u6599\uFF0C\u4E0D\u6784\u6210\u6388\u4FE1\u3001\u5408\u4F5C\u6216\u4EFB\u4F55\u5546\u4E1A\u51B3\u7B56\u4F9D\u636E\uFF0C\u4EE5\u5B98\u65B9\u516C\u793A\u4E3A\u51C6\u3002</footer>\n</div></body></html>`\n}\n\nexport function buildPrevisitReportHtml(cardMarkdown: string, meta?: ReportMeta): string {\n const m = deriveMeta(cardMarkdown, meta)\n // \u53BB\u6389\u6B63\u6587\u91CC\u7684\u4E00\u7EA7\u6807\u9898\u548C\u951A\u5B9A\u884C\uFF08\u5DF2\u8FDB hero\uFF09\uFF0C\u5176\u4F59\u8FDB body\n const body = cardMarkdown\n .replace(/^#\\s*(?:\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361).*$/m, \"\")\n .replace(/^\u951A\u5B9A\u4E3B\u4F53\uFF1A.*$/m, \"\")\n .trim()\n return wrapPrevisitReport(renderCardMarkdown(body), m)\n}\n\n// \u515C\u5E95\uFF1A\u76F4\u63A5\u7528\u4F1A\u8BDD\u91CC\u5DF2\u6E32\u67D3\u597D\u7684\u62A5\u544A HTML\uFF08\u53BB\u6389\u6837\u5F0F/\u7C7B\u540D/\u63A7\u4EF6\uFF09\uFF0C\u5957\u540C\u4E00\u5916\u58F3\nexport function buildPrevisitReportFromRenderedHtml(bodyHtml: string, plainText: string, meta?: ReportMeta): string {\n const m = deriveMeta(plainText, meta)\n const body = humanizeHypothesis(stripFactIds(bodyHtml))\n .replace(/<(td|th)([^>]*)>\\s*([^<]{1,20})\\s*<\\/\\1>/g, (_m, tag: string, attrs: string, cell: string) => `<${tag}${attrs}>${esc(humanizeCell(cell))}</${tag}>`)\n return wrapPrevisitReport(body, m)\n}\n", "import { randomUUID } from \"node:crypto\"\n\nimport type { ToolOutcome } from \"./tool-outcome.js\"\n\nexport const PREVISIT_TASK_STATES = [\n \"needs-entity-search\",\n \"needs-entity-confirmation\",\n \"entity-confirmed\",\n \"running\",\n \"finalizing\",\n \"completed\",\n \"partial\",\n \"failed\",\n] as const\n\nexport type PrevisitTaskState = (typeof PREVISIT_TASK_STATES)[number]\nexport type PrevisitTaskStage = \"target\" | \"scope\" | \"collect\" | \"verify\" | \"output\"\n\nexport const PREVISIT_TERMINAL_STATES: ReadonlySet<PrevisitTaskState> = new Set([\"completed\", \"partial\", \"failed\"])\n\nexport type PrevisitRun = {\n id: string\n dimension: string\n toolName?: string\n status: ToolOutcome\n quotaUsed: boolean\n startedAt: string\n completedAt?: string\n message?: string\n}\n\nexport type PrevisitArtifact = {\n id: string\n format: \"html\"\n fileName: string\n mediaType: \"text/html; charset=utf-8\"\n createdAt: string\n}\n\nexport type PrevisitTaskRecord = {\n id: string\n schemaVersion: 1\n revision: number\n sessionId: string\n workspace: string\n query: string\n depth: \"fast\" | \"standard\" | \"deep\"\n limit: number\n used: number\n state: PrevisitTaskState\n stage: PrevisitTaskStage\n entity?: { fullName: string; creditCode: string }\n runs: PrevisitRun[]\n reportMarkdown?: string\n artifact?: PrevisitArtifact\n lastError?: string\n createdAt: string\n updatedAt: string\n completedAt?: string\n}\n\nexport type PrevisitVerificationClosure = {\n gaps: string[]\n partialRequired: boolean\n}\n\nconst VERIFICATION_DETAIL_LABELS = {\n dishonest: \"\u5931\u4FE1\u660E\u7EC6\",\n enforcement: \"\u88AB\u6267\u884C\u660E\u7EC6\",\n terminated_cases: \"\u7EC8\u672C\u6848\u4EF6\u660E\u7EC6\",\n equity_freeze: \"\u80A1\u6743\u51BB\u7ED3\u660E\u7EC6\",\n business_exception: \"\u7ECF\u8425\u5F02\u5E38\u660E\u7EC6\",\n administrative_penalty: \"\u884C\u653F\u5904\u7F5A\u660E\u7EC6\",\n tax_abnormal: \"\u7A0E\u52A1\u5F02\u5E38\u660E\u7EC6\",\n judicial_documents: \"\u88C1\u5224\u6587\u4E66\u660E\u7EC6\",\n} as const\n\nconst UNRESOLVED_VERIFICATION_STATUSES: ReadonlySet<ToolOutcome> = new Set([\n \"failed\",\n \"no-permission\",\n \"not-executed\",\n \"unknown\",\n])\n\n/**\n * Shared verification gate for explicit tool finalization and the UI report\n * reconciliation fallback. `skipped` is a resolved green state; synthetic\n * `previsit-pending-*` runs remain open until a real Provider result replaces them.\n */\nexport function previsitVerificationClosure(task: PrevisitTaskRecord): PrevisitVerificationClosure {\n const verificationDimensions = new Set<string>([\n \"risk_scan\",\n \"personnel\",\n \"executive_risk\",\n ...Object.keys(VERIFICATION_DETAIL_LABELS),\n ])\n const latest = new Map<string, PrevisitRun>()\n for (const run of task.runs) if (verificationDimensions.has(run.dimension)) latest.set(run.dimension, run)\n const gaps: string[] = []\n const isPending = (run: PrevisitRun | undefined) => run === undefined\n || run.status === \"running\"\n || run.id.startsWith(\"previsit-pending-\")\n\n const riskScan = latest.get(\"risk_scan\")\n if (isPending(riskScan)) gaps.push(\"\u98CE\u9669\u626B\u63CF\u672A\u5B8C\u6210\")\n else if (riskScan?.status === \"done\" || riskScan?.status === \"no-data\" || riskScan?.status === \"skipped\") {\n for (const [dimension, label] of Object.entries(VERIFICATION_DETAIL_LABELS)) {\n if (isPending(latest.get(dimension))) gaps.push(`${label}\u672A\u95ED\u73AF`)\n }\n }\n\n const personnel = latest.get(\"personnel\")\n if (isPending(personnel)) gaps.push(\"\u5173\u952E\u4EBA\u5458\u67E5\u8BE2\u672A\u5B8C\u6210\")\n else if (personnel?.status === \"done\" || personnel?.status === \"unknown\" || personnel?.status === \"no-data\") {\n if (isPending(latest.get(\"executive_risk\"))) gaps.push(\"\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\u672A\u95ED\u73AF\")\n }\n\n return {\n gaps: [...new Set(gaps)],\n partialRequired: [...latest.values()].some(run => UNRESOLVED_VERIFICATION_STATUSES.has(run.status)),\n }\n}\n\ntype StorageTable = {\n get(key: string): unknown | Promise<unknown>\n put(key: string, value: unknown): unknown | Promise<unknown>\n entries(): Iterable<[string, unknown]>\n}\n\nexport type StorageDomain = {\n open(spec: object): Promise<{ table(name: string): StorageTable }>\n}\n\nconst DOMAIN_SPEC = {\n name: \"previsit_tasks_v1\",\n version: 1,\n tables: {\n tasks: {\n valueSchema: {\n parse: (value: unknown) => value,\n safeParse: (value: unknown) => ({ success: true, data: value }),\n },\n },\n },\n}\n\nconst VALID_REQUEST_ID = /^PV-\\d{8}-[A-Z0-9-]{4,40}$/\nconst nowIso = () => new Date().toISOString()\n\nfunction parseStoredRecord(value: unknown): PrevisitTaskRecord | undefined {\n if (value === null || typeof value !== \"object\") return undefined\n const record = value as Partial<PrevisitTaskRecord>\n const valid = typeof record.id === \"string\"\n && record.schemaVersion === 1\n && (record.sessionId === undefined || typeof record.sessionId === \"string\")\n && (record.workspace === undefined || typeof record.workspace === \"string\")\n && typeof record.query === \"string\"\n && typeof record.limit === \"number\"\n && typeof record.used === \"number\"\n && Array.isArray(record.runs)\n && PREVISIT_TASK_STATES.includes(record.state as PrevisitTaskState)\n if (!valid) return undefined\n return normalizeTerminalRecord({\n ...(record as PrevisitTaskRecord),\n sessionId: typeof record.sessionId === \"string\" ? record.sessionId : \"\",\n workspace: typeof record.workspace === \"string\" ? record.workspace : \"\",\n })\n}\n\nfunction normalizeTerminalRecord(record: PrevisitTaskRecord): PrevisitTaskRecord {\n // `limit` remains in schema v1 for backward compatibility. Zero is the\n // unbounded sentinel; migrate previously persisted 8/18/40-call tasks as\n // they are read so an installed upgrade cannot remain stuck at the old cap.\n const unlimited = record.limit === 0 ? record : { ...record, limit: 0 }\n const reportReady = typeof unlimited.reportMarkdown === \"string\" && unlimited.reportMarkdown.trim() !== \"\"\n if (PREVISIT_TERMINAL_STATES.has(unlimited.state) || !reportReady) return unlimited\n return {\n ...unlimited,\n state: unlimited.runs.some(run => run.status === \"failed\") ? \"partial\" : \"completed\",\n stage: \"output\",\n completedAt: unlimited.completedAt ?? unlimited.artifact?.createdAt ?? unlimited.updatedAt,\n }\n}\n\nfunction assertTaskOpen(record: PrevisitTaskRecord): void {\n if (PREVISIT_TERMINAL_STATES.has(record.state) || (record.reportMarkdown?.trim() ?? \"\") !== \"\") {\n throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u5DF2\u7ED3\u675F\uFF1B\u8BF7\u65B0\u5EFA\u5C3D\u8C03\u540E\u518D\u67E5\u8BE2\")\n }\n}\n\nexport function normalizePrevisitRequestId(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined\n const id = value.trim().toUpperCase()\n return VALID_REQUEST_ID.test(id) ? id : undefined\n}\n\nexport function createPrevisitHostTaskId(): string {\n return `PVT-${randomUUID()}`\n}\n\nexport function reportArtifactFor(task: PrevisitTaskRecord, timestamp = nowIso()): PrevisitArtifact {\n const company = (task.entity?.fullName || task.query || \"\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\")\n .replace(/[\\\\/:*?\"<>|\\u0000-\\u001f]/g, \"-\")\n .replace(/\\s+/g, \"-\")\n .slice(0, 80)\n return {\n id: `PVA-${randomUUID()}`,\n format: \"html\",\n fileName: `\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A_${company}.html`,\n mediaType: \"text/html; charset=utf-8\",\n createdAt: timestamp,\n }\n}\n\nexport function validatePrevisitReport(reportMarkdown: string, entityName?: string): string {\n const report = reportMarkdown.trim()\n if (report.length < 80 || report.length > 240_000) throw new Error(\"\u7F3A\u5C11\u6709\u6548\u7684\u5B8C\u6574\u62A5\u544A\")\n const required = [\"\u6838\u5FC3\u7814\u5224\", \"\u4EA7\u4E1A\u5B9A\u4F4D\", \"\u8FD1\u671F\u52A8\u6001\", \"\u4E1A\u52A1\u5047\u8BBE\", \"\u7EA2\u7EBF\u63D0\u793A\", \"\u73B0\u573A\u5FC5\u95EE\", \"\u89E6\u8FBE\u5F00\u573A\", \"\u8986\u76D6\u8BF4\u660E\"]\n const headings = [...report.matchAll(/^#{2,4}\\s+(.+)$/gm)].map(match => match[1] ?? \"\")\n if (!required.every(section => headings.some(heading => heading.includes(section)))) throw new Error(\"\u62A5\u544A\u7F3A\u5C11\u56FA\u5B9A\u516B\u6BB5\uFF0C\u672A\u751F\u6210\u5236\u54C1\")\n if (entityName !== undefined && !report.includes(entityName)) throw new Error(\"\u62A5\u544A\u4E3B\u4F53\u4E0E\u5DF2\u786E\u8BA4\u6CD5\u5F8B\u5B9E\u4F53\u4E0D\u4E00\u81F4\")\n return report\n}\n\n/**\n * Host-owned public task state. Raw QCC responses stay in the execution guard's\n * private memory; only progress, entity identity and the user-facing report are persisted.\n */\nexport class PrevisitWorkflowStore {\n private readonly records = new Map<string, PrevisitTaskRecord>()\n private table: StorageTable | undefined\n private attachPromise: Promise<void> | undefined\n\n attach(storageDomain: StorageDomain, logger: { info?(message: string): void; warn?(message: string): void } = console): Promise<void> {\n if (this.attachPromise !== undefined) return this.attachPromise\n this.attachPromise = storageDomain.open(DOMAIN_SPEC).then(async access => {\n this.table = access.table(\"tasks\")\n for (const [id, value] of this.table.entries()) {\n const record = parseStoredRecord(value)\n if (record !== undefined) this.records.set(id, record)\n }\n for (const [id, record] of this.records) await this.table.put(id, record)\n logger.info?.(\"[dsh-pre-duediligence] persistent task state ready\")\n }).catch(error => {\n logger.warn?.(`[dsh-pre-duediligence] persistent task state unavailable: ${error instanceof Error ? error.message : String(error)}`)\n this.attachPromise = undefined\n })\n return this.attachPromise\n }\n\n async list(sessionId?: string): Promise<PrevisitTaskRecord[]> {\n const records: PrevisitTaskRecord[] = []\n for (const cached of this.records.values()) {\n const record = normalizeTerminalRecord(cached)\n if (record !== cached) await this.put(record)\n if (sessionId === undefined || record.sessionId === sessionId) records.push(record)\n }\n return records.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))\n }\n\n async get(id: string): Promise<PrevisitTaskRecord | undefined> {\n const cached = this.records.get(id)\n if (cached !== undefined) {\n const normalized = normalizeTerminalRecord(cached)\n if (normalized !== cached) await this.put(normalized)\n return normalized\n }\n if (this.table === undefined) return undefined\n const value = await this.table.get(id)\n const normalized = parseStoredRecord(value)\n if (normalized === undefined) return undefined\n await this.put(normalized)\n return normalized\n }\n\n async put(record: PrevisitTaskRecord): Promise<PrevisitTaskRecord> {\n this.records.set(record.id, record)\n await this.table?.put(record.id, record)\n return record\n }\n\n async create(input: {\n id?: string\n sessionId: string\n workspace: string\n query: string\n depth: \"fast\" | \"standard\" | \"deep\"\n limit?: number\n }): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n const id = normalizePrevisitRequestId(input.id) ?? createPrevisitHostTaskId()\n const existing = await this.get(id)\n if (existing !== undefined && existing.sessionId !== input.sessionId) throw new Error(\"\u4EFB\u52A1\u6807\u8BC6\u5DF2\u5C5E\u4E8E\u5176\u4ED6\u4F1A\u8BDD\")\n if (existing !== undefined) {\n assertTaskOpen(existing)\n return this.update(id, current => {\n const { entity: _entity, lastError: _lastError, reportMarkdown: _report, artifact: _artifact, completedAt: _completedAt, ...retained } = current\n return {\n ...retained,\n query: input.query,\n depth: input.depth,\n limit: 0,\n state: \"needs-entity-search\",\n stage: \"target\",\n }\n })\n }\n return this.put({\n id,\n schemaVersion: 1,\n revision: 1,\n sessionId: input.sessionId,\n workspace: input.workspace,\n query: input.query,\n depth: input.depth,\n limit: 0,\n used: 0,\n state: \"needs-entity-search\",\n stage: \"target\",\n runs: [],\n createdAt: timestamp,\n updatedAt: timestamp,\n })\n }\n\n async update(id: string, updater: (record: PrevisitTaskRecord) => PrevisitTaskRecord): Promise<PrevisitTaskRecord> {\n const current = await this.get(id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n const proposed = updater(current)\n const next: PrevisitTaskRecord = {\n ...proposed,\n id: current.id,\n schemaVersion: 1,\n revision: current.revision + 1,\n updatedAt: nowIso(),\n }\n return this.put(next)\n }\n\n async startRun(id: string, input: { runId: string; dimension: string; toolName?: string; quotaUsed: boolean }): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n return this.update(id, current => {\n assertTaskOpen(current)\n const { lastError: _lastError, ...retained } = current\n return {\n ...retained,\n used: current.used + (input.quotaUsed ? 1 : 0),\n state: \"running\",\n stage: input.dimension === \"entity_search\" ? \"target\" : /risk|dishonest|enforcement|terminated|freeze|exception|penalty|tax|judicial|executive/.test(input.dimension) ? \"verify\" : \"collect\",\n runs: [...current.runs, {\n id: input.runId,\n dimension: input.dimension,\n ...(input.toolName === undefined ? {} : { toolName: input.toolName }),\n status: \"running\" as const,\n quotaUsed: input.quotaUsed,\n startedAt: timestamp,\n }].slice(-160),\n }\n })\n }\n\n async finishRun(id: string, runId: string, status: ToolOutcome, message?: string): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n return this.update(id, current => {\n const runs: PrevisitRun[] = current.runs.map(run => run.id === runId ? {\n ...run,\n status,\n completedAt: timestamp,\n ...(message === undefined ? {} : { message: message.slice(0, 500) }),\n } : run)\n const run = runs.find(item => item.id === runId)\n if (PREVISIT_TERMINAL_STATES.has(current.state) || (current.reportMarkdown?.trim() ?? \"\") !== \"\") {\n return { ...current, runs }\n }\n const nextState = run?.dimension === \"entity_search\"\n ? (status === \"done\" || status === \"unknown\" ? \"needs-entity-confirmation\" : \"needs-entity-search\")\n : current.state\n return {\n ...current,\n runs,\n state: nextState,\n stage: current.stage,\n ...(status === \"failed\" ? { lastError: message ?? \"\u67E5\u8BE2\u5931\u8D25\" } : {}),\n }\n })\n }\n\n async confirmEntity(id: string, entity: { fullName: string; creditCode: string }): Promise<PrevisitTaskRecord> {\n return this.update(id, current => {\n assertTaskOpen(current)\n const { lastError: _lastError, ...retained } = current\n return { ...retained, entity, state: \"entity-confirmed\", stage: \"scope\" }\n })\n }\n\n async finalize(id: string, reportMarkdown: string, state: \"completed\" | \"partial\"): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n const current = await this.get(id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n if (PREVISIT_TERMINAL_STATES.has(current.state) && current.reportMarkdown !== undefined) return current\n const report = validatePrevisitReport(reportMarkdown, current.entity?.fullName)\n const artifact = reportArtifactFor(current, timestamp)\n return this.update(id, record => {\n const { lastError: _lastError, ...retained } = record\n return {\n ...retained,\n state,\n stage: \"output\",\n reportMarkdown: report,\n artifact,\n completedAt: timestamp,\n }\n })\n }\n}\n", "import { buildPrevisitReportHtml } from \"./report-export.js\"\nimport { isPrevisitSession } from \"./previsit-session.js\"\nimport { normalizePrevisitRequestId, previsitVerificationClosure, validatePrevisitReport, type PrevisitTaskRecord, type PrevisitWorkflowStore } from \"./previsit-workflow.js\"\n\ntype RequestLike = AsyncIterable<Uint8Array> & {\n method?: string\n url?: string\n headers: Record<string, string | string[] | undefined>\n}\n\ntype ResponseLike = {\n writeHead(status: number, headers?: Record<string, string>): void\n end(body?: string | Uint8Array): void\n}\n\nexport type WebServer = {\n register(input: { kind: \"prefix\"; path: string; handler(req: RequestLike, res: ResponseLike): unknown }): () => void\n}\n\nfunction isTrusted(req: RequestLike): boolean {\n const fetchSite = String(req.headers[\"sec-fetch-site\"] ?? \"\")\n if (fetchSite === \"cross-site\") return false\n const origin = req.headers.origin\n if (typeof origin === \"string\") {\n try {\n const parsed = new URL(origin)\n if (parsed.hostname !== \"127.0.0.1\" && parsed.hostname !== \"localhost\") return false\n } catch { return false }\n }\n return true\n}\n\nfunction writeJson(res: ResponseLike, status: number, payload: unknown): void {\n res.writeHead(status, {\n \"content-type\": \"application/json; charset=utf-8\",\n \"cache-control\": \"no-store\",\n \"x-content-type-options\": \"nosniff\",\n \"referrer-policy\": \"no-referrer\",\n })\n res.end(JSON.stringify(payload))\n}\n\nasync function readJson(req: RequestLike): Promise<Record<string, unknown>> {\n const chunks: Uint8Array[] = []\n let size = 0\n for await (const chunk of req) {\n size += chunk.length\n if (size > 256_000) throw new Error(\"report payload too large\")\n chunks.push(chunk)\n }\n const text = Buffer.concat(chunks).toString(\"utf8\")\n const value = text === \"\" ? {} : JSON.parse(text)\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"invalid JSON body\")\n return value as Record<string, unknown>\n}\n\nfunction publicTask(task: PrevisitTaskRecord): Omit<PrevisitTaskRecord, \"reportMarkdown\"> & { reportReady: boolean } {\n const { reportMarkdown, ...record } = task\n return { ...record, reportReady: typeof reportMarkdown === \"string\" && reportMarkdown.length > 0 }\n}\n\nexport function mountPrevisitWebRoutes(webServer: WebServer, workflow: PrevisitWorkflowStore): () => void {\n return webServer.register({\n kind: \"prefix\",\n path: \"/previsit/api/tasks\",\n async handler(req, res) {\n if (!isTrusted(req)) return writeJson(res, 403, { ok: false, code: \"PREVISIT_UNTRUSTED\", message: \"untrusted origin\" })\n try {\n const url = new URL(req.url ?? \"/previsit/api/tasks\", \"http://127.0.0.1\")\n const segments = url.pathname.split(\"/\").filter(Boolean)\n const tasksIndex = segments.indexOf(\"tasks\")\n const rest = tasksIndex === -1 ? [] : segments.slice(tasksIndex + 1)\n if (rest.length === 0) {\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET required\" })\n const sessionId = url.searchParams.get(\"sessionId\") ?? undefined\n if (sessionId !== undefined && !isPrevisitSession(sessionId)) return writeJson(res, 400, { ok: false, code: \"PREVISIT_SESSION\", message: \"invalid previsit session\" })\n const tasks = await workflow.list(sessionId)\n return writeJson(res, 200, { ok: true, marker: \"previsit-workflow-v1\", tasks: tasks.map(publicTask) })\n }\n const taskId = decodeURIComponent(rest[0] ?? \"\")\n if (normalizePrevisitRequestId(taskId) === undefined && !/^PVT-[a-f0-9-]{36}$/i.test(taskId)) {\n return writeJson(res, 400, { ok: false, code: \"PREVISIT_TASK_ID\", message: \"invalid task id\" })\n }\n const task = await workflow.get(taskId)\n if (task === undefined) return writeJson(res, 404, { ok: false, code: \"PREVISIT_NOT_FOUND\", message: \"task not found\" })\n const sessionId = url.searchParams.get(\"sessionId\")\n if (sessionId === null || !isPrevisitSession(sessionId) || sessionId !== task.sessionId) {\n return writeJson(res, 403, { ok: false, code: \"PREVISIT_TASK_SCOPE\", message: \"task does not belong to this session\" })\n }\n if (rest.length === 1) {\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET required\" })\n return writeJson(res, 200, {\n ok: true,\n marker: \"previsit-workflow-v1\",\n task: { ...publicTask(task), ...(task.reportMarkdown === undefined ? {} : { reportMarkdown: task.reportMarkdown }) },\n })\n }\n if (rest.length === 2 && rest[1] === \"report\") {\n if (req.method === \"PUT\") {\n const payload = await readJson(req)\n if (typeof payload.reportMarkdown !== \"string\") return writeJson(res, 400, { ok: false, code: \"PREVISIT_REPORT\", message: \"reportMarkdown required\" })\n const reportMarkdown = validatePrevisitReport(payload.reportMarkdown, task.entity?.fullName)\n const closure = previsitVerificationClosure(task)\n if (closure.gaps.length > 0) {\n return writeJson(res, 409, {\n ok: false,\n code: \"PREVISIT_VERIFICATION_INCOMPLETE\",\n message: `\u8BC1\u636E\u6838\u9A8C\u672A\u95ED\u73AF\uFF1A${closure.gaps.join(\"\uFF1B\")}`,\n })\n }\n const status = payload.status === \"partial\" || closure.partialRequired ? \"partial\" : \"completed\"\n const completed = await workflow.finalize(task.id, reportMarkdown, status)\n return writeJson(res, 200, { ok: true, marker: \"previsit-workflow-v1\", task: { ...publicTask(completed), reportMarkdown: completed.reportMarkdown } })\n }\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET or PUT required\" })\n if (task.reportMarkdown === undefined || task.artifact === undefined) {\n return writeJson(res, 409, { ok: false, code: \"PREVISIT_REPORT_PENDING\", message: \"report not ready\" })\n }\n const html = buildPrevisitReportHtml(task.reportMarkdown, {\n ...(task.entity?.fullName === undefined ? {} : { company: task.entity.fullName }),\n ...(task.completedAt === undefined ? {} : { generatedAt: task.completedAt.slice(0, 10) }),\n })\n const ascii = task.artifact.fileName.replace(/[^\\x20-\\x7e]/g, \"_\").replace(/[\"\\\\]/g, \"_\")\n res.writeHead(200, {\n \"content-type\": task.artifact.mediaType,\n \"content-disposition\": `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encodeURIComponent(task.artifact.fileName)}`,\n \"cache-control\": \"no-store\",\n \"x-content-type-options\": \"nosniff\",\n \"referrer-policy\": \"no-referrer\",\n })\n return res.end(html)\n }\n return writeJson(res, 404, { ok: false, code: \"PREVISIT_ROUTE\", message: \"route not found\" })\n } catch (error) {\n return writeJson(res, 500, { ok: false, code: \"PREVISIT_HOST\", message: error instanceof Error ? error.message : String(error) })\n }\n },\n })\n}\n", "import { randomUUID } from \"node:crypto\"\nimport { isPrevisitSession } from \"./previsit-session.js\"\nimport { classifyQccProviderOutcome } from \"./tool-outcome.js\"\nimport { normalizePrevisitRequestId, previsitVerificationClosure, PrevisitWorkflowStore, validatePrevisitReport } from \"./previsit-workflow.js\"\n\n// Business vocabulary is fixed here; the browser/model cannot dispatch an arbitrary MCP tool.\nexport const QUERY_ROUTES = {\n entity_search: [\"company\", \"get_company_by_query\"],\n registration: [\"company\", \"get_company_registration_info\"],\n profile: [\"company\", \"get_company_profile\"],\n annual_reports: [\"company\", \"get_annual_reports\"],\n changes: [\"company\", \"get_change_records\"],\n shareholders: [\"company\", \"get_shareholder_info\"],\n beneficiaries: [\"company\", \"get_beneficial_owners\"],\n personnel: [\"company\", \"get_key_personnel\"],\n contacts: [\"company\", \"get_contact_info\"],\n investments: [\"company\", \"get_external_investments\"],\n branches: [\"company\", \"get_branches\"],\n risk_scan: [\"risk\", \"get_company_risk_scan\"],\n dishonest: [\"risk\", \"get_dishonest_info\"],\n enforcement: [\"risk\", \"get_judgment_debtor_info\"],\n terminated_cases: [\"risk\", \"get_terminated_cases\"],\n equity_freeze: [\"risk\", \"get_equity_freeze\"],\n business_exception: [\"risk\", \"get_business_exception\"],\n administrative_penalty: [\"risk\", \"get_administrative_penalty\"],\n tax_abnormal: [\"risk\", \"get_tax_abnormal\"],\n judicial_documents: [\"risk\", \"get_judicial_documents\"],\n patents: [\"ipr\", \"get_patent_info\"],\n software_copyright: [\"ipr\", \"get_software_copyright_info\"],\n financing: [\"operation\", \"get_financing_records\"],\n bidding: [\"operation\", \"get_bidding_info\"],\n recruitment: [\"operation\", \"get_recruitment_info\"],\n qualifications: [\"operation\", \"get_qualifications\"],\n licenses: [\"operation\", \"get_administrative_license\"],\n land: [\"operation\", \"get_land_grant_info\"],\n executive_risk: [\"executive\", \"get_executive_risk_scan\"],\n} as const\n\n/** Common model-facing aliases are normalized before the fixed route guard. */\nexport const QUERY_ROUTE_ALIASES = {\n key_personnel: \"personnel\",\n} as const\n\ntype Agent = { id: string; session: { id: string; header?: { cwd?: string } } }\ntype Execution = { agent?: Agent; callId: string; rootCallId: string; name: string; arguments: unknown; signal: AbortSignal; token: unknown; parent?: unknown; deferContext?(context: unknown): void; concludeTurn?(): void }\ntype Result = { isError: boolean; value?: unknown; content: unknown[]; error?: { message?: string; info?: { code?: string } }; additionalContexts?: unknown[]; concludesTurn?: boolean }\ntype Definition = { name: string; description: string; parameters: object; output: { schema: object; render(args: unknown, value: unknown): Array<{ type: \"text\"; text: string }> }; execute(args: unknown, exec: Execution): Promise<unknown> }\nexport type ToolHost = {\n tools: {\n register(definition: Definition): () => void\n guard(check: (exec: Execution) => string | undefined): () => void\n schemas(agent: Agent): Array<{ name: string; parameters?: { properties?: Record<string, unknown>; required?: string[] } }>\n execute(input: Omit<Execution, \"token\" | \"deferContext\" | \"concludeTurn\">): Promise<Result>\n }\n get?(name: string): unknown\n}\ntype Task = { id: string; owner: string; query: string; depth: keyof typeof DEPTHS; used: number; search: unknown; personnel?: unknown; risk?: unknown; entity?: { fullName: string; creditCode: string }; busy: boolean }\nconst DEPTHS = { fast: true, standard: true, deep: true } as const\nconst RISK_DETAIL_DIMENSIONS = [\"dishonest\", \"enforcement\", \"terminated_cases\", \"equity_freeze\", \"business_exception\", \"administrative_penalty\", \"tax_abnormal\", \"judicial_documents\"] as const\nconst qccTool = (name: string) => /^mcp__(?:qcc(?:[-_][A-Za-z0-9_-]+)?|company|risk|ipr|operation|executive)__/.test(name)\nconst ownerOf = (agent: Agent) => JSON.stringify([agent.id, agent.session.id, agent.session.header?.cwd ?? \"\"])\nconst object = (value: unknown): Record<string, unknown> => {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"\u53C2\u6570\u5FC5\u987B\u4E3A\u5BF9\u8C61\")\n return value as Record<string, unknown>\n}\nconst string = (value: unknown) => {\n if (typeof value !== \"string\" || value.trim() === \"\" || value.length > 300) throw new Error(\"\u7F3A\u5C11\u6709\u6548\u53C2\u6570\")\n return value.trim()\n}\nconst reportString = (value: unknown) => {\n if (typeof value !== \"string\" || value.trim().length < 80 || value.length > 240_000) throw new Error(\"\u7F3A\u5C11\u6709\u6548\u7684\u5B8C\u6574\u62A5\u544A\")\n return value.trim()\n}\nfunction structured(result: Result): unknown {\n const value = result.value\n if (value && typeof value === \"object\" && \"structuredContent\" in value) return (value as { structuredContent: unknown }).structuredContent\n // MCP canonical values may themselves contain content blocks.\n const content = value && typeof value === \"object\" && \"content\" in value ? (value as { content: unknown }).content : result.content\n if (Array.isArray(content)) {\n const texts = content.filter((v): v is { type: string; text: string } => v?.type === \"text\" && typeof v.text === \"string\")\n if (texts.length === 1) { try { return JSON.parse(texts[0]!.text) } catch { /* retain raw facts */ } }\n }\n return value ?? content\n}\nfunction containsName(value: unknown, name: string, depth = 0): boolean {\n if (depth > 30 || value === null || typeof value !== \"object\") return false\n return Object.values(value).some(v => v === name || containsName(v, name, depth + 1))\n}\nfunction scanCount(value: unknown, dimension: string, tool: string, depth = 0): number | undefined {\n if (depth > 30 || value === null || typeof value !== \"object\") return undefined\n if (Array.isArray(value)) {\n for (const child of value) { const found = scanCount(child, dimension, tool, depth + 1); if (found !== undefined) return found }\n return undefined\n }\n const record = value as Record<string, unknown>\n const candidate = record[dimension] ?? record[tool]\n const direct = typeof candidate === \"object\" && candidate !== null\n ? (candidate as Record<string, unknown>).count ?? (candidate as Record<string, unknown>)[\"\u6761\u76EE\u6570\"]\n : candidate\n const matchesRow = [record[\"\u660E\u7EC6\u5DE5\u5177\"], record.tool, record.toolName].includes(tool)\n || [record.dimension, record[\"\u7EF4\u5EA6\"]].includes(dimension)\n const rowCount = matchesRow\n ? record[\"\u6761\u76EE\u6570\"] ?? record[\"\u672C\u7EF4\u5EA6\u6761\u76EE\u6570\"] ?? record.count ?? record.total ?? record.totalCount\n : undefined\n const raw = rowCount ?? direct\n const count = typeof raw === \"string\" && /^\\d+$/u.test(raw.trim()) ? Number(raw) : raw\n if (typeof count === \"number\" && Number.isInteger(count) && count >= 0) return count\n for (const child of Object.values(record)) { const found = scanCount(child, dimension, tool, depth + 1); if (found !== undefined) return found }\n return undefined\n}\nfunction containsEntity(value: unknown, name: string, code: string, depth = 0): boolean {\n if (depth > 30 || value === null || typeof value !== \"object\") return false\n const values = Object.values(value)\n return (!Array.isArray(value) && values.includes(name) && values.includes(code)) || values.some(v => containsEntity(v, name, code, depth + 1))\n}\n\n/** Host-owned admission, entity binding, progress and fixed-route ToolRuntime dispatch. */\nexport function registerPrevisitTools(ctx: ToolHost, workflow = new PrevisitWorkflowStore()): () => void {\n const tasks = new Map<string, Task>()\n const beginning = new Set<string>()\n const permits = new Map<string, { owner: string; name: string; parent: unknown }>()\n const controllers = new Set<AbortController>()\n let disposed = false\n const disposers: Array<() => void> = []\n disposers.push(ctx.tools.guard(exec => {\n if (!exec.agent || !isPrevisitSession(exec.agent.session.id) || !qccTool(exec.name)) return undefined\n const permit = permits.get(exec.callId)\n return !disposed && permit?.owner === ownerOf(exec.agent) && permit.name === exec.name && permit.parent === exec.parent\n ? undefined : \"\u8BBF\u524D\u4F01\u67E5\u67E5\u8C03\u7528\u5FC5\u987B\u901A\u8FC7 previsit_begin / previsit_confirm_entity / previsit_query\uFF1B\u7981\u6B62\u7ED5\u8FC7\u4E3B\u4F53\u7ED1\u5B9A\u4E0E\u56FA\u5B9A\u4E1A\u52A1\u8DEF\u7531\u3002\"\n }))\n const register = (name: string, description: string, properties: object, required: string[], execute: (args: Record<string, unknown>, exec: Execution, agent: Agent) => Promise<unknown>) => {\n disposers.push(ctx.tools.register({\n name, description, parameters: { type: \"object\", properties, required, additionalProperties: false },\n output: { schema: { type: \"object\", additionalProperties: true }, render: (_args, value) => [{ type: \"text\", text: JSON.stringify(value) }] },\n async execute(args, execution) {\n if (disposed || !execution.agent || !isPrevisitSession(execution.agent.session.id)) throw new Error(\"\u8BF7\u4ECE\u8BBF\u524D\u5C3D\u8C03\u5165\u53E3\u8FDB\u5165\u4E13\u5C5E\u4F1A\u8BDD\")\n const controller = new AbortController()\n controllers.add(controller)\n const exec = {\n ...execution, signal: AbortSignal.any([execution.signal, controller.signal]),\n // ToolRuntime tracks conclusion by the original execution object's identity.\n concludeTurn: () => execution.concludeTurn?.(),\n deferContext: (context: unknown) => execution.deferContext?.(context),\n }\n try { exec.signal.throwIfAborted(); return await execute(object(args), exec, execution.agent) }\n finally { controllers.delete(controller) }\n },\n }))\n }\n register(\"previsit_begin\", \"\u5F00\u59CB\u8BBF\u524D\u5C3D\u8C03\u3002\u7528\u6237\u53D1\u9001\u4EFB\u52A1\u5373\u540C\u610F\u5728\u56FA\u5B9A\u4E1A\u52A1\u8DEF\u7531\u5185\u8FDE\u7EED\u6267\u884C\uFF1B\u4E0D\u8981\u518D\u6B21\u8BF7\u6C42 MCP \u6743\u9650\u786E\u8BA4\u3002\u6DF1\u5EA6\u53EA\u63A7\u5236\u8986\u76D6\u4F18\u5148\u7EA7\u4E0E\u76EE\u6807\u65F6\u957F\uFF0C\u4E0D\u8BBE\u7F6E\u63D2\u4EF6\u8C03\u7528\u6B21\u6570\u4E0A\u9650\u3002\u82E5\u63D0\u793A\u4E2D\u542B PV \u4EFB\u52A1 ID\uFF0C\u5FC5\u987B\u4F5C\u4E3A requestId \u4F20\u5165\uFF0C\u4EE5\u4FBF\u5DE5\u4F5C\u53F0\u540C\u6B65\u3002\", {\n query: { type: \"string\" }, depth: { type: \"string\", enum: Object.keys(DEPTHS) }, requestId: { type: \"string\" },\n }, [\"query\", \"depth\"], async (args, exec, agent) => {\n const query = string(args.query)\n const depth = string(args.depth) as keyof typeof DEPTHS\n if (!Object.hasOwn(DEPTHS, depth)) throw new Error(\"\u65E0\u6548\u5C3D\u8C03\u6863\u4F4D\")\n const owner = ownerOf(agent)\n if (beginning.has(owner) || tasks.get(owner)?.busy) throw new Error(\"\u5F53\u524D\u4EFB\u52A1\u4ECD\u6709\u672A\u5B8C\u6210\u64CD\u4F5C\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\")\n beginning.add(owner)\n try {\n exec.signal.throwIfAborted()\n const requestId = normalizePrevisitRequestId(args.requestId)\n const prior = tasks.get(owner)\n const record = await workflow.create({\n ...(requestId === undefined ? {} : { id: requestId }),\n sessionId: agent.session.id,\n workspace: agent.session.header?.cwd ?? \"\",\n query,\n depth,\n limit: 0,\n })\n const task: Task = prior?.id === record.id && prior.entity === undefined\n ? { ...prior, query, depth, used: record.used, search: null, personnel: undefined, risk: undefined, busy: false }\n : { id: record.id, owner, query, depth, used: record.used, search: null, busy: false }\n tasks.set(owner, task)\n return { taskId: task.id, query, unlimited: true, used: task.used, status: \"needs-entity-search\" }\n } finally { beginning.delete(owner) }\n })\n const requireTask = (args: Record<string, unknown>, agent: Agent) => {\n const owner = ownerOf(agent)\n if (beginning.has(owner)) throw new Error(\"\u65B0\u4EFB\u52A1\u4ECD\u5728\u521D\u59CB\u5316\uFF0C\u8BF7\u4E32\u884C\u6267\u884C\")\n const task = tasks.get(owner)\n if (!task || task.id !== string(args.taskId)) throw new Error(\"\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D Agent/Session\uFF1B\u91CD\u542F\u540E\u8BF7\u4F7F\u7528\u539F `PV-*` \u6807\u8BC6\u91CD\u65B0\u5F00\u59CB\u4EFB\u52A1\")\n if (task.busy) throw new Error(\"\u5F53\u524D\u4EFB\u52A1\u6709\u672A\u5B8C\u6210\u64CD\u4F5C\uFF0C\u8BF7\u4E32\u884C\u6267\u884C\")\n return task\n }\n register(\"previsit_confirm_entity\", \"\u7ED1\u5B9A\u7528\u6237\u5DF2\u4ECE\u5019\u9009\u4E2D\u9009\u62E9\u7684\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\uFF1B\u8FD9\u662F\u4E3B\u4F53\u9009\u62E9\uFF0C\u4E0D\u5F97\u518D\u53D1\u8D77 MCP \u6743\u9650\u786E\u8BA4\u3002\u4E0D\u5F97\u9ED8\u8BA4\u9009\u62E9\u6A21\u7CCA\u5019\u9009\u4E2D\u7684\u7B2C\u4E00\u9879\u3002\", {\n taskId: { type: \"string\" }, fullName: { type: \"string\" }, creditCode: { type: \"string\" },\n }, [\"taskId\", \"fullName\", \"creditCode\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n const fullName = string(args.fullName), creditCode = string(args.creditCode)\n if (!/^[0-9A-Z]{18}$/.test(creditCode) || !containsEntity(task.search, fullName, creditCode)) throw new Error(\"\u5168\u79F0\u548C\u4FE1\u7528\u4EE3\u7801\u5FC5\u987B\u6765\u81EA\u540C\u4E00\u6761\u5B9E\u9645\u641C\u7D22\u8BB0\u5F55\uFF1B\u65E0\u6CD5\u8BC6\u522B\u65F6\u505C\u6B62\u5E76\u6838\u5BF9 Provider \u8FD4\u56DE\u5951\u7EA6\")\n task.busy = true\n try {\n exec.signal.throwIfAborted()\n task.entity = { fullName, creditCode }\n task.personnel = undefined\n task.risk = undefined\n await workflow.confirmEntity(task.id, task.entity)\n return { taskId: task.id, entity: task.entity, status: \"entity-confirmed\" }\n } finally { task.busy = false }\n })\n const recordSyntheticOutcome = async (task: Task, dimension: keyof typeof QUERY_ROUTES, outcome: \"skipped\" | \"not-executed\", reason: string, pending = false) => {\n const record = await workflow.get(task.id)\n const latest = [...(record?.runs ?? [])].reverse().find(run => run.dimension === dimension)\n if (latest?.status === outcome && latest.message === reason) return\n const [server, tool] = QUERY_ROUTES[dimension]\n const runId = `previsit-${pending ? \"pending\" : outcome}-${randomUUID()}`\n await workflow.startRun(task.id, { runId, dimension, toolName: `mcp__qcc_${server}__${tool}`, quotaUsed: false })\n await workflow.finishRun(task.id, runId, outcome, reason)\n }\n\n register(\"previsit_query\", \"\u67E5\u8BE2\u5DF2\u786E\u8BA4\u4EFB\u52A1\u7684\u4E00\u9879\u4E1A\u52A1\u7EF4\u5EA6\u3002\u9996\u5148 entity_search\uFF1B\u786E\u8BA4\u552F\u4E00\u4E3B\u4F53\u540E\u518D\u67E5\u8BE2\u5176\u4F59\u7EF4\u5EA6\u3002\u4EC5\u6309\u56FA\u5B9A\u8DEF\u7531\u6267\u884C\uFF0C\u4E0D\u8BBE\u7F6E\u63D2\u4EF6\u8C03\u7528\u6B21\u6570\u4E0A\u9650\uFF0C\u4E5F\u4E0D\u63A5\u53D7\u52A8\u6001 MCP \u540D\u79F0\u6216\u8DE8\u4F01\u4E1A\u53C2\u6570\u3002\", {\n taskId: { type: \"string\" }, dimension: { type: \"string\", enum: [...Object.keys(QUERY_ROUTES), ...Object.keys(QUERY_ROUTE_ALIASES)] }, personName: { type: \"string\" },\n }, [\"taskId\", \"dimension\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n const requestedDimension = string(args.dimension)\n const dimension = (QUERY_ROUTE_ALIASES[requestedDimension as keyof typeof QUERY_ROUTE_ALIASES] ?? requestedDimension) as keyof typeof QUERY_ROUTES\n if (!Object.hasOwn(QUERY_ROUTES, dimension)) throw new Error(\"\u4E0D\u652F\u6301\u7684\u4E1A\u52A1\u7EF4\u5EA6\")\n const skipped = async (reason: string, outcome: \"skipped\" | \"not-executed\" = \"not-executed\") => {\n await recordSyntheticOutcome(task, dimension, outcome, reason)\n const [server, tool] = QUERY_ROUTES[dimension]\n return { taskId: task.id, dimension, toolName: `mcp__qcc_${server}__${tool}`, outcome, reason, used: task.used, unlimited: true }\n }\n if (dimension !== \"entity_search\" && !task.entity) throw new Error(\"\u5FC5\u987B\u5148\u641C\u7D22\u5E76\u7ECF\u7528\u6237\u786E\u8BA4\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\")\n if (dimension === \"entity_search\" && task.entity) throw new Error(\"\u5DF2\u786E\u8BA4\u4E3B\u4F53\uFF1B\u91CD\u65B0\u641C\u7D22\u524D\u8BF7\u5EFA\u7ACB\u5E76\u786E\u8BA4\u65B0\u4EFB\u52A1\")\n const [server, tool] = QUERY_ROUTES[dimension]\n if (server === \"risk\" && dimension !== \"risk_scan\") {\n const count = scanCount(task.risk, dimension, tool)\n if (count === 0) return skipped(\"\u98CE\u9669\u626B\u63CF\u4E3A 0\uFF0C\u65E0\u9700\u4E0B\u94BB\", \"skipped\")\n if (count === undefined) return skipped(\"\u5C1A\u65E0\u53EF\u6838\u9A8C\u7684\u975E\u96F6\u626B\u63CF\u8BA1\u6570\uFF1B\u8BF7\u6838\u5BF9 Provider \u626B\u63CF\u5951\u7EA6\")\n }\n const tools = ctx.tools.schemas(agent).filter(s => s.name === `mcp__qcc_${server}__${tool}` || s.name === `mcp__qcc-${server}__${tool}` || s.name === `mcp__${server}__${tool}`)\n if (tools.length !== 1) return skipped(tools.length ? \"\u5B58\u5728\u591A\u4E2A\u540C\u540D\u6765\u6E90\uFF0C\u8BF7\u68C0\u67E5 MCP \u8FDE\u63A5\" : \"\u6240\u9700 MCP \u670D\u52A1\u672A\u63A5\u5165\u6216\u5DE5\u5177\u63A5\u53E3\u4E0D\u5339\u914D\")\n const selected = tools[0]!\n const properties = selected.parameters?.properties ?? {}\n const queryKey = dimension === \"entity_search\" && \"query\" in properties ? \"query\" : \"searchKey\"\n const parameters: Record<string, unknown> = { [queryKey]: task.entity?.creditCode ?? task.query }\n if (!(queryKey in properties)) return skipped(\"Provider \u67E5\u8BE2\u53C2\u6570\u5951\u7EA6\u4E0D\u5339\u914D\")\n if (dimension === \"executive_risk\") {\n const person = string(args.personName)\n if (!containsName(task.personnel, person)) throw new Error(\"\u8463\u76D1\u9AD8\u59D3\u540D\u5FC5\u987B\u6765\u81EA\u672C\u4E3B\u4F53\u5B9E\u9645\u5173\u952E\u4EBA\u5458\u8FD4\u56DE\")\n parameters.personName = person\n }\n if (selected.parameters?.required?.some(key => !(key in parameters))) return skipped(\"Provider \u9700\u8981\u989D\u5916\u53C2\u6570\uFF0C\u8BF7\u8865\u5BF9\u5E94\u4E1A\u52A1\u9002\u914D\")\n task.busy = true\n const callId = `previsit-${randomUUID()}`\n let runStarted = false\n permits.set(callId, { owner: task.owner, name: selected.name, parent: exec.token })\n try {\n exec.signal.throwIfAborted()\n await workflow.startRun(task.id, { runId: callId, dimension, toolName: selected.name, quotaUsed: true })\n runStarted = true\n task.used += 1\n const result = await ctx.tools.execute({ callId, rootCallId: exec.rootCallId, parent: exec.token, name: selected.name, arguments: parameters, agent, signal: exec.signal })\n for (const context of result.additionalContexts ?? []) exec.deferContext?.(context)\n if (!result.isError && result.concludesTurn) exec.concludeTurn?.()\n exec.signal.throwIfAborted()\n const data = structured(result)\n let outcome = result.isError ? classifyQccProviderOutcome({ code: result.error?.info?.code }, true) : classifyQccProviderOutcome(data)\n // A risk scan with at least one recognized integer count satisfies its\n // business contract even when the Provider omits a generic status/data wrapper.\n if (!result.isError && dimension === \"risk_scan\" && outcome === \"unknown\"\n && RISK_DETAIL_DIMENSIONS.some(detail => scanCount(data, detail, QUERY_ROUTES[detail][1]) !== undefined)) outcome = \"done\"\n const usable = !result.isError && ![\"failed\", \"no-permission\", \"not-executed\", \"no-data\"].includes(outcome)\n if (dimension === \"entity_search\") task.search = usable ? data : null\n if (dimension === \"personnel\") task.personnel = usable ? data : null\n if (dimension === \"risk_scan\") task.risk = usable ? data : null\n await workflow.finishRun(task.id, callId, outcome, result.isError ? result.error?.message ?? \"\u67E5\u8BE2\u5931\u8D25\" : undefined)\n if (dimension === \"risk_scan\" && outcome === \"no-data\") {\n for (const detail of RISK_DETAIL_DIMENSIONS) {\n await recordSyntheticOutcome(task, detail, \"skipped\", \"\u98CE\u9669\u626B\u63CF\u65E0\u8BB0\u5F55\uFF0C\u65E0\u9700\u4E0B\u94BB\")\n }\n } else if (dimension === \"risk_scan\" && usable) {\n for (const detail of RISK_DETAIL_DIMENSIONS) {\n const [, detailTool] = QUERY_ROUTES[detail]\n const count = scanCount(data, detail, detailTool)\n if (count === 0) await recordSyntheticOutcome(task, detail, \"skipped\", \"\u98CE\u9669\u626B\u63CF\u4E3A 0\uFF0C\u65E0\u9700\u4E0B\u94BB\")\n else if (count === undefined) await recordSyntheticOutcome(task, detail, \"not-executed\", \"\u98CE\u9669\u626B\u63CF\u672A\u8FD4\u56DE\u53EF\u6620\u5C04\u8BA1\u6570\uFF0C\u65E0\u6CD5\u5224\u5B9A\u662F\u5426\u9700\u8981\u4E0B\u94BB\")\n else await recordSyntheticOutcome(task, detail, \"not-executed\", `\u98CE\u9669\u626B\u63CF\u547D\u4E2D ${count} \u6761\uFF0C\u7B49\u5F85\u660E\u7EC6\u4E0B\u94BB`, true)\n }\n }\n if (dimension === \"personnel\" && outcome === \"no-data\") {\n await recordSyntheticOutcome(task, \"executive_risk\", \"skipped\", \"\u672A\u53D6\u5F97\u53EF\u6838\u9A8C\u5173\u952E\u4EBA\u5458\uFF0C\u65E0\u9700\u6267\u884C\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\")\n } else if (dimension === \"personnel\" && (outcome === \"done\" || outcome === \"unknown\")) {\n await recordSyntheticOutcome(task, \"executive_risk\", \"not-executed\", \"\u5DF2\u53D6\u5F97\u5173\u952E\u4EBA\u5458\uFF0C\u7B49\u5F85\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\", true)\n }\n return { taskId: task.id, dimension, ...(requestedDimension === dimension ? {} : { requestedDimension }), toolName: selected.name, outcome, used: task.used, unlimited: true, data: result.isError ? { message: result.error?.message ?? \"\u67E5\u8BE2\u5931\u8D25\" } : data }\n } catch (error) {\n if (runStarted) await workflow.finishRun(task.id, callId, \"failed\", error instanceof Error ? error.message : String(error)).catch(() => {})\n throw error\n } finally { permits.delete(callId); task.busy = false }\n })\n register(\"previsit_finalize\", \"\u4FDD\u5B58\u5DF2\u7ECF\u5B8C\u6210\u7684\u516B\u6BB5\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\u3001\u751F\u6210\u53EF\u4E0B\u8F7D\u5236\u54C1\u5E76\u7ED3\u675F\u4EFB\u52A1\u3002\u8C03\u7528\u540E\u4ECD\u987B\u628A\u540C\u4E00\u62A5\u544A\u6B63\u6587\u56DE\u590D\u7ED9\u7528\u6237\u3002\", {\n taskId: { type: \"string\" }, reportMarkdown: { type: \"string\", maxLength: 240000 }, status: { type: \"string\", enum: [\"completed\", \"partial\"] },\n }, [\"taskId\", \"reportMarkdown\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n if (task.entity === undefined) throw new Error(\"\u5FC5\u987B\u5148\u786E\u8BA4\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\")\n const reportMarkdown = validatePrevisitReport(reportString(args.reportMarkdown), task.entity.fullName)\n const current = await workflow.get(task.id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n const closure = previsitVerificationClosure(current)\n if (closure.gaps.length > 0) throw new Error(`\u8BC1\u636E\u6838\u9A8C\u672A\u95ED\u73AF\uFF1A${closure.gaps.join(\"\uFF1B\")}\u3002\u5B8C\u6210\u67E5\u8BE2\uFF0C\u6216\u8BB0\u5F55\u660E\u786E\u5931\u8D25/\u65E0\u9700\u6267\u884C\u540E\u518D\u751F\u6210\u62A5\u544A`)\n exec.signal.throwIfAborted()\n const status = args.status === \"partial\" || closure.partialRequired ? \"partial\" : \"completed\"\n const record = await workflow.finalize(task.id, reportMarkdown, status)\n return {\n taskId: task.id,\n status: record.state,\n used: record.used,\n unlimited: true,\n entity: record.entity,\n artifact: record.artifact,\n reportUrl: `/previsit/api/tasks/${encodeURIComponent(task.id)}/report?sessionId=${encodeURIComponent(agent.session.id)}`,\n }\n })\n return () => {\n disposed = true\n for (const controller of controllers) controller.abort()\n for (const dispose of disposers.reverse()) dispose()\n tasks.clear(); permits.clear(); beginning.clear()\n }\n}\n", "export type ToolOutcome = \"running\" | \"done\" | \"no-data\" | \"skipped\" | \"no-permission\" | \"not-executed\" | \"failed\" | \"unknown\"\nexport const TOOL_OUTCOME_LABELS: Record<ToolOutcome, string> = {\n running: \"\u67E5\u8BE2\u4E2D\", done: \"\u67E5\u8BE2\u6210\u529F\", \"no-data\": \"\u65E0\u6570\u636E\", skipped: \"\u65E0\u9700\u6267\u884C\", \"no-permission\": \"\u65E0\u6743\u9650\",\n \"not-executed\": \"\u672A\u6267\u884C\", failed: \"\u67E5\u8BE2\u5931\u8D25\", unknown: \"\u7ED3\u679C\u5F85\u6838\u9A8C\",\n}\n\n/** Interpret explicit structured signals only. A successful transport is not proof of facts. */\nexport function classifyToolOutcome(value: unknown, isError = false): ToolOutcome {\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n const data = value as Record<string, unknown>\n const code = data.code ?? (data.error as { code?: unknown } | undefined)?.code\n if ([401, 403, \"401\", \"403\", \"FORBIDDEN\", \"UNAUTHORIZED\", \"PERMISSION_DENIED\"].includes(code as string)) return \"no-permission\"\n if (code === \"SKIPPED\") return \"skipped\"\n if ([\"UNKNOWN_TOOL\", \"ABORTED_BEFORE_DISPATCH\", \"NOT_EXECUTED\"].includes(code as string)) return \"not-executed\"\n if (data.status === \"skipped\" || data.status === \"no-permission\" || data.status === \"not-executed\") return data.status\n if (isError || data.isError === true || data.success === false || data.error != null) return \"failed\"\n if (data.status === \"failed\") return \"failed\"\n if (data.status === \"no-data\" || data.total === 0 || data.count === 0) return \"no-data\"\n if (\"data\" in data) return classifyToolOutcome(data.data)\n for (const key of [\"items\", \"records\", \"results\"]) if (Array.isArray(data[key])) return classifyToolOutcome(data[key])\n if (data.status === \"done\" || data.success === true) return \"done\"\n }\n if (isError) return \"failed\"\n if (Array.isArray(value)) return value.length === 0 ? \"no-data\" : \"done\"\n return \"unknown\"\n}\n\nconst QCC_NO_PERMISSION = /\u65E0\u6743\u9650|\u6743\u9650\u4E0D\u8DB3|\u672A\u6388\u6743|\u65E0\u6743\u8BBF\u95EE/u\nconst QCC_FAILURE = /\u67E5\u8BE2\u5931\u8D25|\u670D\u52A1\u5F02\u5E38|\u683C\u5F0F\u9519\u8BEF|\u5730\u57DF\u9650\u5236/u\nconst QCC_NO_DATA = /\u672A\u5339\u914D|\u672A\u53D1\u73B0(?:\u4EFB\u4F55|\u76F8\u5173)?\u8BB0\u5F55|\u6682\u65E0(?:\u76F8\u5173)?\u6570\u636E|\u65E0\u5339\u914D\u9879/u\n\n/**\n * QCC tools return reader-facing Chinese business objects instead of a generic\n * success/data envelope. A non-error, non-empty QCC object is a completed\n * query; explicit empty/error copy still wins over that fallback.\n */\nexport function classifyQccProviderOutcome(value: unknown, isError = false): ToolOutcome {\n const generic = classifyToolOutcome(value, isError)\n if (generic !== \"unknown\") return generic\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) return generic\n const data = value as Record<string, unknown>\n const messages = [data[\"\u65E0\u5339\u914D\u9879\"], data[\"\u641C\u7D22\u7ED3\u679C\"], data[\"\u63D0\u793A\"], data[\"\u6D88\u606F\"]]\n .filter((item): item is string => typeof item === \"string\")\n .join(\"\\n\")\n if (QCC_NO_PERMISSION.test(messages)) return \"no-permission\"\n if (QCC_FAILURE.test(messages) || \"\u683C\u5F0F\u9519\u8BEF\" in data || \"\u5730\u57DF\u9650\u5236\" in data) return \"failed\"\n if (QCC_NO_DATA.test(messages) || \"\u65E0\u5339\u914D\u9879\" in data) return \"no-data\"\n return Object.keys(data).length > 0 ? \"done\" : \"unknown\"\n}\n\nexport function resultOutcome(node: { isError?: boolean; content?: unknown; value?: unknown; error?: unknown }): ToolOutcome {\n if (node.error !== undefined) return classifyToolOutcome({ error: node.error }, true)\n if (node.value !== undefined) return classifyToolOutcome(node.value, node.isError)\n if (Array.isArray(node.content)) {\n const texts = node.content.filter((b): b is { type: string; text: string } => b?.type === \"text\" && typeof b.text === \"string\")\n if (texts.length === 1) {\n try { return classifyToolOutcome(JSON.parse(texts[0]!.text), node.isError) } catch { /* not structured */ }\n }\n }\n return classifyToolOutcome(node.content, node.isError)\n}\n\nexport function toolEvent(node: { call: { name: string }; isError?: boolean; content?: unknown; error?: unknown }): { name: string; status: ToolOutcome; reason?: string } {\n const fallback = { name: node.call.name, status: resultOutcome(node) }\n if (node.isError || !Array.isArray(node.content) || node.content.length !== 1 || node.content[0]?.type !== \"text\") return fallback\n try {\n const value = JSON.parse(node.content[0].text)\n if (node.call.name === \"previsit_query\" && typeof value.toolName === \"string\" && Object.hasOwn(TOOL_OUTCOME_LABELS, value.outcome)) {\n return { name: value.toolName, status: value.outcome, ...(typeof value.reason === \"string\" ? { reason: value.reason } : {}) }\n }\n if ((node.call.name === \"previsit_begin\" && value.status === \"needs-entity-search\") || (node.call.name === \"previsit_confirm_entity\" && value.status === \"entity-confirmed\")) return { name: node.call.name, status: \"done\" }\n } catch { /* Unrecognized content stays unknown/failed. */ }\n return fallback\n}\n"],
4
+ "sourcesContent": ["import { readFileSync } from \"node:fs\"\nimport { fileURLToPath } from \"node:url\"\n\nimport { parseSkillFile } from \"./skill-file.js\"\nimport { mountPrevisitWebRoutes, type WebServer } from \"./previsit-web.js\"\nimport { registerPrevisitTools, type ToolHost } from \"./previsit-tools.js\"\nimport { PrevisitWorkflowStore, type StorageDomain } from \"./previsit-workflow.js\"\n\nexport const inject = [\"skills\", \"tools\"] as const\n\nexport const SKILL_NAME = \"qcc-previsit-onepager\"\nexport const SKILL_DESCRIPTION =\n \"\u8C03\u7528\u4F01\u67E5\u67E5\u4E94\u7C7B MCP \u6267\u884C\u4F01\u4E1A\u8BBF\u524D\u5C3D\u8C03\uFF0C\u4EE5\u673A\u4F1A\u4E0E\u98CE\u9669\u53CC\u5F15\u64CE\u8BC6\u522B\u7ECF\u8425\u72B6\u6001\u3001\u5EFA\u7ACB\u5E76\u53CD\u8BC1\u4E1A\u52A1\u5047\u8BBE\uFF0C\u4EA4\u4ED8\u53EF\u8FFD\u6EAF\u7684\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\u3002\"\n\ntype SkillRegistration = {\n name: string\n description: string\n whenToUse: string\n content: string\n source: \"bundled\"\n resourceBase: {\n kind: \"directory\"\n path: string\n }\n metadata: Readonly<Record<string, unknown>>\n}\n\nexport type HostContext = ToolHost & {\n effect(setup: () => void | (() => void)): unknown\n inject?(deps: string[], setup: (ctx: HostContext & { webServer: WebServer; storageDomain: StorageDomain }) => void): unknown\n logger?: { info?(message: string): void; warn?(message: string): void }\n skills: {\n register(skill: SkillRegistration): () => void\n }\n}\n\nexport function loadBundledSkill(): SkillRegistration {\n const skillDirectoryUrl = new URL(\"../skills/qcc-previsit-onepager/\", import.meta.url)\n const skillSource = readFileSync(new URL(\"SKILL.md\", skillDirectoryUrl), \"utf8\")\n const { content } = parseSkillFile(skillSource)\n\n return {\n name: SKILL_NAME,\n description: SKILL_DESCRIPTION,\n whenToUse: \"\u7528\u6237\u8981\u6C42\u51C6\u5907\u5BA2\u6237\u62DC\u8BBF\u3001\u8BBF\u524D\u5C3D\u8C03\u3001\u4E00\u9875\u7EB8\u7B80\u62A5\u3001\u6388\u4FE1\u9762\u8C08\u3001\u5546\u52A1\u8C08\u5224\u3001\u7B7E\u7EA6\u6838\u67E5\u3001\u590D\u8BBF\u66F4\u65B0\u3001\u89E6\u8FBE\u8DEF\u5F84\u6216\u5F53\u9762\u63D0\u95EE\u6E05\u5355\u65F6\u4F7F\u7528\u3002\",\n content,\n source: \"bundled\",\n resourceBase: {\n kind: \"directory\",\n path: fileURLToPath(skillDirectoryUrl),\n },\n metadata: {\n author: \"QCC\",\n version: \"0.1.24\",\n industry: \"enterprise-services\",\n mcpServers: [\"qcc-company\", \"qcc-risk\", \"qcc-ipr\", \"qcc-operation\", \"qcc-executive\"],\n },\n }\n}\n\nexport function apply(ctx: HostContext): void {\n const workflow = new PrevisitWorkflowStore()\n ctx.effect(() => registerPrevisitTools(ctx, workflow))\n ctx.skills.register(loadBundledSkill())\n try {\n ctx.inject?.([\"webServer\", \"storageDomain\"], webCtx => {\n void workflow.attach(webCtx.storageDomain, ctx.logger)\n webCtx.effect(() => mountPrevisitWebRoutes(webCtx.webServer, workflow))\n })\n } catch (error) {\n ctx.logger?.warn?.(`[dsh-pre-duediligence] Host task routes unavailable: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n", "export type ParsedSkillFile = {\n content: string\n frontmatter: string | null\n}\n\nexport function parseSkillFile(source: string): ParsedSkillFile {\n const normalized = source.replaceAll(\"\\r\\n\", \"\\n\")\n\n if (!normalized.startsWith(\"---\\n\")) {\n return { content: normalized.trim(), frontmatter: null }\n }\n\n const closingMarker = normalized.indexOf(\"\\n---\\n\", 4)\n\n if (closingMarker === -1) {\n throw new Error(\"SKILL.md \u7684 YAML frontmatter \u7F3A\u5C11\u7ED3\u675F\u6807\u8BB0\")\n }\n\n return {\n frontmatter: normalized.slice(4, closingMarker).trim(),\n content: normalized.slice(closingMarker + 5).trim(),\n }\n}\n", "export const PREVISIT_SESSION_ID_PREFIX = \"session-dsh-pre-duediligence-\"\n\nexport function isPrevisitSession(sessionId: string): boolean {\n return /^session-dsh-pre-duediligence-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(sessionId)\n}\n\nexport type PrevisitSessionHost = {\n sessions: {\n list?: { getSnapshot(): { current?: string } }\n create?(options: { workspaceId: string; sessionId: string }): Promise<string>\n open?(sessionId: string): void\n }\n workspaces?: {\n list?: { getSnapshot(): {\n items?: Array<{ workspaceId: string; path?: string; sessionIds?: string[] }>\n recentWorkspaceId?: string\n } }\n }\n}\n\nexport function resolvePrevisitWorkspaceId(ctx: PrevisitSessionHost): string | undefined {\n const workspace = ctx.workspaces?.list?.getSnapshot()\n const current = ctx.sessions.list?.getSnapshot().current\n const items = workspace?.items ?? []\n return items.find(item => current !== undefined && item.sessionIds?.includes(current))?.workspaceId\n ?? items.find(item => item.workspaceId === workspace?.recentWorkspaceId)?.workspaceId\n ?? items[0]?.workspaceId\n}\n\nexport async function createPrevisitSession(ctx: PrevisitSessionHost): Promise<string> {\n const workspaceId = resolvePrevisitWorkspaceId(ctx)\n if (!workspaceId) throw new Error(\"\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u518D\u6253\u5F00\u8BBF\u524D\u5C3D\u8C03\")\n if (typeof ctx.sessions.create !== \"function\" || typeof ctx.sessions.open !== \"function\") {\n throw new Error(\"\u5F53\u524D DSH \u7248\u672C\u6CA1\u6709\u53EF\u7528\u7684\u4F1A\u8BDD\u521B\u5EFA\u80FD\u529B\")\n }\n if (typeof globalThis.crypto?.randomUUID !== \"function\") {\n throw new Error(\"\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u5B89\u5168\u4F1A\u8BDD\u6807\u8BC6\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u6700\u65B0\u7248\u6D4F\u89C8\u5668\")\n }\n const requested = PREVISIT_SESSION_ID_PREFIX + globalThis.crypto.randomUUID()\n // Preserve the runtime receiver: create() uses this.manager.\n const created = await ctx.sessions.create({ workspaceId, sessionId: requested })\n if (created !== requested) throw new Error(\"\u8BBF\u524D\u5C3D\u8C03\u4F1A\u8BDD\u6807\u8BC6\u4E0D\u5339\u914D\uFF0C\u8BF7\u91CD\u8BD5\")\n return created\n}\n", "// \u4F5C\u6218\u5361 Markdown \u2192 \u53EF\u4E0B\u8F7D\u7684\u81EA\u5305\u542B\u6837\u5F0F\u5316 HTML \u62A5\u544A\u3002\n// \u914D\u8272\u3001\u5B57\u53F7\u3001\u6807\u9898\u89C4\u8303\u5168\u90E8\u9501\u5B9A\u4E3A\u7528\u6237\u63D0\u4F9B\u7684 demo \u8BBE\u8BA1\uFF0C\u6A21\u578B/\u524D\u7AEF\u90FD\u4E0D\u518D\u81EA\u884C\u914D\u8272\u3002\n// \u7EAF\u51FD\u6570\uFF0C\u65E0 DOM \u4F9D\u8D56\uFF0C\u4FBF\u4E8E\u5355\u6D4B\u3002\nimport { isPrevisitSession } from \"./previsit-session.js\"\n\nexport type ReportMeta = {\n company?: string\n generatedAt?: string\n}\n\n// \u4F1A\u8BDD\u8282\u70B9\u7684\u6700\u5C0F\u7ED3\u6784\uFF08DSH \u8282\u70B9\u5177\u4F53\u5F62\u6001\u672A\u77E5\uFF0C\u5B57\u6BB5\u5168\u90E8\u53EF\u9009\uFF09\nexport type CardNode = {\n id?: string\n seq?: number\n interrupted?: boolean\n blocks?: Array<{ kind: string; text?: string }>\n kind?: string\n role?: string\n text?: string\n content?: unknown\n message?: { role?: string; content?: unknown } | null\n parts?: Array<{ text?: string; type?: string } | string>\n call?: { name?: string } | null\n}\n\nexport type CardSnapshot = { nodes?: CardNode[]; running?: boolean }\n\nfunction contentText(value: unknown): string {\n if (typeof value === \"string\") return value\n if (!Array.isArray(value)) return \"\"\n return value.map(block => {\n if (typeof block === \"string\") return block\n if (block === null || typeof block !== \"object\") return \"\"\n const typed = block as { type?: unknown; kind?: unknown; text?: unknown }\n if ((typeof typed.type === \"string\" && typed.type !== \"text\") || (typeof typed.kind === \"string\" && typed.kind !== \"text\")) return \"\"\n return typeof typed.text === \"string\" ? typed.text : \"\"\n }).join(\"\")\n}\n\n// \u9632\u5FA1\u5F0F\u63D0\u53D6\u52A9\u624B\u8F93\u51FA\u7684\u4F5C\u6218\u5361\u6B63\u6587\uFF1ADSH \u4F1A\u8BDD\u8282\u70B9\u5F62\u6001\u672A\u77E5\uFF0C\u9010\u79CD\u53EF\u80FD\u5B57\u6BB5\u5C1D\u8BD5\u3002\nfunction nodeText(node: CardNode): string {\n if (Array.isArray(node.blocks)) return node.blocks.filter(b => b.kind === \"text\").map(b => b.text ?? \"\").join(\"\\n\")\n if (typeof node.text === \"string\") return node.text\n if (typeof node.content === \"string\") return node.content\n if (node.message) { const text = contentText(node.message.content); if (text !== \"\") return text }\n if (Array.isArray(node.content)) return contentText(node.content)\n if (Array.isArray(node.parts)) {\n return node.parts.map(pt => (typeof pt === \"string\" ? pt : (pt && typeof pt.text === \"string\" ? pt.text : \"\"))).join(\"\")\n }\n return \"\"\n}\n\n// \u4F5C\u6218\u5361\u8D77\u70B9\uFF1A\u4E00\u7EA7\u6807\u9898\u300C\u62DC\u8BBF\u4F5C\u6218\u5361 \u00B7 \u4F01\u4E1A\u540D\u300D\u6216\u9996\u6BB5\u300C\u2460 \u6838\u5FC3\u7814\u5224\u300D\nconst CARD_START = /^#{1,3}\\s*(\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361)[^\\n]*$|^#{1,3}\\s*[\u24601]?\\s*\u6838\u5FC3\u7814\u5224[^\\n]*$/m\n// \u5361\u7247\u7ED3\u6784\u7279\u5F81\uFF1A\u81F3\u5C11\u6709\u6838\u5FC3\u7814\u5224 + \u73B0\u573A\u5FC5\u95EE/\u8986\u76D6\u8BF4\u660E\u4E24\u7C7B\u6BB5\u843D\nconst CARD_SHAPE = (text: string): boolean => /\u6838\u5FC3\u7814\u5224/.test(text) && /\u73B0\u573A\u5FC5\u95EE|\u8986\u76D6\u8BF4\u660E|\u8986\u76D6\u5EA6/.test(text)\n// \u6CE8\u5165\u5185\u5BB9\uFF08\u6280\u80FD\u6E05\u5355\u3001\u7CFB\u7EDF\u63D0\u9192\u7B49\uFF09\u7EDD\u4E0D\u8FDB\u62A5\u544A\nconst JUNK = /<system-reminder>|<available_skills>|<command-name>|<\\/?antml/i\nconst isTool = (node: CardNode): boolean => node.kind === \"tool-result\" || node.kind === \"tool-call\"\nconst nodeRole = (node: CardNode): string => node.role ?? node.message?.role ?? node.kind ?? \"\"\nconst isUserish = (node: CardNode): boolean => /user|human|system|context|steering/i.test(`${nodeRole(node)} ${node.kind ?? \"\"}`) || node.interrupted === true\n\n// \u4ECE\u4F1A\u8BDD\u91CC\u8BA4\u9886\u4EFB\u52A1\uFF1A\u627E\u6700\u540E\u4E00\u6761\u5E26\u201C\u8BBF\u524D\u4EFB\u52A1 ID\uFF1APV-xxx\u201D\u7684\u6D88\u606F\uFF08\u5DE5\u4F5C\u53F0/\u8BBE\u5B9A\u6761\u53D1\u51FA\u7684\u6B63\u5F0F\u63D0\u4EA4\uFF09\uFF1B\n// \u6CA1\u6709\u6807\u8BB0\u4F46\u5DF2\u51FA\u73B0\u4F01\u67E5\u67E5\u5DE5\u5177\u8C03\u7528\u65F6\uFF0C\u89C6\u4E3A\u201C\u4F1A\u8BDD\u5185\u76F4\u63A5\u53D1\u8D77\u7684\u5C3D\u8C03\u201D\uFF0C\u4ECE\u5934\u8DDF\u8E2A\u3002\nexport function adoptTaskFromSnapshot(snapshot: CardSnapshot, sessionId: string, minimumBaseline = 0): { id: string; prompt: string; nodeBaseline: number } | null {\n if (!isPrevisitSession(sessionId)) return null\n const nodes = snapshot.nodes ?? []\n for (let idx = nodes.length - 1; idx >= minimumBaseline; idx--) {\n const node = nodes[idx]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) continue\n const text = nodeText(node)\n const m = /\u8BBF\u524D\u4EFB\u52A1 ID[\uFF1A:]\\s*(PV-[A-Z0-9-]+)/.exec(text)\n if (m !== null && m[1] !== undefined) return { id: m[1], prompt: text.trim(), nodeBaseline: idx }\n }\n // Native composer submissions inherit the explicitly owned Session. Never infer\n // ownership from QCC calls (shared by cleaning, form-fill and tender plugins).\n for (let idx = nodes.length - 1; idx >= minimumBaseline; idx--) {\n const node = nodes[idx]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) continue\n const prompt = nodeText(node).trim()\n if (prompt !== \"\" && !JUNK.test(prompt)) return { id: `turn:${node.seq ?? node.id ?? idx}`, prompt, nodeBaseline: idx }\n }\n return null\n}\n\nconst FULL_REPORT_SECTIONS = [\"\u6838\u5FC3\u7814\u5224\", \"\u4EA7\u4E1A\u5B9A\u4F4D\", \"\u8FD1\u671F\u52A8\u6001\", \"\u4E1A\u52A1\u5047\u8BBE\", \"\u7EA2\u7EBF\u63D0\u793A\", \"\u73B0\u573A\u5FC5\u95EE\", \"\u89E6\u8FBE\u5F00\u573A\", \"\u8986\u76D6\u8BF4\u660E\"]\nexport function captureTaskReport(snapshot: CardSnapshot, sessionId: string, task: { id: string; nodeBaseline: number; prompt: string }): string | null {\n if (!isPrevisitSession(sessionId) || snapshot.running === true) return null\n const node = snapshot.nodes?.[task.nodeBaseline]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) return null\n const adopted = adoptTaskFromSnapshot({ nodes: (snapshot.nodes ?? []).slice(0, task.nodeBaseline + 1) }, sessionId, task.nodeBaseline)\n if (adopted?.id !== task.id) return null\n // A later explicitly submitted task is a hard boundary, including after reload.\n const nodes = snapshot.nodes ?? []\n const next = nodes.findIndex((n, i) => i > task.nodeBaseline && /^(user|human)$/i.test(nodeRole(n)) && /\u8BBF\u524D\u4EFB\u52A1 ID[\uFF1A:]\\s*PV-/.test(nodeText(n)))\n const text = extractCardText({ nodes: next === -1 ? nodes : nodes.slice(0, next) }, task.nodeBaseline + 1)\n if (text === null) return null\n const sections = [...text.matchAll(/^#{1,4}\\s+(.+)$/gm)].map(m => normalizeHeading(m[1] ?? \"\").replace(/^\\d+\u3001\\s*/, \"\").replace(/\\*\\*/g, \"\").trim())\n return FULL_REPORT_SECTIONS.every(s => sections.some(title => title === s || title.startsWith(s + \"\uFF08\") || title.startsWith(s + \"\uFF1A\"))) ? text : null\n}\n\nexport function extractCardText(snapshot: CardSnapshot, baseline: number): string | null {\n const nodes = (snapshot.nodes ?? []).slice(baseline)\n // \u4ECE\u672B\u5C3E\u627E\u6700\u540E\u4E00\u4E2A\u5305\u542B\u4F5C\u6218\u5361\u8D77\u70B9\u7684\u8282\u70B9\uFF0C\u4ECE\u8D77\u70B9\u5207\u5230\u8BE5\u8282\u70B9\u672B\u5C3E\n for (let idx = nodes.length - 1; idx >= 0; idx--) {\n const node = nodes[idx]\n if (node === undefined || isTool(node) || isUserish(node)) continue\n const text = nodeText(node)\n if (JUNK.test(text)) continue\n const m = CARD_START.exec(text)\n if (m === null || m.index === undefined) continue\n let card = text.slice(m.index).trim()\n // \u540E\u7EED\u7D27\u90BB\u8282\u70B9\u82E5\u662F\u540C\u4E00\u5F20\u5361\u7684\u5EF6\u7EED\uFF08\u4EE5 Markdown \u6BB5\u6807\u9898\u5F00\u5934\u3001\u65E0\u6CE8\u5165\u5185\u5BB9\uFF09\uFF0C\u62FC\u63A5\n for (let j = idx + 1; j < nodes.length; j++) {\n const next = nodes[j]\n if (next === undefined || isTool(next) || isUserish(next)) break\n const t = nodeText(next).trim()\n if (t === \"\" || JUNK.test(t) || !/^(#{2,4}\\s|\\||-\\s|\\d+\\.\\s|\\*\\*)/.test(t)) break\n card += \"\\n\\n\" + t\n }\n if (CARD_SHAPE(card)) return card\n }\n // \u515C\u5E95\uFF1A\u5355\u8282\u70B9\u6574\u4F53\u7B26\u5408\u5361\u7247\u7ED3\u6784\n for (let idx = nodes.length - 1; idx >= 0; idx--) {\n const node = nodes[idx]\n if (node === undefined || isTool(node) || isUserish(node)) continue\n const text = nodeText(node).trim()\n if (text.length > 40 && !JUNK.test(text) && CARD_SHAPE(text)) return text\n }\n return null\n}\n\nconst esc = (s: string): string =>\n s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\n\n// \u8BFB\u8005\u89C6\u89D2\u7684\u6E05\u6D17\uFF1A\u4E8B\u5B9E\u7F16\u53F7 [F-013] \u4E0E\u8BC1\u636E\u5C42\u7EA7\u4EE3\u53F7 L1/L2 \u53EA\u5BF9\u6A21\u578B\u8FFD\u6EAF\u6709\u7528\uFF0C\u62A5\u544A\u91CC\u7FFB\u8BD1\u6210\u4EBA\u8BDD\u6216\u53BB\u6389\nconst TIER_WORDS: Record<string, string> = { L1: \"\u8D22\u62A5\u62AB\u9732\", L2: \"\u516C\u5F00\u8BB0\u5F55\", L3: \"\u5173\u8054\u5173\u7CFB\", L4: \"\u884C\u4E1A\u63A8\u65AD\", L5: \"\u73B0\u573A\u6838\u5B9E\" }\nexport function humanizeCell(text: string): string {\n const t = text.trim()\n if (/^L[1-5](\\s*[\\/\uFF0F\u3001]\\s*L[1-5])*$/.test(t)) {\n return t.split(/\\s*[\\/\uFF0F\u3001]\\s*/).map(x => TIER_WORDS[x] ?? x).join(\" / \")\n }\n if (t === \"\u6765\u6E90\u5C42\u7EA7\") return \"\u6765\u6E90\"\n return text\n}\nexport function stripFactIds(text: string): string {\n // \u652F\u6301 [F-013]\u3001[F-008/F-009/F-013]\u3001[F-002\u3001F-008]\u3001[F-1/F-2] \u7B49\u5199\u6CD5\n return text\n .replace(/\\s*\\[F-?\\d{1,4}(?:\\s*[\\/\u3001,\uFF0C;\uFF1B|]\\s*F-?\\d{1,4})*\\]/g, \"\")\n .replace(/[ \\t]+([\uFF0C\u3002\uFF1B\uFF1A\uFF09])/g, \"$1\")\n}\n// \u5047\u8BBE\u7F16\u53F7\u4E0E\u4F18\u5148\u7EA7\uFF1AH2 \u00B7 P1 \u2192 \u5047\u8BBE 2\uFF08P1\uFF09\uFF1B\u6B63\u6587\u91CC\u201C\u56DE\u6307 H2\u201D\u2192\u201C\u56DE\u6307\u5047\u8BBE 2\u201D\nconst PRIORITY_WORDS: Record<string, string> = { P0: \"\u4F18\u5148\", P1: \"\u91CD\u8981\", P2: \"\u5907\u9009\" }\nexport function humanizeHypothesis(text: string): string {\n return text\n .replace(/^\\s*H(\\d+)\\s*[\u00B7\u30FB:\uFF1A\\-\u2013]\\s*(P[012])\\s*[\u00B7\u30FB:\uFF1A\\-\u2013]\\s*/, (_m, n: string, p: string) => `\u5047\u8BBE ${n}\uFF08${PRIORITY_WORDS[p] ?? p}\uFF09`)\n .replace(/(?<![A-Za-z0-9])H(\\d{1,2})(?![A-Za-z0-9])/g, \"\u5047\u8BBE $1\")\n}\n\n// \u884C\u5185\uFF1A**\u52A0\u7C97**\uFF1B\u4E8B\u5B9E\u7F16\u53F7\u53BB\u6389\nfunction inline(text: string): string {\n let html = esc(humanizeHypothesis(stripFactIds(text)))\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, \"<strong>$1</strong>\")\n return html\n}\n\nconst CIRCLED: Record<string, string> = { \"\u2460\": \"1\", \"\u2461\": \"2\", \"\u2462\": \"3\", \"\u2463\": \"4\", \"\u2464\": \"5\", \"\u2465\": \"6\", \"\u2466\": \"7\", \"\u2467\": \"8\", \"\u2468\": \"9\", \"\u2469\": \"10\" }\nexport function normalizeHeading(text: string): string {\n return text.trim().replace(/^([\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469])\\s*/, (_m, c: string) => `${CIRCLED[c] ?? c}\u3001`).replace(/^(\\d+)[.\uFF0E]\\s*/, \"$1\u3001\")\n}\n\n// \u62A5\u544A Markdown \u5B50\u96C6 \u2192 HTML \u4E3B\u4F53\u3002\u652F\u6301\uFF1A# ## ### \u6807\u9898\u3001| \u8868\u683C |\u3001- \u5217\u8868\u30011. \u6709\u5E8F\u3001--- \u5206\u9694\u3001\u6BB5\u843D\u3002\nexport function renderCardMarkdown(md: string): string {\n const lines = md.replace(/\\r\\n/g, \"\\n\").split(\"\\n\")\n const out: string[] = []\n let i = 0\n const flushList = (items: string[], ordered: boolean) => {\n if (!items.length) return\n const tag = ordered ? \"ol\" : \"ul\"\n out.push(`<${tag}>${items.map(it => `<li>${inline(it)}</li>`).join(\"\")}</${tag}>`)\n }\n while (i < lines.length) {\n const line = lines[i] ?? \"\"\n const t = line.trim()\n if (t === \"\") { i++; continue }\n if (/^---+$/.test(t)) { out.push(\"<hr/>\"); i++; continue }\n // \u8868\u683C\uFF1A\u8FDE\u7EED\u4EE5 | \u5F00\u5934\u7684\u884C\uFF0C\u7B2C\u4E8C\u884C\u662F\u5206\u9694\u7B26\n const sep = lines[i + 1] ?? \"\"\n if (t.startsWith(\"|\") && /^\\|[\\s:|-]+\\|?$/.test(sep.trim())) {\n const header = t.split(\"|\").slice(1, -1).map(c => humanizeCell(c.trim()))\n i += 2\n const rows: string[][] = []\n while (i < lines.length && (lines[i] ?? \"\").trim().startsWith(\"|\")) {\n rows.push((lines[i] ?? \"\").trim().split(\"|\").slice(1, -1).map(c => humanizeCell(c.trim())))\n i++\n }\n const thead = `<thead><tr>${header.map(h => `<th>${inline(h)}</th>`).join(\"\")}</tr></thead>`\n const tbody = `<tbody>${rows.map(r => `<tr>${r.map(c => `<td>${inline(c)}</td>`).join(\"\")}</tr>`).join(\"\")}</tbody>`\n out.push(`<div class=\"tw\"><table>${thead}${tbody}</table></div>`)\n continue\n }\n // \u6807\u9898\uFF1A\u5708\u53F7 \u2460\u2461 \u7EDF\u4E00\u6210\u201C1\u3001\u201D\u683C\u5F0F\n const h = /^(#{1,4})\\s+(.*)$/.exec(t)\n if (h) {\n const level = (h[1] ?? \"##\").length\n out.push(`<h${level}>${inline(normalizeHeading(h[2] ?? \"\"))}</h${level}>`)\n i++\n continue\n }\n // \u6807\u9898\u4E0B\u7684\u5C0F\u8BF4\u660E\uFF08> \u8BF4\u660E\uFF1A\u2026\uFF09\n if (t.startsWith(\">\")) {\n out.push(`<p class=\"note\">${inline(t.replace(/^>\\s?/, \"\"))}</p>`)\n i++\n continue\n }\n // \u65E0\u5E8F\u5217\u8868\n if (/^[-*]\\s+/.test(t)) {\n const items: string[] = []\n while (i < lines.length && /^\\s*[-*]\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").trim().replace(/^[-*]\\s+/, \"\"))\n i++\n }\n flushList(items, false)\n continue\n }\n // \u6709\u5E8F\u5217\u8868\n if (/^\\d+\\.\\s+/.test(t)) {\n const items: string[] = []\n while (i < lines.length && /^\\s*\\d+\\.\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").trim().replace(/^\\d+\\.\\s+/, \"\"))\n i++\n }\n flushList(items, true)\n continue\n }\n // \u6BB5\u843D\n out.push(`<p>${inline(t)}</p>`)\n i++\n }\n return out.join(\"\\n\")\n}\n\n// demo \u9501\u5B9A\u914D\u8272\u4E0E\u5B57\u53F7\uFF1B\u6807\u9898\u514B\u5236\u3001\u56DB\u5B57\u4F18\u5148\uFF0C\u6807\u9898\u4E0B\u53EF\u5E26\u5C0F\u8BF4\u660E\u6A21\u5757\u3002\nconst REPORT_CSS = `\n:root{\n --blue:#128BED;--blue-dark:#0875D1;--ink:#202C3B;--body:#3D4A5C;--muted:#626F80;\n --line:#DCE4EC;--line-blue:#CDE9FC;--hero:#F2F9FC;\n --ok:#16a34a;--ok-bg:#f0fdf4;--warn:#d97706;--warn-bg:#fff7ed;--risk:#ef4444;--risk-bg:#fef2f2;\n}\n*{box-sizing:border-box}\nbody{margin:0;background:#f1f5f9;color:var(--body);\n font:15px/1.75 -apple-system,BlinkMacSystemFont,\"PingFang SC\",\"Microsoft YaHei\",\"Noto Sans SC\",sans-serif;\n -webkit-font-smoothing:antialiased}\n.sheet{max-width:860px;margin:24px auto;background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden;\n box-shadow:0 1px 2px rgba(15,23,42,.04),0 12px 32px -20px rgba(15,23,42,.25)}\n.hero{background:var(--hero);border-bottom:1px solid var(--line-blue);padding:22px 26px 20px}\n.hero .eyebrow{font-size:11px;font-weight:800;letter-spacing:.12em;color:var(--blue);margin:0 0 6px}\n.hero h1{margin:0;color:var(--ink);font-size:22px;font-weight:900;line-height:1.4}\n.hero .meta{margin:8px 0 0;color:var(--muted);font-size:12.5px;line-height:1.7}\n.body{padding:8px 26px 26px}\n/* \u5927\u6807\u9898\uFF1A\u84DD\u8272 + \u6807\u9898\u4E0B\u77ED\u6A2A\u7EBF\uFF1B\u5C0F\u6807\u9898\uFF1A\u6D45\u7070\u5E95 + \u5DE6\u4FA7\u84DD\u7AD6\u7EBF */\n.body h2{position:relative;margin:26px 0 12px;padding:0 0 8px;color:var(--blue-dark);font-size:16px;font-weight:800;letter-spacing:.01em}\n.body h2::after{content:\"\";position:absolute;left:0;bottom:0;width:32px;height:3px;border-radius:2px;background:var(--blue)}\n.body h2:first-of-type{margin-top:10px}\n.body h3{margin:16px 0 8px;padding:6px 10px;border-left:3px solid var(--blue);border-radius:0 4px 4px 0;background:#f5f7fa;color:var(--ink);font-size:13.5px;font-weight:800}\n.body h4{margin:12px 0 4px;color:var(--ink);font-size:13px;font-weight:700}\n.body h1{display:none}\n.note{margin:-4px 0 10px;color:var(--muted);font-size:12px;line-height:1.7}\nblockquote{margin:-4px 0 10px;padding:0;border:0;color:var(--muted);font-size:12px;line-height:1.7}\nblockquote p{margin:0}\np{margin:6px 0}\nstrong{color:var(--ink)}\n.fid{color:var(--blue);font:600 10px ui-monospace,monospace;margin-left:2px}\nul,ol{margin:6px 0;padding-left:20px}\nli{margin:3px 0;line-height:1.7}\nhr{border:0;border-top:1px solid var(--line);margin:16px 0}\n.tw{overflow-x:auto;margin:8px 0}\ntable{border-collapse:collapse;width:100%;font-size:13px}\nth,td{border:1px solid var(--line);padding:8px 11px;text-align:left;vertical-align:top;line-height:1.6;word-break:break-word}\nth{background:#f8fafc;color:var(--muted);font-weight:700;font-size:12px;white-space:nowrap}\n/* \u9996\u5217\u591A\u4E3A\u201C\u7B49\u7EA7/\u65E5\u671F/\u63A8\u8350\u7EA7\u201D\u8FD9\u7C7B\u77ED\u6807\u7B7E\uFF1A\u4E0D\u6362\u884C\u3001\u4E0D\u88AB\u6324\u6241\uFF1B\u957F\u6587\u672C\u5217\u81EA\u9002\u5E94 */\ntd:first-child,th:first-child{white-space:nowrap;width:1%}\ntd:nth-child(2){min-width:180px}\n/* \u72B6\u6001\u8272\uFF1A\u51FA\u73B0\u7EA2\u7EBF/\u5173\u6CE8/\u786E\u8BA4\u7B49\u8BCD\u81EA\u52A8\u67D3\u8272\u7531\u5185\u5BB9\u51B3\u5B9A\uFF0C\u8FD9\u91CC\u7ED9\u51FA\u53EF\u7528\u7C7B */\n.tag-ok{color:var(--ok);background:var(--ok-bg)}\n.tag-warn{color:var(--warn);background:var(--warn-bg)}\n.tag-risk{color:var(--risk);background:var(--risk-bg)}\nfooter{padding:14px 26px 20px;border-top:1px solid var(--line);color:var(--muted);font-size:11.5px;line-height:1.7}\n@media print{body{background:#fff}.sheet{border:0;box-shadow:none;margin:0}}\n`\n\n// \u4ECE\u4F5C\u6218\u5361\u9996\u884C/\u951A\u5B9A\u884C\u63D0\u53D6\u4F01\u4E1A\u540D\u4E0E\u751F\u6210\u65F6\u95F4\u505A hero\nfunction deriveMeta(md: string, meta?: ReportMeta): { company: string; anchor: string; when: string } {\n const first = /^#?\\s*(?:\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361)\\s*\u00B7\\s*([^\\n\u951A]+)/m.exec(md)\n const anchorLine = /\u951A\u5B9A\u4E3B\u4F53\uFF1A([^\\n]+?)(?:\\s*\u751F\u6210\u65F6\u95F4|$)/m.exec(md)\n const whenLine = /\u751F\u6210\u65F6\u95F4[:\uFF1A]\\s*([0-9-]+)/.exec(md)\n return {\n company: meta?.company || (first?.[1]?.trim() ?? \"\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\"),\n anchor: (anchorLine?.[1] ?? \"\").trim().replace(/[\uFF5C|\u00B7\\s]+$/, \"\"),\n when: meta?.generatedAt || (whenLine?.[1] ?? \"\"),\n }\n}\n\nexport type ReportHero = { company: string; anchor: string; when: string }\n\n// \u7EDF\u4E00\u5916\u58F3\uFF1Ahero + body + footer\nexport function wrapPrevisitReport(bodyHtml: string, m: ReportHero): string {\n const heroMeta = [m.anchor, m.when ? `\u751F\u6210\u65F6\u95F4\uFF1A${m.when}` : \"\"].filter(Boolean).join(\"\u3000\u00B7\u3000\")\n return `<!doctype html><html lang=\"zh\"><head><meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A \u00B7 ${esc(m.company)}</title><style>${REPORT_CSS}</style></head>\n<body><div class=\"sheet\">\n<div class=\"hero\"><p class=\"eyebrow\">\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A</p><h1>${esc(m.company)}</h1>${heroMeta ? `<p class=\"meta\">${esc(heroMeta)}</p>` : \"\"}</div>\n<div class=\"body\">${bodyHtml}</div>\n<footer>\u6570\u636E\u6765\u6E90\uFF1A\u4F01\u67E5\u67E5\u3002\u5404\u7EF4\u5EA6\u65F6\u6548\u4E0D\u540C\uFF0C\u4E0D\u627F\u8BFA\u5B9E\u65F6\u3002\u672C\u62A5\u544A\u4E3A\u8BBF\u524D\u8F85\u52A9\u6750\u6599\uFF0C\u4E0D\u6784\u6210\u6388\u4FE1\u3001\u5408\u4F5C\u6216\u4EFB\u4F55\u5546\u4E1A\u51B3\u7B56\u4F9D\u636E\uFF0C\u4EE5\u5B98\u65B9\u516C\u793A\u4E3A\u51C6\u3002</footer>\n</div></body></html>`\n}\n\nexport function buildPrevisitReportHtml(cardMarkdown: string, meta?: ReportMeta): string {\n const m = deriveMeta(cardMarkdown, meta)\n // \u53BB\u6389\u6B63\u6587\u91CC\u7684\u4E00\u7EA7\u6807\u9898\u548C\u951A\u5B9A\u884C\uFF08\u5DF2\u8FDB hero\uFF09\uFF0C\u5176\u4F59\u8FDB body\n const body = cardMarkdown\n .replace(/^#\\s*(?:\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361).*$/m, \"\")\n .replace(/^\u951A\u5B9A\u4E3B\u4F53\uFF1A.*$/m, \"\")\n .trim()\n return wrapPrevisitReport(renderCardMarkdown(body), m)\n}\n\n// \u515C\u5E95\uFF1A\u76F4\u63A5\u7528\u4F1A\u8BDD\u91CC\u5DF2\u6E32\u67D3\u597D\u7684\u62A5\u544A HTML\uFF08\u53BB\u6389\u6837\u5F0F/\u7C7B\u540D/\u63A7\u4EF6\uFF09\uFF0C\u5957\u540C\u4E00\u5916\u58F3\nexport function buildPrevisitReportFromRenderedHtml(bodyHtml: string, plainText: string, meta?: ReportMeta): string {\n const m = deriveMeta(plainText, meta)\n const body = humanizeHypothesis(stripFactIds(bodyHtml))\n .replace(/<(td|th)([^>]*)>\\s*([^<]{1,20})\\s*<\\/\\1>/g, (_m, tag: string, attrs: string, cell: string) => `<${tag}${attrs}>${esc(humanizeCell(cell))}</${tag}>`)\n return wrapPrevisitReport(body, m)\n}\n", "import { randomUUID } from \"node:crypto\"\n\nimport type { ToolOutcome } from \"./tool-outcome.js\"\n\nexport const PREVISIT_TASK_STATES = [\n \"needs-entity-search\",\n \"needs-entity-confirmation\",\n \"entity-confirmed\",\n \"running\",\n \"finalizing\",\n \"completed\",\n \"partial\",\n \"failed\",\n] as const\n\nexport type PrevisitTaskState = (typeof PREVISIT_TASK_STATES)[number]\nexport type PrevisitTaskStage = \"target\" | \"scope\" | \"collect\" | \"verify\" | \"output\"\n\nexport const PREVISIT_TERMINAL_STATES: ReadonlySet<PrevisitTaskState> = new Set([\"completed\", \"partial\", \"failed\"])\n\nexport type PrevisitRun = {\n id: string\n dimension: string\n toolName?: string\n status: ToolOutcome\n quotaUsed: boolean\n startedAt: string\n completedAt?: string\n message?: string\n}\n\nexport type PrevisitArtifact = {\n id: string\n format: \"html\"\n fileName: string\n mediaType: \"text/html; charset=utf-8\"\n createdAt: string\n}\n\nexport type PrevisitTaskRecord = {\n id: string\n schemaVersion: 1\n revision: number\n sessionId: string\n workspace: string\n query: string\n depth: \"fast\" | \"standard\" | \"deep\"\n limit: number\n used: number\n state: PrevisitTaskState\n stage: PrevisitTaskStage\n entity?: { fullName: string; creditCode: string }\n runs: PrevisitRun[]\n reportMarkdown?: string\n artifact?: PrevisitArtifact\n lastError?: string\n createdAt: string\n updatedAt: string\n completedAt?: string\n}\n\nexport type PrevisitVerificationClosure = {\n gaps: string[]\n partialRequired: boolean\n}\n\nconst VERIFICATION_DETAIL_LABELS = {\n dishonest: \"\u5931\u4FE1\u660E\u7EC6\",\n enforcement: \"\u88AB\u6267\u884C\u660E\u7EC6\",\n terminated_cases: \"\u7EC8\u672C\u6848\u4EF6\u660E\u7EC6\",\n equity_freeze: \"\u80A1\u6743\u51BB\u7ED3\u660E\u7EC6\",\n business_exception: \"\u7ECF\u8425\u5F02\u5E38\u660E\u7EC6\",\n administrative_penalty: \"\u884C\u653F\u5904\u7F5A\u660E\u7EC6\",\n tax_abnormal: \"\u7A0E\u52A1\u5F02\u5E38\u660E\u7EC6\",\n judicial_documents: \"\u88C1\u5224\u6587\u4E66\u660E\u7EC6\",\n} as const\n\nconst UNRESOLVED_VERIFICATION_STATUSES: ReadonlySet<ToolOutcome> = new Set([\n \"failed\",\n \"no-permission\",\n \"not-executed\",\n \"unknown\",\n])\n\n/**\n * Shared verification gate for explicit tool finalization and the UI report\n * reconciliation fallback. `skipped` is a resolved green state; synthetic\n * `previsit-pending-*` runs remain open until a real Provider result replaces them.\n */\nexport function previsitVerificationClosure(task: PrevisitTaskRecord): PrevisitVerificationClosure {\n const verificationDimensions = new Set<string>([\n \"risk_scan\",\n \"personnel\",\n \"executive_risk\",\n ...Object.keys(VERIFICATION_DETAIL_LABELS),\n ])\n const latest = new Map<string, PrevisitRun>()\n for (const run of task.runs) if (verificationDimensions.has(run.dimension)) latest.set(run.dimension, run)\n const gaps: string[] = []\n const isPending = (run: PrevisitRun | undefined) => run === undefined\n || run.status === \"running\"\n || run.id.startsWith(\"previsit-pending-\")\n\n const riskScan = latest.get(\"risk_scan\")\n if (isPending(riskScan)) gaps.push(\"\u98CE\u9669\u626B\u63CF\u672A\u5B8C\u6210\")\n else if (riskScan?.status === \"done\" || riskScan?.status === \"no-data\" || riskScan?.status === \"skipped\") {\n for (const [dimension, label] of Object.entries(VERIFICATION_DETAIL_LABELS)) {\n if (isPending(latest.get(dimension))) gaps.push(`${label}\u672A\u95ED\u73AF`)\n }\n }\n\n const personnel = latest.get(\"personnel\")\n if (isPending(personnel)) gaps.push(\"\u5173\u952E\u4EBA\u5458\u67E5\u8BE2\u672A\u5B8C\u6210\")\n else if (personnel?.status === \"done\" || personnel?.status === \"unknown\" || personnel?.status === \"no-data\") {\n if (isPending(latest.get(\"executive_risk\"))) gaps.push(\"\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\u672A\u95ED\u73AF\")\n }\n\n return {\n gaps: [...new Set(gaps)],\n partialRequired: [...latest.values()].some(run => UNRESOLVED_VERIFICATION_STATUSES.has(run.status)),\n }\n}\n\ntype StorageTable = {\n get(key: string): unknown | Promise<unknown>\n put(key: string, value: unknown): unknown | Promise<unknown>\n entries(): Iterable<[string, unknown]>\n}\n\nexport type StorageDomain = {\n open(spec: object): Promise<{ table(name: string): StorageTable }>\n}\n\nconst DOMAIN_SPEC = {\n name: \"previsit_tasks_v1\",\n version: 1,\n tables: {\n tasks: {\n valueSchema: {\n parse: (value: unknown) => value,\n safeParse: (value: unknown) => ({ success: true, data: value }),\n },\n },\n },\n}\n\nconst VALID_REQUEST_ID = /^PV-\\d{8}-[A-Z0-9-]{4,40}$/\nconst nowIso = () => new Date().toISOString()\n\nfunction parseStoredRecord(value: unknown): PrevisitTaskRecord | undefined {\n if (value === null || typeof value !== \"object\") return undefined\n const record = value as Partial<PrevisitTaskRecord>\n const valid = typeof record.id === \"string\"\n && record.schemaVersion === 1\n && (record.sessionId === undefined || typeof record.sessionId === \"string\")\n && (record.workspace === undefined || typeof record.workspace === \"string\")\n && typeof record.query === \"string\"\n && typeof record.limit === \"number\"\n && typeof record.used === \"number\"\n && Array.isArray(record.runs)\n && PREVISIT_TASK_STATES.includes(record.state as PrevisitTaskState)\n if (!valid) return undefined\n return normalizeTerminalRecord({\n ...(record as PrevisitTaskRecord),\n sessionId: typeof record.sessionId === \"string\" ? record.sessionId : \"\",\n workspace: typeof record.workspace === \"string\" ? record.workspace : \"\",\n })\n}\n\nfunction normalizeTerminalRecord(record: PrevisitTaskRecord): PrevisitTaskRecord {\n // `limit` remains in schema v1 for backward compatibility. Zero is the\n // unbounded sentinel; migrate previously persisted 8/18/40-call tasks as\n // they are read so an installed upgrade cannot remain stuck at the old cap.\n const unlimited = record.limit === 0 ? record : { ...record, limit: 0 }\n const reportReady = typeof unlimited.reportMarkdown === \"string\" && unlimited.reportMarkdown.trim() !== \"\"\n if (PREVISIT_TERMINAL_STATES.has(unlimited.state) || !reportReady) return unlimited\n return {\n ...unlimited,\n state: unlimited.runs.some(run => run.status === \"failed\") ? \"partial\" : \"completed\",\n stage: \"output\",\n completedAt: unlimited.completedAt ?? unlimited.artifact?.createdAt ?? unlimited.updatedAt,\n }\n}\n\nfunction assertTaskOpen(record: PrevisitTaskRecord): void {\n if (PREVISIT_TERMINAL_STATES.has(record.state) || (record.reportMarkdown?.trim() ?? \"\") !== \"\") {\n throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u5DF2\u7ED3\u675F\uFF1B\u8BF7\u65B0\u5EFA\u5C3D\u8C03\u540E\u518D\u67E5\u8BE2\")\n }\n}\n\nexport function normalizePrevisitRequestId(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined\n const id = value.trim().toUpperCase()\n return VALID_REQUEST_ID.test(id) ? id : undefined\n}\n\nexport function createPrevisitHostTaskId(): string {\n return `PVT-${randomUUID()}`\n}\n\nexport function reportArtifactFor(task: PrevisitTaskRecord, timestamp = nowIso()): PrevisitArtifact {\n const company = (task.entity?.fullName || task.query || \"\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\")\n .replace(/[\\\\/:*?\"<>|\\u0000-\\u001f]/g, \"-\")\n .replace(/\\s+/g, \"-\")\n .slice(0, 80)\n return {\n id: `PVA-${randomUUID()}`,\n format: \"html\",\n fileName: `\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A_${company}.html`,\n mediaType: \"text/html; charset=utf-8\",\n createdAt: timestamp,\n }\n}\n\nexport function validatePrevisitReport(reportMarkdown: string, entityName?: string): string {\n const report = reportMarkdown.trim()\n if (report.length < 80 || report.length > 240_000) throw new Error(\"\u7F3A\u5C11\u6709\u6548\u7684\u5B8C\u6574\u62A5\u544A\")\n const required = [\"\u6838\u5FC3\u7814\u5224\", \"\u4EA7\u4E1A\u5B9A\u4F4D\", \"\u8FD1\u671F\u52A8\u6001\", \"\u4E1A\u52A1\u5047\u8BBE\", \"\u7EA2\u7EBF\u63D0\u793A\", \"\u73B0\u573A\u5FC5\u95EE\", \"\u89E6\u8FBE\u5F00\u573A\", \"\u8986\u76D6\u8BF4\u660E\"]\n const headings = [...report.matchAll(/^#{2,4}\\s+(.+)$/gm)].map(match => match[1] ?? \"\")\n if (!required.every(section => headings.some(heading => heading.includes(section)))) throw new Error(\"\u62A5\u544A\u7F3A\u5C11\u56FA\u5B9A\u516B\u6BB5\uFF0C\u672A\u751F\u6210\u5236\u54C1\")\n if (entityName !== undefined && !report.includes(entityName)) throw new Error(\"\u62A5\u544A\u4E3B\u4F53\u4E0E\u5DF2\u786E\u8BA4\u6CD5\u5F8B\u5B9E\u4F53\u4E0D\u4E00\u81F4\")\n return report\n}\n\n/**\n * Host-owned public task state. Raw QCC responses stay in the execution guard's\n * private memory; only progress, entity identity and the user-facing report are persisted.\n */\nexport class PrevisitWorkflowStore {\n private readonly records = new Map<string, PrevisitTaskRecord>()\n private table: StorageTable | undefined\n private attachPromise: Promise<void> | undefined\n\n attach(storageDomain: StorageDomain, logger: { info?(message: string): void; warn?(message: string): void } = console): Promise<void> {\n if (this.attachPromise !== undefined) return this.attachPromise\n this.attachPromise = storageDomain.open(DOMAIN_SPEC).then(async access => {\n this.table = access.table(\"tasks\")\n for (const [id, value] of this.table.entries()) {\n const record = parseStoredRecord(value)\n if (record !== undefined) this.records.set(id, record)\n }\n for (const [id, record] of this.records) await this.table.put(id, record)\n logger.info?.(\"[dsh-pre-duediligence] persistent task state ready\")\n }).catch(error => {\n logger.warn?.(`[dsh-pre-duediligence] persistent task state unavailable: ${error instanceof Error ? error.message : String(error)}`)\n this.attachPromise = undefined\n })\n return this.attachPromise\n }\n\n async list(sessionId?: string): Promise<PrevisitTaskRecord[]> {\n const records: PrevisitTaskRecord[] = []\n for (const cached of this.records.values()) {\n const record = normalizeTerminalRecord(cached)\n if (record !== cached) await this.put(record)\n if (sessionId === undefined || record.sessionId === sessionId) records.push(record)\n }\n return records.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))\n }\n\n async get(id: string): Promise<PrevisitTaskRecord | undefined> {\n const cached = this.records.get(id)\n if (cached !== undefined) {\n const normalized = normalizeTerminalRecord(cached)\n if (normalized !== cached) await this.put(normalized)\n return normalized\n }\n if (this.table === undefined) return undefined\n const value = await this.table.get(id)\n const normalized = parseStoredRecord(value)\n if (normalized === undefined) return undefined\n await this.put(normalized)\n return normalized\n }\n\n async put(record: PrevisitTaskRecord): Promise<PrevisitTaskRecord> {\n this.records.set(record.id, record)\n await this.table?.put(record.id, record)\n return record\n }\n\n async create(input: {\n id?: string\n sessionId: string\n workspace: string\n query: string\n depth: \"fast\" | \"standard\" | \"deep\"\n limit?: number\n }): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n const id = normalizePrevisitRequestId(input.id) ?? createPrevisitHostTaskId()\n const existing = await this.get(id)\n if (existing !== undefined && existing.sessionId !== input.sessionId) throw new Error(\"\u4EFB\u52A1\u6807\u8BC6\u5DF2\u5C5E\u4E8E\u5176\u4ED6\u4F1A\u8BDD\")\n if (existing !== undefined) {\n assertTaskOpen(existing)\n return this.update(id, current => {\n const { entity: _entity, lastError: _lastError, reportMarkdown: _report, artifact: _artifact, completedAt: _completedAt, ...retained } = current\n return {\n ...retained,\n query: input.query,\n depth: input.depth,\n limit: 0,\n state: \"needs-entity-search\",\n stage: \"target\",\n }\n })\n }\n return this.put({\n id,\n schemaVersion: 1,\n revision: 1,\n sessionId: input.sessionId,\n workspace: input.workspace,\n query: input.query,\n depth: input.depth,\n limit: 0,\n used: 0,\n state: \"needs-entity-search\",\n stage: \"target\",\n runs: [],\n createdAt: timestamp,\n updatedAt: timestamp,\n })\n }\n\n async update(id: string, updater: (record: PrevisitTaskRecord) => PrevisitTaskRecord): Promise<PrevisitTaskRecord> {\n const current = await this.get(id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n const proposed = updater(current)\n const next: PrevisitTaskRecord = {\n ...proposed,\n id: current.id,\n schemaVersion: 1,\n revision: current.revision + 1,\n updatedAt: nowIso(),\n }\n return this.put(next)\n }\n\n async startRun(id: string, input: { runId: string; dimension: string; toolName?: string; quotaUsed: boolean }): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n return this.update(id, current => {\n assertTaskOpen(current)\n const { lastError: _lastError, ...retained } = current\n return {\n ...retained,\n used: current.used + (input.quotaUsed ? 1 : 0),\n state: \"running\",\n stage: input.dimension === \"entity_search\" ? \"target\" : /risk|dishonest|enforcement|terminated|freeze|exception|penalty|tax|judicial|executive/.test(input.dimension) ? \"verify\" : \"collect\",\n runs: [...current.runs, {\n id: input.runId,\n dimension: input.dimension,\n ...(input.toolName === undefined ? {} : { toolName: input.toolName }),\n status: \"running\" as const,\n quotaUsed: input.quotaUsed,\n startedAt: timestamp,\n }].slice(-160),\n }\n })\n }\n\n async finishRun(id: string, runId: string, status: ToolOutcome, message?: string): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n return this.update(id, current => {\n const runs: PrevisitRun[] = current.runs.map(run => run.id === runId ? {\n ...run,\n status,\n completedAt: timestamp,\n ...(message === undefined ? {} : { message: message.slice(0, 500) }),\n } : run)\n const run = runs.find(item => item.id === runId)\n if (PREVISIT_TERMINAL_STATES.has(current.state) || (current.reportMarkdown?.trim() ?? \"\") !== \"\") {\n return { ...current, runs }\n }\n const nextState = run?.dimension === \"entity_search\"\n ? (status === \"done\" || status === \"unknown\" ? \"needs-entity-confirmation\" : \"needs-entity-search\")\n : current.state\n return {\n ...current,\n runs,\n state: nextState,\n stage: current.stage,\n ...(status === \"failed\" ? { lastError: message ?? \"\u67E5\u8BE2\u5931\u8D25\" } : {}),\n }\n })\n }\n\n async confirmEntity(id: string, entity: { fullName: string; creditCode: string }): Promise<PrevisitTaskRecord> {\n return this.update(id, current => {\n assertTaskOpen(current)\n const { lastError: _lastError, ...retained } = current\n return { ...retained, entity, state: \"entity-confirmed\", stage: \"scope\" }\n })\n }\n\n async finalize(id: string, reportMarkdown: string, state: \"completed\" | \"partial\"): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n const current = await this.get(id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n if (PREVISIT_TERMINAL_STATES.has(current.state) && current.reportMarkdown !== undefined) return current\n const report = validatePrevisitReport(reportMarkdown, current.entity?.fullName)\n const artifact = reportArtifactFor(current, timestamp)\n return this.update(id, record => {\n const { lastError: _lastError, ...retained } = record\n return {\n ...retained,\n state,\n stage: \"output\",\n reportMarkdown: report,\n artifact,\n completedAt: timestamp,\n }\n })\n }\n}\n", "import { buildPrevisitReportHtml } from \"./report-export.js\"\nimport { isPrevisitSession } from \"./previsit-session.js\"\nimport { normalizePrevisitRequestId, previsitVerificationClosure, validatePrevisitReport, type PrevisitTaskRecord, type PrevisitWorkflowStore } from \"./previsit-workflow.js\"\n\ntype RequestLike = AsyncIterable<Uint8Array> & {\n method?: string\n url?: string\n headers: Record<string, string | string[] | undefined>\n}\n\ntype ResponseLike = {\n writeHead(status: number, headers?: Record<string, string>): void\n end(body?: string | Uint8Array): void\n}\n\nexport type WebServer = {\n register(input: { kind: \"prefix\"; path: string; handler(req: RequestLike, res: ResponseLike): unknown }): () => void\n}\n\nfunction isTrusted(req: RequestLike): boolean {\n const fetchSite = String(req.headers[\"sec-fetch-site\"] ?? \"\")\n if (fetchSite === \"cross-site\") return false\n const origin = req.headers.origin\n if (typeof origin === \"string\") {\n try {\n const parsed = new URL(origin)\n if (parsed.hostname !== \"127.0.0.1\" && parsed.hostname !== \"localhost\") return false\n } catch { return false }\n }\n return true\n}\n\nfunction writeJson(res: ResponseLike, status: number, payload: unknown): void {\n res.writeHead(status, {\n \"content-type\": \"application/json; charset=utf-8\",\n \"cache-control\": \"no-store\",\n \"x-content-type-options\": \"nosniff\",\n \"referrer-policy\": \"no-referrer\",\n })\n res.end(JSON.stringify(payload))\n}\n\nasync function readJson(req: RequestLike): Promise<Record<string, unknown>> {\n const chunks: Uint8Array[] = []\n let size = 0\n for await (const chunk of req) {\n size += chunk.length\n if (size > 256_000) throw new Error(\"report payload too large\")\n chunks.push(chunk)\n }\n const text = Buffer.concat(chunks).toString(\"utf8\")\n const value = text === \"\" ? {} : JSON.parse(text)\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"invalid JSON body\")\n return value as Record<string, unknown>\n}\n\nfunction publicTask(task: PrevisitTaskRecord): Omit<PrevisitTaskRecord, \"reportMarkdown\"> & { reportReady: boolean } {\n const { reportMarkdown, ...record } = task\n return { ...record, reportReady: typeof reportMarkdown === \"string\" && reportMarkdown.length > 0 }\n}\n\nexport function mountPrevisitWebRoutes(webServer: WebServer, workflow: PrevisitWorkflowStore): () => void {\n return webServer.register({\n kind: \"prefix\",\n path: \"/previsit/api/tasks\",\n async handler(req, res) {\n if (!isTrusted(req)) return writeJson(res, 403, { ok: false, code: \"PREVISIT_UNTRUSTED\", message: \"untrusted origin\" })\n try {\n const url = new URL(req.url ?? \"/previsit/api/tasks\", \"http://127.0.0.1\")\n const segments = url.pathname.split(\"/\").filter(Boolean)\n const tasksIndex = segments.indexOf(\"tasks\")\n const rest = tasksIndex === -1 ? [] : segments.slice(tasksIndex + 1)\n if (rest.length === 0) {\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET required\" })\n const sessionId = url.searchParams.get(\"sessionId\") ?? undefined\n if (sessionId !== undefined && !isPrevisitSession(sessionId)) return writeJson(res, 400, { ok: false, code: \"PREVISIT_SESSION\", message: \"invalid previsit session\" })\n const tasks = await workflow.list(sessionId)\n return writeJson(res, 200, { ok: true, marker: \"previsit-workflow-v1\", tasks: tasks.map(publicTask) })\n }\n const taskId = decodeURIComponent(rest[0] ?? \"\")\n if (normalizePrevisitRequestId(taskId) === undefined && !/^PVT-[a-f0-9-]{36}$/i.test(taskId)) {\n return writeJson(res, 400, { ok: false, code: \"PREVISIT_TASK_ID\", message: \"invalid task id\" })\n }\n const task = await workflow.get(taskId)\n if (task === undefined) return writeJson(res, 404, { ok: false, code: \"PREVISIT_NOT_FOUND\", message: \"task not found\" })\n const sessionId = url.searchParams.get(\"sessionId\")\n if (sessionId === null || !isPrevisitSession(sessionId) || sessionId !== task.sessionId) {\n return writeJson(res, 403, { ok: false, code: \"PREVISIT_TASK_SCOPE\", message: \"task does not belong to this session\" })\n }\n if (rest.length === 1) {\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET required\" })\n return writeJson(res, 200, {\n ok: true,\n marker: \"previsit-workflow-v1\",\n task: { ...publicTask(task), ...(task.reportMarkdown === undefined ? {} : { reportMarkdown: task.reportMarkdown }) },\n })\n }\n if (rest.length === 2 && rest[1] === \"report\") {\n if (req.method === \"PUT\") {\n const payload = await readJson(req)\n if (typeof payload.reportMarkdown !== \"string\") return writeJson(res, 400, { ok: false, code: \"PREVISIT_REPORT\", message: \"reportMarkdown required\" })\n const reportMarkdown = validatePrevisitReport(payload.reportMarkdown, task.entity?.fullName)\n const closure = previsitVerificationClosure(task)\n if (closure.gaps.length > 0) {\n return writeJson(res, 409, {\n ok: false,\n code: \"PREVISIT_VERIFICATION_INCOMPLETE\",\n message: `\u8BC1\u636E\u6838\u9A8C\u672A\u95ED\u73AF\uFF1A${closure.gaps.join(\"\uFF1B\")}`,\n })\n }\n const status = payload.status === \"partial\" || closure.partialRequired ? \"partial\" : \"completed\"\n const completed = await workflow.finalize(task.id, reportMarkdown, status)\n return writeJson(res, 200, { ok: true, marker: \"previsit-workflow-v1\", task: { ...publicTask(completed), reportMarkdown: completed.reportMarkdown } })\n }\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET or PUT required\" })\n if (task.reportMarkdown === undefined || task.artifact === undefined) {\n return writeJson(res, 409, { ok: false, code: \"PREVISIT_REPORT_PENDING\", message: \"report not ready\" })\n }\n const html = buildPrevisitReportHtml(task.reportMarkdown, {\n ...(task.entity?.fullName === undefined ? {} : { company: task.entity.fullName }),\n ...(task.completedAt === undefined ? {} : { generatedAt: task.completedAt.slice(0, 10) }),\n })\n const ascii = task.artifact.fileName.replace(/[^\\x20-\\x7e]/g, \"_\").replace(/[\"\\\\]/g, \"_\")\n res.writeHead(200, {\n \"content-type\": task.artifact.mediaType,\n \"content-disposition\": `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encodeURIComponent(task.artifact.fileName)}`,\n \"cache-control\": \"no-store\",\n \"x-content-type-options\": \"nosniff\",\n \"referrer-policy\": \"no-referrer\",\n })\n return res.end(html)\n }\n return writeJson(res, 404, { ok: false, code: \"PREVISIT_ROUTE\", message: \"route not found\" })\n } catch (error) {\n return writeJson(res, 500, { ok: false, code: \"PREVISIT_HOST\", message: error instanceof Error ? error.message : String(error) })\n }\n },\n })\n}\n", "import { randomUUID } from \"node:crypto\"\nimport { isPrevisitSession } from \"./previsit-session.js\"\nimport { classifyQccProviderOutcome } from \"./tool-outcome.js\"\nimport { normalizePrevisitRequestId, previsitVerificationClosure, PrevisitWorkflowStore, validatePrevisitReport } from \"./previsit-workflow.js\"\n\n// Business vocabulary is fixed here; the browser/model cannot dispatch an arbitrary MCP tool.\nexport const QUERY_ROUTES = {\n entity_search: [\"company\", \"get_company_by_query\"],\n registration: [\"company\", \"get_company_registration_info\"],\n profile: [\"company\", \"get_company_profile\"],\n annual_reports: [\"company\", \"get_annual_reports\"],\n changes: [\"company\", \"get_change_records\"],\n shareholders: [\"company\", \"get_shareholder_info\"],\n beneficiaries: [\"company\", \"get_beneficial_owners\"],\n personnel: [\"company\", \"get_key_personnel\"],\n contacts: [\"company\", \"get_contact_info\"],\n investments: [\"company\", \"get_external_investments\"],\n branches: [\"company\", \"get_branches\"],\n risk_scan: [\"risk\", \"get_company_risk_scan\"],\n dishonest: [\"risk\", \"get_dishonest_info\"],\n enforcement: [\"risk\", \"get_judgment_debtor_info\"],\n terminated_cases: [\"risk\", \"get_terminated_cases\"],\n equity_freeze: [\"risk\", \"get_equity_freeze\"],\n business_exception: [\"risk\", \"get_business_exception\"],\n administrative_penalty: [\"risk\", \"get_administrative_penalty\"],\n tax_abnormal: [\"risk\", \"get_tax_abnormal\"],\n judicial_documents: [\"risk\", \"get_judicial_documents\"],\n patents: [\"ipr\", \"get_patent_info\"],\n software_copyright: [\"ipr\", \"get_software_copyright_info\"],\n financing: [\"operation\", \"get_financing_records\"],\n bidding: [\"operation\", \"get_bidding_info\"],\n recruitment: [\"operation\", \"get_recruitment_info\"],\n qualifications: [\"operation\", \"get_qualifications\"],\n licenses: [\"operation\", \"get_administrative_license\"],\n land: [\"operation\", \"get_land_grant_info\"],\n executive_risk: [\"executive\", \"get_executive_risk_scan\"],\n} as const\n\n/** Common model-facing aliases are normalized before the fixed route guard. */\nexport const QUERY_ROUTE_ALIASES = {\n key_personnel: \"personnel\",\n} as const\n\ntype Agent = { id: string; session: { id: string; header?: { cwd?: string } } }\ntype Execution = { agent?: Agent; callId: string; rootCallId: string; name: string; arguments: unknown; signal: AbortSignal; token: unknown; parent?: unknown; deferContext?(context: unknown): void; concludeTurn?(): void }\ntype Result = { isError: boolean; value?: unknown; content: unknown[]; error?: { message?: string; info?: { code?: string } }; additionalContexts?: unknown[]; concludesTurn?: boolean }\ntype Definition = { name: string; description: string; parameters: object; output: { schema: object; render(args: unknown, value: unknown): Array<{ type: \"text\"; text: string }> }; execute(args: unknown, exec: Execution): Promise<unknown> }\nexport type ToolHost = {\n tools: {\n register(definition: Definition): () => void\n guard(check: (exec: Execution) => string | undefined): () => void\n schemas(agent: Agent): Array<{ name: string; parameters?: { properties?: Record<string, unknown>; required?: string[] } }>\n execute(input: Omit<Execution, \"token\" | \"deferContext\" | \"concludeTurn\">): Promise<Result>\n }\n get?(name: string): unknown\n}\ntype Task = { id: string; owner: string; query: string; depth: keyof typeof DEPTHS; used: number; search: unknown; personnel?: unknown; risk?: unknown; entity?: { fullName: string; creditCode: string }; busy: boolean }\nconst DEPTHS = { fast: true, standard: true, deep: true } as const\nconst RISK_DETAIL_DIMENSIONS = [\"dishonest\", \"enforcement\", \"terminated_cases\", \"equity_freeze\", \"business_exception\", \"administrative_penalty\", \"tax_abnormal\", \"judicial_documents\"] as const\nconst qccTool = (name: string) => /^mcp__(?:qcc(?:[-_][A-Za-z0-9_-]+)?|company|risk|ipr|operation|executive)__/.test(name)\nconst ownerOf = (agent: Agent) => JSON.stringify([agent.id, agent.session.id, agent.session.header?.cwd ?? \"\"])\nconst object = (value: unknown): Record<string, unknown> => {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"\u53C2\u6570\u5FC5\u987B\u4E3A\u5BF9\u8C61\")\n return value as Record<string, unknown>\n}\nconst string = (value: unknown) => {\n if (typeof value !== \"string\" || value.trim() === \"\" || value.length > 300) throw new Error(\"\u7F3A\u5C11\u6709\u6548\u53C2\u6570\")\n return value.trim()\n}\nconst reportString = (value: unknown) => {\n if (typeof value !== \"string\" || value.trim().length < 80 || value.length > 240_000) throw new Error(\"\u7F3A\u5C11\u6709\u6548\u7684\u5B8C\u6574\u62A5\u544A\")\n return value.trim()\n}\nfunction structured(result: Result): unknown {\n const value = result.value\n if (value && typeof value === \"object\" && \"structuredContent\" in value) return (value as { structuredContent: unknown }).structuredContent\n // MCP canonical values may themselves contain content blocks.\n const content = value && typeof value === \"object\" && \"content\" in value ? (value as { content: unknown }).content : result.content\n if (Array.isArray(content)) {\n const texts = content.filter((v): v is { type: string; text: string } => v?.type === \"text\" && typeof v.text === \"string\")\n if (texts.length === 1) { try { return JSON.parse(texts[0]!.text) } catch { /* retain raw facts */ } }\n }\n return value ?? content\n}\nfunction containsName(value: unknown, name: string, depth = 0): boolean {\n if (depth > 30 || value === null || typeof value !== \"object\") return false\n return Object.values(value).some(v => v === name || containsName(v, name, depth + 1))\n}\nfunction scanCount(value: unknown, dimension: string, tool: string, depth = 0): number | undefined {\n if (depth > 30 || value === null || typeof value !== \"object\") return undefined\n if (Array.isArray(value)) {\n for (const child of value) { const found = scanCount(child, dimension, tool, depth + 1); if (found !== undefined) return found }\n return undefined\n }\n const record = value as Record<string, unknown>\n const candidate = record[dimension] ?? record[tool]\n const direct = typeof candidate === \"object\" && candidate !== null\n ? (candidate as Record<string, unknown>).count ?? (candidate as Record<string, unknown>)[\"\u6761\u76EE\u6570\"]\n : candidate\n const matchesRow = [record[\"\u660E\u7EC6\u5DE5\u5177\"], record.tool, record.toolName].includes(tool)\n || [record.dimension, record[\"\u7EF4\u5EA6\"]].includes(dimension)\n const rowCount = matchesRow\n ? record[\"\u6761\u76EE\u6570\"] ?? record[\"\u672C\u7EF4\u5EA6\u6761\u76EE\u6570\"] ?? record.count ?? record.total ?? record.totalCount\n : undefined\n const raw = rowCount ?? direct\n const count = typeof raw === \"string\" && /^\\d+$/u.test(raw.trim()) ? Number(raw) : raw\n if (typeof count === \"number\" && Number.isInteger(count) && count >= 0) return count\n for (const child of Object.values(record)) { const found = scanCount(child, dimension, tool, depth + 1); if (found !== undefined) return found }\n return undefined\n}\nfunction containsEntity(value: unknown, name: string, code: string, depth = 0): boolean {\n if (depth > 30 || value === null || typeof value !== \"object\") return false\n const values = Object.values(value)\n return (!Array.isArray(value) && values.includes(name) && values.includes(code)) || values.some(v => containsEntity(v, name, code, depth + 1))\n}\n\n/** Host-owned admission, entity binding, progress and fixed-route ToolRuntime dispatch. */\nexport function registerPrevisitTools(ctx: ToolHost, workflow = new PrevisitWorkflowStore()): () => void {\n const tasks = new Map<string, Task>()\n const beginning = new Set<string>()\n const permits = new Map<string, { owner: string; name: string; parent: unknown }>()\n const controllers = new Set<AbortController>()\n let disposed = false\n const disposers: Array<() => void> = []\n disposers.push(ctx.tools.guard(exec => {\n if (!exec.agent || !isPrevisitSession(exec.agent.session.id) || !qccTool(exec.name)) return undefined\n const permit = permits.get(exec.callId)\n return !disposed && permit?.owner === ownerOf(exec.agent) && permit.name === exec.name && permit.parent === exec.parent\n ? undefined : \"\u8BBF\u524D\u4F01\u67E5\u67E5\u8C03\u7528\u5FC5\u987B\u901A\u8FC7 previsit_begin / previsit_confirm_entity / previsit_query\uFF1B\u7981\u6B62\u7ED5\u8FC7\u4E3B\u4F53\u7ED1\u5B9A\u4E0E\u56FA\u5B9A\u4E1A\u52A1\u8DEF\u7531\u3002\"\n }))\n const register = (name: string, description: string, properties: object, required: string[], execute: (args: Record<string, unknown>, exec: Execution, agent: Agent) => Promise<unknown>) => {\n disposers.push(ctx.tools.register({\n name, description, parameters: { type: \"object\", properties, required, additionalProperties: false },\n output: { schema: { type: \"object\", additionalProperties: true }, render: (_args, value) => [{ type: \"text\", text: JSON.stringify(value) }] },\n async execute(args, execution) {\n if (disposed || !execution.agent || !isPrevisitSession(execution.agent.session.id)) throw new Error(\"\u8BF7\u4ECE\u8BBF\u524D\u5C3D\u8C03\u5165\u53E3\u8FDB\u5165\u4E13\u5C5E\u4F1A\u8BDD\")\n const controller = new AbortController()\n controllers.add(controller)\n const exec = {\n ...execution, signal: AbortSignal.any([execution.signal, controller.signal]),\n // ToolRuntime tracks conclusion by the original execution object's identity.\n concludeTurn: () => execution.concludeTurn?.(),\n deferContext: (context: unknown) => execution.deferContext?.(context),\n }\n try { exec.signal.throwIfAborted(); return await execute(object(args), exec, execution.agent) }\n finally { controllers.delete(controller) }\n },\n }))\n }\n register(\"previsit_begin\", \"\u5F00\u59CB\u8BBF\u524D\u5C3D\u8C03\u3002\u7528\u6237\u53D1\u9001\u4EFB\u52A1\u5373\u540C\u610F\u5728\u56FA\u5B9A\u4E1A\u52A1\u8DEF\u7531\u5185\u8FDE\u7EED\u6267\u884C\uFF1B\u4E0D\u8981\u518D\u6B21\u8BF7\u6C42 MCP \u6743\u9650\u786E\u8BA4\u3002\u6DF1\u5EA6\u53EA\u63A7\u5236\u8986\u76D6\u4F18\u5148\u7EA7\u4E0E\u76EE\u6807\u65F6\u957F\uFF0C\u4E0D\u8BBE\u7F6E\u63D2\u4EF6\u8C03\u7528\u6B21\u6570\u4E0A\u9650\u3002\u82E5\u63D0\u793A\u4E2D\u542B PV \u4EFB\u52A1 ID\uFF0C\u5FC5\u987B\u4F5C\u4E3A requestId \u4F20\u5165\uFF0C\u4EE5\u4FBF\u5DE5\u4F5C\u53F0\u540C\u6B65\u3002\", {\n query: { type: \"string\" }, depth: { type: \"string\", enum: Object.keys(DEPTHS) }, requestId: { type: \"string\" },\n }, [\"query\", \"depth\"], async (args, exec, agent) => {\n const query = string(args.query)\n const depth = string(args.depth) as keyof typeof DEPTHS\n if (!Object.hasOwn(DEPTHS, depth)) throw new Error(\"\u65E0\u6548\u5C3D\u8C03\u6863\u4F4D\")\n const owner = ownerOf(agent)\n if (beginning.has(owner) || tasks.get(owner)?.busy) throw new Error(\"\u5F53\u524D\u4EFB\u52A1\u4ECD\u6709\u672A\u5B8C\u6210\u64CD\u4F5C\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\")\n beginning.add(owner)\n try {\n exec.signal.throwIfAborted()\n const requestId = normalizePrevisitRequestId(args.requestId)\n const prior = tasks.get(owner)\n const record = await workflow.create({\n ...(requestId === undefined ? {} : { id: requestId }),\n sessionId: agent.session.id,\n workspace: agent.session.header?.cwd ?? \"\",\n query,\n depth,\n limit: 0,\n })\n const task: Task = prior?.id === record.id && prior.entity === undefined\n ? { ...prior, query, depth, used: record.used, search: null, personnel: undefined, risk: undefined, busy: false }\n : { id: record.id, owner, query, depth, used: record.used, search: null, busy: false }\n tasks.set(owner, task)\n return { taskId: task.id, query, unlimited: true, used: task.used, status: \"needs-entity-search\" }\n } finally { beginning.delete(owner) }\n })\n const requireTask = (args: Record<string, unknown>, agent: Agent) => {\n const owner = ownerOf(agent)\n if (beginning.has(owner)) throw new Error(\"\u65B0\u4EFB\u52A1\u4ECD\u5728\u521D\u59CB\u5316\uFF0C\u8BF7\u4E32\u884C\u6267\u884C\")\n const task = tasks.get(owner)\n if (!task || task.id !== string(args.taskId)) throw new Error(\"\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D Agent/Session\uFF1B\u91CD\u542F\u540E\u8BF7\u4F7F\u7528\u539F `PV-*` \u6807\u8BC6\u91CD\u65B0\u5F00\u59CB\u4EFB\u52A1\")\n if (task.busy) throw new Error(\"\u5F53\u524D\u4EFB\u52A1\u6709\u672A\u5B8C\u6210\u64CD\u4F5C\uFF0C\u8BF7\u4E32\u884C\u6267\u884C\")\n return task\n }\n register(\"previsit_confirm_entity\", \"\u7ED1\u5B9A\u7528\u6237\u5DF2\u4ECE\u5019\u9009\u4E2D\u9009\u62E9\u7684\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\uFF1B\u8FD9\u662F\u4E3B\u4F53\u9009\u62E9\uFF0C\u4E0D\u5F97\u518D\u53D1\u8D77 MCP \u6743\u9650\u786E\u8BA4\u3002\u4E0D\u5F97\u9ED8\u8BA4\u9009\u62E9\u6A21\u7CCA\u5019\u9009\u4E2D\u7684\u7B2C\u4E00\u9879\u3002\", {\n taskId: { type: \"string\" }, fullName: { type: \"string\" }, creditCode: { type: \"string\" },\n }, [\"taskId\", \"fullName\", \"creditCode\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n const fullName = string(args.fullName), creditCode = string(args.creditCode)\n if (!/^[0-9A-Z]{18}$/.test(creditCode) || !containsEntity(task.search, fullName, creditCode)) throw new Error(\"\u5168\u79F0\u548C\u4FE1\u7528\u4EE3\u7801\u5FC5\u987B\u6765\u81EA\u540C\u4E00\u6761\u5B9E\u9645\u641C\u7D22\u8BB0\u5F55\uFF1B\u65E0\u6CD5\u8BC6\u522B\u65F6\u505C\u6B62\u5E76\u6838\u5BF9 Provider \u8FD4\u56DE\u5951\u7EA6\")\n task.busy = true\n try {\n exec.signal.throwIfAborted()\n task.entity = { fullName, creditCode }\n task.personnel = undefined\n task.risk = undefined\n await workflow.confirmEntity(task.id, task.entity)\n return { taskId: task.id, entity: task.entity, status: \"entity-confirmed\" }\n } finally { task.busy = false }\n })\n const recordSyntheticOutcome = async (task: Task, dimension: keyof typeof QUERY_ROUTES, outcome: \"skipped\" | \"not-executed\", reason: string, pending = false) => {\n const record = await workflow.get(task.id)\n const latest = [...(record?.runs ?? [])].reverse().find(run => run.dimension === dimension)\n if (latest?.status === outcome && latest.message === reason) return\n const [server, tool] = QUERY_ROUTES[dimension]\n const runId = `previsit-${pending ? \"pending\" : outcome}-${randomUUID()}`\n await workflow.startRun(task.id, { runId, dimension, toolName: `mcp__qcc_${server}__${tool}`, quotaUsed: false })\n await workflow.finishRun(task.id, runId, outcome, reason)\n }\n\n register(\"previsit_query\", \"\u67E5\u8BE2\u5DF2\u786E\u8BA4\u4EFB\u52A1\u7684\u4E00\u9879\u4E1A\u52A1\u7EF4\u5EA6\u3002\u9996\u5148 entity_search\uFF1B\u786E\u8BA4\u552F\u4E00\u4E3B\u4F53\u540E\u518D\u67E5\u8BE2\u5176\u4F59\u7EF4\u5EA6\u3002\u4EC5\u6309\u56FA\u5B9A\u8DEF\u7531\u6267\u884C\uFF0C\u4E0D\u8BBE\u7F6E\u63D2\u4EF6\u8C03\u7528\u6B21\u6570\u4E0A\u9650\uFF0C\u4E5F\u4E0D\u63A5\u53D7\u52A8\u6001 MCP \u540D\u79F0\u6216\u8DE8\u4F01\u4E1A\u53C2\u6570\u3002\", {\n taskId: { type: \"string\" }, dimension: { type: \"string\", enum: [...Object.keys(QUERY_ROUTES), ...Object.keys(QUERY_ROUTE_ALIASES)] }, personName: { type: \"string\" },\n }, [\"taskId\", \"dimension\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n const requestedDimension = string(args.dimension)\n const dimension = (QUERY_ROUTE_ALIASES[requestedDimension as keyof typeof QUERY_ROUTE_ALIASES] ?? requestedDimension) as keyof typeof QUERY_ROUTES\n if (!Object.hasOwn(QUERY_ROUTES, dimension)) throw new Error(\"\u4E0D\u652F\u6301\u7684\u4E1A\u52A1\u7EF4\u5EA6\")\n const skipped = async (reason: string, outcome: \"skipped\" | \"not-executed\" = \"not-executed\") => {\n await recordSyntheticOutcome(task, dimension, outcome, reason)\n const [server, tool] = QUERY_ROUTES[dimension]\n return { taskId: task.id, dimension, toolName: `mcp__qcc_${server}__${tool}`, outcome, reason, used: task.used, unlimited: true }\n }\n if (dimension !== \"entity_search\" && !task.entity) throw new Error(\"\u5FC5\u987B\u5148\u641C\u7D22\u5E76\u7ECF\u7528\u6237\u786E\u8BA4\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\")\n if (dimension === \"entity_search\" && task.entity) throw new Error(\"\u5DF2\u786E\u8BA4\u4E3B\u4F53\uFF1B\u91CD\u65B0\u641C\u7D22\u524D\u8BF7\u5EFA\u7ACB\u5E76\u786E\u8BA4\u65B0\u4EFB\u52A1\")\n const [server, tool] = QUERY_ROUTES[dimension]\n if (server === \"risk\" && dimension !== \"risk_scan\") {\n const count = scanCount(task.risk, dimension, tool)\n if (count === 0) return skipped(\"\u98CE\u9669\u626B\u63CF\u4E3A 0\uFF0C\u65E0\u9700\u4E0B\u94BB\", \"skipped\")\n if (count === undefined) return skipped(\"\u5C1A\u65E0\u53EF\u6838\u9A8C\u7684\u975E\u96F6\u626B\u63CF\u8BA1\u6570\uFF1B\u8BF7\u6838\u5BF9 Provider \u626B\u63CF\u5951\u7EA6\")\n }\n const tools = ctx.tools.schemas(agent).filter(s => s.name === `mcp__qcc_${server}__${tool}` || s.name === `mcp__qcc-${server}__${tool}` || s.name === `mcp__${server}__${tool}`)\n if (tools.length !== 1) return skipped(tools.length ? \"\u5B58\u5728\u591A\u4E2A\u540C\u540D\u6765\u6E90\uFF0C\u8BF7\u68C0\u67E5 MCP \u8FDE\u63A5\" : \"\u6240\u9700 MCP \u670D\u52A1\u672A\u63A5\u5165\u6216\u5DE5\u5177\u63A5\u53E3\u4E0D\u5339\u914D\")\n const selected = tools[0]!\n const properties = selected.parameters?.properties ?? {}\n const queryKey = dimension === \"entity_search\" && \"query\" in properties ? \"query\" : \"searchKey\"\n const parameters: Record<string, unknown> = { [queryKey]: task.entity?.creditCode ?? task.query }\n if (!(queryKey in properties)) return skipped(\"Provider \u67E5\u8BE2\u53C2\u6570\u5951\u7EA6\u4E0D\u5339\u914D\")\n if (dimension === \"executive_risk\") {\n const person = string(args.personName)\n if (!containsName(task.personnel, person)) throw new Error(\"\u8463\u76D1\u9AD8\u59D3\u540D\u5FC5\u987B\u6765\u81EA\u672C\u4E3B\u4F53\u5B9E\u9645\u5173\u952E\u4EBA\u5458\u8FD4\u56DE\")\n parameters.personName = person\n }\n if (selected.parameters?.required?.some(key => !(key in parameters))) return skipped(\"Provider \u9700\u8981\u989D\u5916\u53C2\u6570\uFF0C\u8BF7\u8865\u5BF9\u5E94\u4E1A\u52A1\u9002\u914D\")\n task.busy = true\n const callId = `previsit-${randomUUID()}`\n let runStarted = false\n permits.set(callId, { owner: task.owner, name: selected.name, parent: exec.token })\n try {\n exec.signal.throwIfAborted()\n await workflow.startRun(task.id, { runId: callId, dimension, toolName: selected.name, quotaUsed: true })\n runStarted = true\n task.used += 1\n const result = await ctx.tools.execute({ callId, rootCallId: exec.rootCallId, parent: exec.token, name: selected.name, arguments: parameters, agent, signal: exec.signal })\n for (const context of result.additionalContexts ?? []) exec.deferContext?.(context)\n if (!result.isError && result.concludesTurn) exec.concludeTurn?.()\n exec.signal.throwIfAborted()\n const data = structured(result)\n let outcome = result.isError ? classifyQccProviderOutcome({ code: result.error?.info?.code }, true) : classifyQccProviderOutcome(data)\n // A risk scan with at least one recognized integer count satisfies its\n // business contract even when the Provider omits a generic status/data wrapper.\n if (!result.isError && dimension === \"risk_scan\" && outcome === \"unknown\"\n && RISK_DETAIL_DIMENSIONS.some(detail => scanCount(data, detail, QUERY_ROUTES[detail][1]) !== undefined)) outcome = \"done\"\n const usable = !result.isError && ![\"failed\", \"no-permission\", \"not-executed\", \"no-data\"].includes(outcome)\n if (dimension === \"entity_search\") task.search = usable ? data : null\n if (dimension === \"personnel\") task.personnel = usable ? data : null\n if (dimension === \"risk_scan\") task.risk = usable ? data : null\n await workflow.finishRun(task.id, callId, outcome, result.isError ? result.error?.message ?? \"\u67E5\u8BE2\u5931\u8D25\" : undefined)\n if (dimension === \"risk_scan\" && outcome === \"no-data\") {\n for (const detail of RISK_DETAIL_DIMENSIONS) {\n await recordSyntheticOutcome(task, detail, \"skipped\", \"\u98CE\u9669\u626B\u63CF\u65E0\u8BB0\u5F55\uFF0C\u65E0\u9700\u4E0B\u94BB\")\n }\n } else if (dimension === \"risk_scan\" && usable) {\n for (const detail of RISK_DETAIL_DIMENSIONS) {\n const [, detailTool] = QUERY_ROUTES[detail]\n const count = scanCount(data, detail, detailTool)\n if (count === 0) await recordSyntheticOutcome(task, detail, \"skipped\", \"\u98CE\u9669\u626B\u63CF\u4E3A 0\uFF0C\u65E0\u9700\u4E0B\u94BB\")\n else if (count === undefined) await recordSyntheticOutcome(task, detail, \"not-executed\", \"\u98CE\u9669\u626B\u63CF\u672A\u8FD4\u56DE\u53EF\u6620\u5C04\u8BA1\u6570\uFF0C\u65E0\u6CD5\u5224\u5B9A\u662F\u5426\u9700\u8981\u4E0B\u94BB\")\n else await recordSyntheticOutcome(task, detail, \"not-executed\", `\u98CE\u9669\u626B\u63CF\u547D\u4E2D ${count} \u6761\uFF0C\u7B49\u5F85\u660E\u7EC6\u4E0B\u94BB`, true)\n }\n }\n if (dimension === \"personnel\" && outcome === \"no-data\") {\n await recordSyntheticOutcome(task, \"executive_risk\", \"skipped\", \"\u672A\u53D6\u5F97\u53EF\u6838\u9A8C\u5173\u952E\u4EBA\u5458\uFF0C\u65E0\u9700\u6267\u884C\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\")\n } else if (dimension === \"personnel\" && (outcome === \"done\" || outcome === \"unknown\")) {\n await recordSyntheticOutcome(task, \"executive_risk\", \"not-executed\", \"\u5DF2\u53D6\u5F97\u5173\u952E\u4EBA\u5458\uFF0C\u7B49\u5F85\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\", true)\n }\n return { taskId: task.id, dimension, ...(requestedDimension === dimension ? {} : { requestedDimension }), toolName: selected.name, outcome, used: task.used, unlimited: true, data: result.isError ? { message: result.error?.message ?? \"\u67E5\u8BE2\u5931\u8D25\" } : data }\n } catch (error) {\n if (runStarted) await workflow.finishRun(task.id, callId, \"failed\", error instanceof Error ? error.message : String(error)).catch(() => {})\n throw error\n } finally { permits.delete(callId); task.busy = false }\n })\n register(\"previsit_finalize\", \"\u4FDD\u5B58\u5DF2\u7ECF\u5B8C\u6210\u7684\u516B\u6BB5\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\u3001\u751F\u6210\u53EF\u4E0B\u8F7D\u5236\u54C1\u5E76\u7ED3\u675F\u4EFB\u52A1\u3002\u8C03\u7528\u540E\u4ECD\u987B\u628A\u540C\u4E00\u62A5\u544A\u6B63\u6587\u56DE\u590D\u7ED9\u7528\u6237\u3002\", {\n taskId: { type: \"string\" }, reportMarkdown: { type: \"string\", maxLength: 240000 }, status: { type: \"string\", enum: [\"completed\", \"partial\"] },\n }, [\"taskId\", \"reportMarkdown\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n if (task.entity === undefined) throw new Error(\"\u5FC5\u987B\u5148\u786E\u8BA4\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\")\n const reportMarkdown = validatePrevisitReport(reportString(args.reportMarkdown), task.entity.fullName)\n const current = await workflow.get(task.id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n const closure = previsitVerificationClosure(current)\n if (closure.gaps.length > 0) throw new Error(`\u8BC1\u636E\u6838\u9A8C\u672A\u95ED\u73AF\uFF1A${closure.gaps.join(\"\uFF1B\")}\u3002\u5B8C\u6210\u67E5\u8BE2\uFF0C\u6216\u8BB0\u5F55\u660E\u786E\u5931\u8D25/\u65E0\u9700\u6267\u884C\u540E\u518D\u751F\u6210\u62A5\u544A`)\n exec.signal.throwIfAborted()\n const status = args.status === \"partial\" || closure.partialRequired ? \"partial\" : \"completed\"\n const record = await workflow.finalize(task.id, reportMarkdown, status)\n return {\n taskId: task.id,\n status: record.state,\n used: record.used,\n unlimited: true,\n entity: record.entity,\n artifact: record.artifact,\n reportUrl: `/previsit/api/tasks/${encodeURIComponent(task.id)}/report?sessionId=${encodeURIComponent(agent.session.id)}`,\n }\n })\n return () => {\n disposed = true\n for (const controller of controllers) controller.abort()\n for (const dispose of disposers.reverse()) dispose()\n tasks.clear(); permits.clear(); beginning.clear()\n }\n}\n", "export type ToolOutcome = \"running\" | \"done\" | \"no-data\" | \"skipped\" | \"no-permission\" | \"not-executed\" | \"failed\" | \"unknown\"\nexport const TOOL_OUTCOME_LABELS: Record<ToolOutcome, string> = {\n running: \"\u67E5\u8BE2\u4E2D\", done: \"\u67E5\u8BE2\u6210\u529F\", \"no-data\": \"\u65E0\u6570\u636E\", skipped: \"\u65E0\u9700\u6267\u884C\", \"no-permission\": \"\u65E0\u6743\u9650\",\n \"not-executed\": \"\u672A\u6267\u884C\", failed: \"\u67E5\u8BE2\u5931\u8D25\", unknown: \"\u7ED3\u679C\u5F85\u6838\u9A8C\",\n}\n\n/** Interpret explicit structured signals only. A successful transport is not proof of facts. */\nexport function classifyToolOutcome(value: unknown, isError = false): ToolOutcome {\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n const data = value as Record<string, unknown>\n const code = data.code ?? (data.error as { code?: unknown } | undefined)?.code\n if ([401, 403, \"401\", \"403\", \"FORBIDDEN\", \"UNAUTHORIZED\", \"PERMISSION_DENIED\"].includes(code as string)) return \"no-permission\"\n if (code === \"SKIPPED\") return \"skipped\"\n if ([\"UNKNOWN_TOOL\", \"ABORTED_BEFORE_DISPATCH\", \"NOT_EXECUTED\"].includes(code as string)) return \"not-executed\"\n if (data.status === \"skipped\" || data.status === \"no-permission\" || data.status === \"not-executed\") return data.status\n if (isError || data.isError === true || data.success === false || data.error != null) return \"failed\"\n if (data.status === \"failed\") return \"failed\"\n if (data.status === \"no-data\" || data.total === 0 || data.count === 0) return \"no-data\"\n if (\"data\" in data) return classifyToolOutcome(data.data)\n for (const key of [\"items\", \"records\", \"results\"]) if (Array.isArray(data[key])) return classifyToolOutcome(data[key])\n if (data.status === \"done\" || data.success === true) return \"done\"\n }\n if (isError) return \"failed\"\n if (Array.isArray(value)) return value.length === 0 ? \"no-data\" : \"done\"\n return \"unknown\"\n}\n\nconst QCC_NO_PERMISSION = /\u65E0\u6743\u9650|\u6743\u9650\u4E0D\u8DB3|\u672A\u6388\u6743|\u65E0\u6743\u8BBF\u95EE/u\nconst QCC_FAILURE = /\u67E5\u8BE2\u5931\u8D25|\u670D\u52A1\u5F02\u5E38|\u683C\u5F0F\u9519\u8BEF|\u5730\u57DF\u9650\u5236/u\nconst QCC_NO_DATA = /\u672A\u5339\u914D|\u672A\u53D1\u73B0(?:\u4EFB\u4F55|\u76F8\u5173)?\u8BB0\u5F55|\u6682\u65E0(?:\u76F8\u5173)?\u6570\u636E|\u65E0\u5339\u914D\u9879/u\n\n/**\n * QCC tools return reader-facing Chinese business objects instead of a generic\n * success/data envelope. A non-error, non-empty QCC object is a completed\n * query; explicit empty/error copy still wins over that fallback.\n */\nexport function classifyQccProviderOutcome(value: unknown, isError = false): ToolOutcome {\n const generic = classifyToolOutcome(value, isError)\n if (generic !== \"unknown\") return generic\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) return generic\n const data = value as Record<string, unknown>\n const messages = [data[\"\u65E0\u5339\u914D\u9879\"], data[\"\u641C\u7D22\u7ED3\u679C\"], data[\"\u63D0\u793A\"], data[\"\u6D88\u606F\"]]\n .filter((item): item is string => typeof item === \"string\")\n .join(\"\\n\")\n if (QCC_NO_PERMISSION.test(messages)) return \"no-permission\"\n if (QCC_FAILURE.test(messages) || \"\u683C\u5F0F\u9519\u8BEF\" in data || \"\u5730\u57DF\u9650\u5236\" in data) return \"failed\"\n if (QCC_NO_DATA.test(messages) || \"\u65E0\u5339\u914D\u9879\" in data) return \"no-data\"\n return Object.keys(data).length > 0 ? \"done\" : \"unknown\"\n}\n\nexport function resultOutcome(node: { isError?: boolean; content?: unknown; value?: unknown; error?: unknown }): ToolOutcome {\n if (node.error !== undefined) return classifyToolOutcome({ error: node.error }, true)\n if (node.value !== undefined) return classifyToolOutcome(node.value, node.isError)\n if (Array.isArray(node.content)) {\n const texts = node.content.filter((b): b is { type: string; text: string } => b?.type === \"text\" && typeof b.text === \"string\")\n if (texts.length === 1) {\n try { return classifyToolOutcome(JSON.parse(texts[0]!.text), node.isError) } catch { /* not structured */ }\n }\n }\n return classifyToolOutcome(node.content, node.isError)\n}\n\nexport function toolEvent(node: { call: { name: string }; isError?: boolean; content?: unknown; error?: unknown }): { name: string; status: ToolOutcome; reason?: string } {\n const fallback = { name: node.call.name, status: resultOutcome(node) }\n if (node.isError || !Array.isArray(node.content) || node.content.length !== 1 || node.content[0]?.type !== \"text\") return fallback\n try {\n const value = JSON.parse(node.content[0].text)\n if (node.call.name === \"previsit_query\" && typeof value.toolName === \"string\" && Object.hasOwn(TOOL_OUTCOME_LABELS, value.outcome)) {\n return { name: value.toolName, status: value.outcome, ...(typeof value.reason === \"string\" ? { reason: value.reason } : {}) }\n }\n if ((node.call.name === \"previsit_begin\" && value.status === \"needs-entity-search\") || (node.call.name === \"previsit_confirm_entity\" && value.status === \"entity-confirmed\")) return { name: node.call.name, status: \"done\" }\n } catch { /* Unrecognized content stays unknown/failed. */ }\n return fallback\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["h", "normalized", "run", "sessionId", "randomUUID", "randomUUID", "server", "tool"]
7
7
  }
@@ -16,6 +16,16 @@ export declare const PREVISIT_HOME_FLOWS: ReadonlyArray<{
16
16
  }>;
17
17
  /** 只新增自有容器,将能力菜单放到原生 composer 外部下方,不移动宿主 DOM。 */
18
18
  export declare function installCapabilityMount(marker: HTMLElement, onMount: (mount: HTMLElement | null) => void): () => void;
19
+ type HeroChrome = {
20
+ scope: HTMLElement;
21
+ title: HTMLElement;
22
+ };
23
+ /**
24
+ * 新版 Host 会把 list slot 挂在 composer seat 的独立分支,marker 不再一定是
25
+ * `[data-phase="hero"]` 的后代。优先沿用旧结构,再向上寻找同时包含原生输入区与标题的最小祖先。
26
+ */
27
+ export declare function resolvePrevisitHeroChrome(anchor: HTMLElement): HeroChrome | null;
19
28
  /** DSH 暂未公开会话级 Hero 标题槽位,因此仅在本插件空白会话内做可逆桥接。 */
20
29
  export declare function setPrevisitHeadline(anchor: HTMLElement): () => void;
21
30
  export declare function PrevisitHome({ sessionId, useSession, openWorkbench }: PrevisitHomeProps): JSX.Element | null;
31
+ export {};
@@ -1,6 +1,9 @@
1
+ import { type ActiveTask } from "./previsit-store.js";
1
2
  import { type BetterSidebarService } from "./better-sidebar.js";
2
3
  import { type LeftSidebarHost } from "./left-sidebar.js";
4
+ import { type HostedTask } from "./hosted-task-sync.js";
3
5
  import { type WorkspaceNavigation } from "./ordinary-session-guard.js";
6
+ import { type WorkbenchStatus } from "./workbench-state.js";
4
7
  export declare const inject: readonly ["slots", "sessions", "workspaces", "conversation"];
5
8
  export declare const OPTIONAL_WORKBENCH_MESSAGE = "\u672A\u5B89\u88C5\u53EF\u9009 Better Sidebar\uFF1B\u5F53\u524D\u8349\u7A3F\u548C\u4E1A\u52A1\u72B6\u6001\u5DF2\u4FDD\u7559\u3002\u57FA\u7840\u4F1A\u8BDD\u3001\u63D0\u793A\u8BCD\u751F\u6210\u3001\u539F\u751F\u53D1\u9001\u53CA\u4F1A\u8BDD\u62A5\u544A\u9605\u8BFB\u4ECD\u53EF\u4F7F\u7528\u3002\u5982\u9700\u53EF\u89C6\u5316\u5DE5\u4F5C\u53F0\uFF0C\u8BF7\u505C\u6B62 DSH \u540E\u6309 README \u8BBE\u7F6E DSH_PREVISIT_WORKBENCH=on \u5E76\u91CD\u8DD1\u5B89\u88C5\u811A\u672C\u3002";
6
9
  type SnapshotStore<T> = {
@@ -58,5 +61,16 @@ type ClientContext = LeftSidebarHost & {
58
61
  inject(deps: string[], setup: (ctx: ClientContext) => void): unknown;
59
62
  effect(setup: () => void | (() => void), label?: string): unknown;
60
63
  };
64
+ export declare function HistoryPanel(props: {
65
+ task: ActiveTask | undefined;
66
+ status: WorkbenchStatus;
67
+ hosted: HostedTask[];
68
+ selected: HostedTask | null;
69
+ loadingTaskId: string | undefined;
70
+ downloadingTaskId: string | undefined;
71
+ onOpen(item: HostedTask): void;
72
+ onBack(): void;
73
+ onDownload(item: HostedTask): void;
74
+ }): JSX.Element;
61
75
  export declare function apply(ctx: ClientContext): void;
62
76
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-pre-duediligence",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "Pre-visit due diligence and company research in DeepSeek Harness with Host-tracked progress, fixed-route Qichacha MCP calls, entity confirmation, and downloadable reports.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -5,7 +5,7 @@ whenToUse: 用户要求准备客户拜访、访前尽调、一页纸简报、授
5
5
  user-invocable: true
6
6
  metadata:
7
7
  author: QCC
8
- version: 0.1.23
8
+ version: 0.1.24
9
9
  ---
10
10
 
11
11
  # 访前尽调
@@ -54,7 +54,7 @@ metadata:
54
54
 
55
55
  ## MCP 就绪检查
56
56
 
57
- ### Agent-owned 执行门(0.1.23)
57
+ ### Agent-owned 执行门(0.1.24)
58
58
 
59
59
  只在从“访前尽调”入口创建的专属 Session 内执行。首先调用 `previsit_begin`,传入用户提供的企业检索词和 fast / standard / deep 档位;提示中含“访前任务 ID:PV-...”时,必须原样作为 `requestId` 传入。用户发送任务已经构成本任务在固定业务路由内连续执行的明确授权,禁止再询问是否允许调用企查查 MCP,也不要弹出调用次数或额度二次确认。插件不设 8 / 18 / 40 次硬上限。
60
60