dsh-pre-duediligence 0.1.25 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +6 -6
- package/docs/MARKETPLACE.md +3 -3
- package/docs/RELEASE-0.1.26.md +10 -0
- package/docs/SEARCH-CONVERSION.md +2 -2
- package/install.sh +1 -1
- package/lib/client.js +35 -9
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/types/previsit-home.d.ts +6 -2
- package/lib/types/processing-status.d.ts +11 -0
- package/package.json +1 -1
- package/skills/qcc-previsit-onepager/SKILL.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
## [Unreleased]
|
|
3
3
|
|
|
4
|
+
## [0.1.26] - 2026-09-13
|
|
5
|
+
|
|
6
|
+
- 首页标题改为“访前尽调智能体”,适配当前 Host 的 blank / awaitingFirstTurn 状态,并由原生 Hero DOM 限定可逆标题桥接,不再依赖已移除的 composerPhase。
|
|
7
|
+
- 当前任务五个 TAB 共用滚动区域外的处理状态条,显示查询、分析等待时长、主体确认、停止与同步异常;计时不作为联网或运行证明,不展示虚构百分比。
|
|
8
|
+
|
|
4
9
|
## [0.1.25] - 2026-09-13
|
|
5
10
|
|
|
6
11
|
- 历史清单隐藏阶段按钮,详情提供独立五阶段只读回顾。
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# dsh-pre-duediligence
|
|
2
2
|
|
|
3
|
-
0.1.
|
|
3
|
+
0.1.26 修复首页标题,显示 Logo + 访前尽调智能体;所有当前任务 TAB 共用常驻处理状态条,展示运行、分析等待、停止与同步异常,长列表滚动不再遮住状态提示。不展示虚构百分比。详见 [发布说明](docs/RELEASE-0.1.26.md)、[作用域契约](docs/TASK-SCOPE-HISTORY-CONTRACT.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.
|
|
10
|
+
dsh plugin --profile web add dsh-pre-duediligence@0.1.26
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
请先满足下文的 DSH 与连接器要求;Better Sidebar 仅在需要可视化工作台时选装。安装后完整停止并重启对应 Profile。
|
|
@@ -23,7 +23,7 @@ dsh plugin --profile web add dsh-pre-duediligence@0.1.25
|
|
|
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.
|
|
26
|
+
版本:**0.1.26**。本版保持 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.
|
|
95
|
+
2. `dsh-pre-duediligence@0.1.26`:提供左侧智能体入口、原生会话首页、提示词生成器、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.
|
|
123
|
+
dsh plugin --profile web add dsh-pre-duediligence@0.1.26 --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.
|
|
139
|
+
应始终看到所选基线的 Connector 和 `dsh-pre-duediligence@0.1.26`;仅在显式启用或原本已安装时看到 Sidebar。重启 DSH 并连接“企查查·企业工商”后,在左侧菜单点击“访前尽调”进入初始原生会话。无 Sidebar 时可继续生成提示词、回填/发送草稿并在会话中阅读报告;五项流程按钮会提示安装可选工作台。安装 Sidebar 时,右侧工作台仍默认关闭,点击业务按钮后才打开对应视图;其右侧“+”菜单不会列出该入口,普通会话保持 DSH 原样。
|
|
140
140
|
|
|
141
141
|
### 无 Sidebar 功能边界
|
|
142
142
|
|
package/docs/MARKETPLACE.md
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
| 项目 | 已核验值 |
|
|
8
8
|
| --- | --- |
|
|
9
9
|
| 仓库 | `https://github.com/duhu2000/dsh-pre-duediligence`,公开、未归档 |
|
|
10
|
-
| npm | `dsh-pre-duediligence@0.1.
|
|
11
|
-
| Release | `https://github.com/duhu2000/dsh-pre-duediligence/releases/tag/v0.1.
|
|
10
|
+
| npm | `dsh-pre-duediligence@0.1.26`,发布后核验 `latest=0.1.26` |
|
|
11
|
+
| Release | `https://github.com/duhu2000/dsh-pre-duediligence/releases/tag/v0.1.26` |
|
|
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.
|
|
31
|
+
- 0.1.26 保持显式 TaskScope 与旧记录只读边界,并补齐稳定菜单名、Host 新版首页标题定位及历史详情/报告下载;生产账号端到端复测仍以用户验收为准,市场描述不把待验项写成已通过。
|
|
32
32
|
|
|
33
33
|
## 安装、卸载与截图证据
|
|
34
34
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# 0.1.26 · 首页标题与常驻处理状态
|
|
2
|
+
|
|
3
|
+
- 首页显示 Logo + 访前尽调智能体。适配当前 DSH 的 blank / awaitingFirstTurn 字段,并由原生 Hero DOM 限定标题桥接;不再依赖旧 composerPhase 字段启动替换。
|
|
4
|
+
- 当前任务所有 TAB 共用滚动区域外的常驻状态条,查看长风险列表时仍可看到任务状态。
|
|
5
|
+
- 区分宿主运行中、查询待返回、分析等待、主体确认、会话停止和状态同步异常;显示等待时长而非虚构完成百分比。
|
|
6
|
+
- 历史任务不显示当前会话的运行状态。切换到不可读取运行状态的会话时清除旧状态。
|
|
7
|
+
|
|
8
|
+
验证:174 项单元测试、类型检查、构建及四种隔离浏览器场景。模拟当前 Host 字段,不连接真实 MCP;生产环境仍需升级并重启 DSH 后验收。
|
|
9
|
+
|
|
10
|
+
发布沿用 main CI → 注释 tag → GitHub OIDC npm Trusted Publishing → registry 与 provenance 核验,无需重复人工 npm 登录。
|
|
@@ -12,14 +12,14 @@ npm keywords 已在包清单内更新。GitHub description 建议使用上述英
|
|
|
12
12
|
|
|
13
13
|
验收:使用实时完整目录和原版搜索函数对比登记描述更新前后;记录语言、命中位置、结果数与版本。独立目录缺失条目必须标为未上线,不能把模拟添加的结果当作上线结果。
|
|
14
14
|
|
|
15
|
-
发布清单:0.1.
|
|
15
|
+
发布清单:0.1.26 已获得发布授权;完成仓库 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.
|
|
22
|
+
dsh plugin --profile web add dsh-pre-duediligence@0.1.26
|
|
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.
|
|
8
|
+
readonly PREVISIT_SPEC="${DSH_PRE_DUEDILIGENCE_SPEC:-dsh-pre-duediligence@0.1.26}"
|
|
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
|
@@ -241,6 +241,15 @@ function PrevisitLogo(props) {
|
|
|
241
241
|
);
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
// src/processing-status.ts
|
|
245
|
+
function processingStatus(input) {
|
|
246
|
+
if (input.error) return { busy: false, title: "\u72B6\u6001\u540C\u6B65\u5F02\u5E38", detail: "\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u4EFB\u52A1\u8FDB\u5C55\uFF0C\u8BF7\u67E5\u770B\u4F1A\u8BDD\u6216\u8FDE\u63A5\u72B6\u6001\u3002" };
|
|
247
|
+
if (input.waiting) return { busy: false, title: "\u7B49\u5F85\u4E3B\u4F53\u786E\u8BA4 / \u7EE7\u7EED", detail: "\u8BF7\u5728\u4F1A\u8BDD\u4E2D\u786E\u8BA4\u4F01\u4E1A\u540E\u7EE7\u7EED\u3002" };
|
|
248
|
+
if (input.running === false) return { busy: false, title: "\u672C\u8F6E\u4F1A\u8BDD\u5DF2\u505C\u6B62\uFF0C\u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210", detail: "\u8BF7\u67E5\u770B\u4F1A\u8BDD\u4E2D\u7684\u786E\u8BA4\u8BF7\u6C42\u3001\u505C\u6B62\u6216\u9519\u8BEF\u63D0\u793A\u3002" };
|
|
249
|
+
if (input.querying) return { busy: input.running === true, title: input.running === true ? "\u6B63\u5728\u67E5\u8BE2\u8D44\u6599" : "\u67E5\u8BE2\u7ED3\u679C\u5F85\u66F4\u65B0", detail: "\u5C1A\u672A\u6536\u5230\u8BE5\u67E5\u8BE2\u7684\u5B8C\u6210\u8BB0\u5F55\uFF0C\u7ED3\u679C\u8FD4\u56DE\u540E\u81EA\u52A8\u66F4\u65B0\u3002" };
|
|
250
|
+
return { busy: input.running === true, title: input.running === true ? "\u5BBF\u4E3B\u663E\u793A\u4ECD\u5728\u5904\u7406 \xB7 \u6B63\u5728\u5206\u6790 / \u6574\u7406\u6750\u6599" : "\u7B49\u5F85\u4E0B\u4E00\u6B21\u4EFB\u52A1\u66F4\u65B0", detail: `\u8DDD\u6700\u8FD1\u4EFB\u52A1\u7ED3\u679C ${Math.max(0, Math.floor(input.secondsSinceResult))} \u79D2\u3002${input.secondsSinceResult >= 120 ? "\u7B49\u5F85\u65F6\u95F4\u8F83\u957F\uFF0C\u53EF\u68C0\u67E5\u4F1A\u8BDD\u8FD0\u884C\u72B6\u6001\uFF1B\u8BF7\u52FF\u91CD\u590D\u542F\u52A8\u4EFB\u52A1\u3002" : "\u5206\u6790\u9636\u6BB5\u53EF\u80FD\u6682\u65F6\u6CA1\u6709\u65B0\u7684\u5DE5\u5177\u7ED3\u679C\u3002"} \u4E0D\u63D0\u4F9B\u865A\u6784\u767E\u5206\u6BD4\u6216\u5185\u90E8\u601D\u8003\u5185\u5BB9\u3002` };
|
|
251
|
+
}
|
|
252
|
+
|
|
244
253
|
// src/hosted-task-sync.ts
|
|
245
254
|
function hostedTaskOrigin(task) {
|
|
246
255
|
const workspace = task.workspace.trim() || "\u672A\u8BB0\u5F55\uFF08\u65E7\u8BB0\u5F55\uFF09";
|
|
@@ -1468,7 +1477,7 @@ function registerLeftSidebarLauncher(ctx, isActive = () => true) {
|
|
|
1468
1477
|
var import_react5 = require("react");
|
|
1469
1478
|
var import_react_dom4 = require("react-dom");
|
|
1470
1479
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1471
|
-
var PREVISIT_HOME_TITLE = "\u8BBF\u524D\u5C3D\u8C03\
|
|
1480
|
+
var PREVISIT_HOME_TITLE = "\u8BBF\u524D\u5C3D\u8C03\u667A\u80FD\u4F53";
|
|
1472
1481
|
var PREVISIT_HOME_SUMMARY = "\u660E\u786E\u62DC\u8BBF\u5BF9\u8C61\u4E0E\u76EE\u6807\uFF0C\u6838\u9A8C\u4F01\u4E1A\u4FE1\u606F\u5E76\u51C6\u5907\u8BBF\u524D\u6750\u6599\u3002";
|
|
1473
1482
|
var PREVISIT_HOME_FLOWS = [
|
|
1474
1483
|
{ view: "target", label: "\u5BF9\u8C61\u4E0E\u76EE\u6807", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
@@ -1534,6 +1543,8 @@ function installCapabilityMount(marker, onMount) {
|
|
|
1534
1543
|
};
|
|
1535
1544
|
}
|
|
1536
1545
|
function resolvePrevisitHeroChrome(anchor) {
|
|
1546
|
+
const phaseRoot = anchor.closest("[data-phase]");
|
|
1547
|
+
if (phaseRoot?.getAttribute?.("data-phase") === "active") return null;
|
|
1537
1548
|
const direct = anchor.closest('[data-phase="hero"]');
|
|
1538
1549
|
const findTitle = (scope2) => {
|
|
1539
1550
|
const spans = [...scope2.querySelectorAll("span")];
|
|
@@ -1612,7 +1623,7 @@ function setPrevisitHeadline(anchor) {
|
|
|
1612
1623
|
};
|
|
1613
1624
|
}
|
|
1614
1625
|
function PrevisitHome({ sessionId, useSession, openWorkbench: openWorkbench2 }) {
|
|
1615
|
-
const blank = useSession((state) => state.composerPhase === "blank");
|
|
1626
|
+
const blank = useSession((state) => state.composerPhase === "blank" || state.composerPhase === void 0 && (state.blank === true || state.awaitingFirstTurn === true) && !state.running && !state.promptAttempted);
|
|
1616
1627
|
const enabled = isPrevisitSession(sessionId);
|
|
1617
1628
|
const marker = (0, import_react5.useRef)(null);
|
|
1618
1629
|
const [menuMount, setMenuMount] = (0, import_react5.useState)(null);
|
|
@@ -1625,7 +1636,7 @@ function PrevisitHome({ sessionId, useSession, openWorkbench: openWorkbench2 })
|
|
|
1625
1636
|
return installCapabilityMount(marker.current, (mount) => setMenuMount((current) => current === mount ? current : mount));
|
|
1626
1637
|
}, [enabled, sessionId, blank]);
|
|
1627
1638
|
(0, import_react5.useEffect)(() => {
|
|
1628
|
-
if (!enabled ||
|
|
1639
|
+
if (!enabled || marker.current === null) return;
|
|
1629
1640
|
return setPrevisitHeadline(marker.current);
|
|
1630
1641
|
}, [enabled, blank, sessionId]);
|
|
1631
1642
|
if (!enabled) return null;
|
|
@@ -1930,6 +1941,7 @@ html[data-theme="dark"] .qccPrevisitHeroLogo,html.dark .qccPrevisitHeroLogo{colo
|
|
|
1930
1941
|
.qccPwIcon{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.65;stroke-linecap:round;stroke-linejoin:round}
|
|
1931
1942
|
.qccPwStageCopy{display:block;min-width:0;max-width:100%}.qccPwStageCopy strong{display:block;overflow:hidden;font-size:12px;line-height:1.35;text-overflow:ellipsis;white-space:nowrap}
|
|
1932
1943
|
.qccPwBody{min-height:0;padding:16px;overflow:auto;flex:1}.qccPwPanel{display:grid;width:100%;min-width:0;gap:13px;max-width:960px;margin:0 auto}
|
|
1944
|
+
.qccPwShell>.qccPwLiveProgress{flex-shrink:0;margin:8px 12px;max-height:180px;overflow:auto}.qccPwLiveProgress[data-running="false"] .qccPwLivePulse{animation:none;background:var(--qcc-secondary)}
|
|
1933
1945
|
.qccPwPageHeading{display:flex;align-items:flex-start;justify-content:space-between;gap:14px}.qccPwPageHeading h2{margin:1px 0 0;font-size:19px}.qccPwPageHeading p:not(.qccPwEyebrow){margin:4px 0 0;color:var(--qcc-secondary);font-size:12px;line-height:1.5}.qccPwEyebrow{margin:0;color:var(--qcc-action);font-size:10px;font-weight:750;letter-spacing:.12em}.qccPwTaskId{padding:5px 8px;border:1px solid var(--qcc-border);border-radius:7px;background:var(--qcc-surface);color:var(--qcc-secondary);font:11px ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
1934
1946
|
.qccPwCard{min-width:0;padding:15px;border:1px solid var(--qcc-border);border-radius:12px;background:var(--qcc-surface)}.qccPwCardHeader{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}.qccPwCardHeader>div{min-width:0;flex:1 1 0}.qccPwCardHeader h3{margin:0;font-size:14px}.qccPwCardHeader p{margin:4px 0 0;color:var(--qcc-secondary);font-size:11px;line-height:1.5}.qccPwModeGroup{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:5px}.qccPwMode{padding:3px 7px;border-radius:999px;background:var(--qcc-selected);color:var(--qcc-action);font-size:10px;white-space:nowrap}.qccPwMode[data-tone="neutral"]{background:var(--qcc-table-head);color:var(--qcc-secondary)}.qccPwMode[data-tone="success"]{background:var(--qcc-success-bg);color:var(--qcc-success)}.qccPwMode[data-tone="review"]{background:var(--qcc-review-bg);color:var(--qcc-review)}.qccPwMode[data-tone="error"]{background:var(--qcc-danger-bg);color:var(--qcc-danger)}
|
|
1935
1947
|
.qccPwFeedback{display:flex;align-items:flex-start;gap:10px;padding:12px 13px;border:1px solid var(--qcc-border);border-radius:10px;background:var(--qcc-table-head)}.qccPwFeedback[data-tone="success"]{border-color:var(--qcc-success);background:var(--qcc-success-bg)}.qccPwFeedback[data-tone="error"]{border-color:var(--qcc-danger);background:var(--qcc-danger-bg)}.qccPwFeedbackIcon{color:var(--qcc-action);flex:none}.qccPwFeedback[data-tone="success"] .qccPwFeedbackIcon{color:var(--qcc-success)}.qccPwFeedback[data-tone="error"] .qccPwFeedbackIcon{color:var(--qcc-danger)}.qccPwFeedback strong{font-size:13px}.qccPwFeedback p{margin:3px 0 0;color:var(--qcc-secondary);font-size:11px;line-height:1.55}
|
|
@@ -2174,6 +2186,7 @@ var STATUS_LABELS = {
|
|
|
2174
2186
|
failed: "\u9700\u8981\u5904\u7406"
|
|
2175
2187
|
};
|
|
2176
2188
|
var EMPTY_RUNTIME = {
|
|
2189
|
+
available: false,
|
|
2177
2190
|
running: false,
|
|
2178
2191
|
partial: false,
|
|
2179
2192
|
lastAgentError: null,
|
|
@@ -2249,13 +2262,25 @@ function Feedback(props) {
|
|
|
2249
2262
|
] });
|
|
2250
2263
|
}
|
|
2251
2264
|
function ExecutionProgress(props) {
|
|
2265
|
+
const [now, setNow] = (0, import_react6.useState)(Date.now);
|
|
2266
|
+
(0, import_react6.useEffect)(() => {
|
|
2267
|
+
if (props.task === null || props.status === "ready") return;
|
|
2268
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
2269
|
+
return () => clearInterval(timer);
|
|
2270
|
+
}, [props.task?.id, props.status]);
|
|
2252
2271
|
if (props.task === null || props.status === "ready") return null;
|
|
2253
2272
|
const progress = hostedLiveProgress(props.task);
|
|
2254
|
-
|
|
2273
|
+
const activity = processingStatus({ running: props.modelRunning, error: props.syncError, waiting: props.task.state === "needs-entity-confirmation", querying: progress.current !== null, secondsSinceResult: (now - Date.parse(props.task.updatedAt)) / 1e3 });
|
|
2274
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwLiveProgress", "data-running": activity.busy, children: [
|
|
2255
2275
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "qccPwLivePulse", "aria-hidden": "true" }),
|
|
2256
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwLiveCopy", role: "status",
|
|
2257
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children:
|
|
2258
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children:
|
|
2276
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwLiveCopy", role: "status", children: [
|
|
2277
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: activity.title }),
|
|
2278
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: activity.detail }),
|
|
2279
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { children: [
|
|
2280
|
+
progress.title,
|
|
2281
|
+
" \xB7 ",
|
|
2282
|
+
progress.detail
|
|
2283
|
+
] })
|
|
2259
2284
|
] }),
|
|
2260
2285
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwLiveMetrics", "aria-label": "\u5B9E\u65F6\u6267\u884C\u7EDF\u8BA1", children: [
|
|
2261
2286
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
@@ -2437,7 +2462,6 @@ function OpportunityPanel(props) {
|
|
|
2437
2462
|
const steps = opportunitySteps(props.events, insights, finished);
|
|
2438
2463
|
const dims = opportunityDimensions(props.events);
|
|
2439
2464
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(StagePanel, { eyebrow: "COLLECT", title: "\u8D44\u6599\u91C7\u96C6", task: props.task, children: [
|
|
2440
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExecutionProgress, { task: props.hostedTask, status: props.status }),
|
|
2441
2465
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Steps, { steps }),
|
|
2442
2466
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Dimensions, { title: "\u91C7\u96C6\u7ED3\u679C", items: dims, empty: running2 ? "\u6B63\u5728\u5EFA\u7ACB\u4E3B\u4F53\u4E0E\u4FE1\u53F7\u96C6\u2026" : "\u5C3D\u8C03\u5F00\u59CB\u540E\u663E\u793A\u91C7\u96C6\u7ED3\u679C" }),
|
|
2443
2467
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScanFindings, { task: props.hostedTask }),
|
|
@@ -2490,7 +2514,6 @@ function RiskPanel(props) {
|
|
|
2490
2514
|
const real = (level) => rows(level).filter((r) => isRiskFindingText(r.text));
|
|
2491
2515
|
const judged = insights.sections.includes("\u7EA2\u7EBF\u63D0\u793A");
|
|
2492
2516
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(StagePanel, { eyebrow: "VERIFY", title: "\u8BC1\u636E\u6838\u9A8C", task: props.task, children: [
|
|
2493
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExecutionProgress, { task: props.hostedTask, status: props.status }),
|
|
2494
2517
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Steps, { steps }),
|
|
2495
2518
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Dimensions, { title: "\u6838\u9A8C\u7ED3\u679C", items: dims, empty: running2 ? "\u7B49\u5F85\u98CE\u9669\u626B\u63CF\u2026" : "\u5C3D\u8C03\u5F00\u59CB\u540E\u663E\u793A\u6838\u9A8C\u7ED3\u679C" }),
|
|
2496
2519
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScanFindings, { task: props.hostedTask }),
|
|
@@ -2833,6 +2856,7 @@ function PrevisitWorkbenchTab(props) {
|
|
|
2833
2856
|
(0, import_react6.useEffect)(() => {
|
|
2834
2857
|
const face = props.ctx.sessions.binding?.(sessionId)?.session;
|
|
2835
2858
|
if (face === void 0) {
|
|
2859
|
+
setRuntime(EMPTY_RUNTIME);
|
|
2836
2860
|
return;
|
|
2837
2861
|
}
|
|
2838
2862
|
const refresh = () => {
|
|
@@ -2849,6 +2873,7 @@ function PrevisitWorkbenchTab(props) {
|
|
|
2849
2873
|
const baseline = task?.nodeBaseline ?? (snapshot.nodes?.length ?? 0);
|
|
2850
2874
|
const calls = collectRuntime(snapshot, baseline);
|
|
2851
2875
|
setRuntime({
|
|
2876
|
+
available: typeof snapshot.running === "boolean",
|
|
2852
2877
|
running: snapshot.running === true,
|
|
2853
2878
|
partial: snapshot.partial !== null && snapshot.partial !== void 0,
|
|
2854
2879
|
lastAgentError: snapshot.lastAgentError ?? null,
|
|
@@ -3038,6 +3063,7 @@ function PrevisitWorkbenchTab(props) {
|
|
|
3038
3063
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "qccPwStageCopy", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: PHASE_LABELS[current] }) })
|
|
3039
3064
|
] }, current);
|
|
3040
3065
|
}) }),
|
|
3066
|
+
shared.view !== "history" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExecutionProgress, { task: hostedTask, status, modelRunning: runtime.available ? runtime.running : void 0, syncError: hostError ?? runtime.lastAgentError ?? void 0 }) : null,
|
|
3041
3067
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwBody", children: [
|
|
3042
3068
|
shared.view === "target" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SetupPanel, { sessionId, store: props.shared, task, input: resolveSessionInput(props.ctx, sessionId), start: (prompt) => props.startPrompt(sessionId, prompt), onStarted: () => setPhase("collect") }) : null,
|
|
3043
3069
|
shared.view === "scope" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScopePanel, { state: shared, task }) : null,
|
package/lib/index.js
CHANGED
|
@@ -1014,7 +1014,7 @@ function loadBundledSkill() {
|
|
|
1014
1014
|
},
|
|
1015
1015
|
metadata: {
|
|
1016
1016
|
author: "QCC",
|
|
1017
|
-
version: "0.1.
|
|
1017
|
+
version: "0.1.26",
|
|
1018
1018
|
industry: "enterprise-services",
|
|
1019
1019
|
mcpServers: ["qcc-company", "qcc-risk", "qcc-ipr", "qcc-operation", "qcc-executive"]
|
|
1020
1020
|
}
|
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/result-summary.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.25\",\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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\")\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\"\nimport type { ResultSummary } from \"./result-summary.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 result?: ResultSummary\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, result?: ResultSummary): 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 ...(result === undefined ? {} : { result }),\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 { summarizeResult } from \"./result-summary.js\"\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, usable ? summarizeResult(data) : 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", "/** Bounded provider facts for live display and historical review; never executable content. */\nexport type ResultSummary = { summary: string; facts: string[]; factors: { name: string; count: number }[] }\n\nexport function summarizeResult(value: unknown): ResultSummary {\n const result: ResultSummary = { summary: \"\", facts: [], factors: [] }\n let visited = 0\n const visit = (item: unknown, depth: number): void => {\n if (depth > 7 || ++visited > 1000 || item === null || typeof item !== \"object\") return\n if (Array.isArray(item)) { item.slice(0, 80).forEach(child => visit(child, depth + 1)); return }\n const row = item as Record<string, unknown>\n const name = row[\"\u98CE\u9669\u56E0\u5B50\"] ?? row[\"\u56E0\u5B50\u540D\u79F0\"]\n const raw = row[\"\u6761\u76EE\u6570\"] ?? row[\"\u8BB0\u5F55\u6570\"]\n const count = typeof raw === \"number\" ? raw : typeof raw === \"string\" && /^\\d+$/.test(raw) ? Number(raw) : NaN\n if (typeof name === \"string\" && Number.isSafeInteger(count) && count >= 0 && !result.factors.some(f => f.name === name)) result.factors.push({ name: name.slice(0, 100), count })\n for (const [key, child] of Object.entries(row)) {\n if ((key === \"\u6458\u8981\" || key === \"summary\") && typeof child === \"string\" && !result.summary) result.summary = child.slice(0, 1800)\n if (depth <= 2 && /^(\u7ECF\u8425\u72B6\u6001|\u767B\u8BB0\u72B6\u6001|\u4E3B\u8425\u4E1A\u52A1|\u7ECF\u8425\u8303\u56F4|\u6240\u5C5E\u884C\u4E1A|\u884C\u4E1A|\u6CE8\u518C\u8D44\u672C|\u6210\u7ACB\u65E5\u671F|\u53C2\u4FDD\u4EBA\u6570|\u4F01\u4E1A\u540D\u79F0)$/.test(key) && (typeof child === \"string\" || typeof child === \"number\") && result.facts.length < 12) result.facts.push(`${key}\uFF1A${String(child).slice(0, 500)}`)\n visit(child, depth + 1)\n }\n }\n visit(value, 0)\n return result\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.26\",\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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\")\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\"\nimport type { ResultSummary } from \"./result-summary.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 result?: ResultSummary\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, result?: ResultSummary): 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 ...(result === undefined ? {} : { result }),\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 { summarizeResult } from \"./result-summary.js\"\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, usable ? summarizeResult(data) : 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", "/** Bounded provider facts for live display and historical review; never executable content. */\nexport type ResultSummary = { summary: string; facts: string[]; factors: { name: string; count: number }[] }\n\nexport function summarizeResult(value: unknown): ResultSummary {\n const result: ResultSummary = { summary: \"\", facts: [], factors: [] }\n let visited = 0\n const visit = (item: unknown, depth: number): void => {\n if (depth > 7 || ++visited > 1000 || item === null || typeof item !== \"object\") return\n if (Array.isArray(item)) { item.slice(0, 80).forEach(child => visit(child, depth + 1)); return }\n const row = item as Record<string, unknown>\n const name = row[\"\u98CE\u9669\u56E0\u5B50\"] ?? row[\"\u56E0\u5B50\u540D\u79F0\"]\n const raw = row[\"\u6761\u76EE\u6570\"] ?? row[\"\u8BB0\u5F55\u6570\"]\n const count = typeof raw === \"number\" ? raw : typeof raw === \"string\" && /^\\d+$/.test(raw) ? Number(raw) : NaN\n if (typeof name === \"string\" && Number.isSafeInteger(count) && count >= 0 && !result.factors.some(f => f.name === name)) result.factors.push({ name: name.slice(0, 100), count })\n for (const [key, child] of Object.entries(row)) {\n if ((key === \"\u6458\u8981\" || key === \"summary\") && typeof child === \"string\" && !result.summary) result.summary = child.slice(0, 1800)\n if (depth <= 2 && /^(\u7ECF\u8425\u72B6\u6001|\u767B\u8BB0\u72B6\u6001|\u4E3B\u8425\u4E1A\u52A1|\u7ECF\u8425\u8303\u56F4|\u6240\u5C5E\u884C\u4E1A|\u884C\u4E1A|\u6CE8\u518C\u8D44\u672C|\u6210\u7ACB\u65E5\u671F|\u53C2\u4FDD\u4EBA\u6570|\u4F01\u4E1A\u540D\u79F0)$/.test(key) && (typeof child === \"string\" || typeof child === \"number\") && result.facts.length < 12) result.facts.push(`${key}\uFF1A${String(child).slice(0, 500)}`)\n visit(child, depth + 1)\n }\n }\n visit(value, 0)\n return result\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
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import type { PrevisitView } from "./previsit-store.js";
|
|
3
|
-
export declare const PREVISIT_HOME_TITLE = "\u8BBF\u524D\u5C3D\u8C03\
|
|
3
|
+
export declare const PREVISIT_HOME_TITLE = "\u8BBF\u524D\u5C3D\u8C03\u667A\u80FD\u4F53";
|
|
4
4
|
export declare const PREVISIT_HOME_SUMMARY = "\u660E\u786E\u62DC\u8BBF\u5BF9\u8C61\u4E0E\u76EE\u6807\uFF0C\u6838\u9A8C\u4F01\u4E1A\u4FE1\u606F\u5E76\u51C6\u5907\u8BBF\u524D\u6750\u6599\u3002";
|
|
5
5
|
export type PrevisitHomeProps = {
|
|
6
6
|
sessionId: string;
|
|
7
7
|
useSession<T>(selector: (state: {
|
|
8
|
-
composerPhase
|
|
8
|
+
composerPhase?: string;
|
|
9
|
+
blank?: boolean;
|
|
10
|
+
awaitingFirstTurn?: boolean;
|
|
11
|
+
running?: boolean;
|
|
12
|
+
promptAttempted?: boolean;
|
|
9
13
|
}) => T): T;
|
|
10
14
|
openWorkbench?: (view?: PrevisitView) => void;
|
|
11
15
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-pre-duediligence",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
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.
|
|
8
|
+
version: 0.1.26
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# 访前尽调
|
|
@@ -54,7 +54,7 @@ metadata:
|
|
|
54
54
|
|
|
55
55
|
## MCP 就绪检查
|
|
56
56
|
|
|
57
|
-
### Agent-owned 执行门(0.1.
|
|
57
|
+
### Agent-owned 执行门(0.1.26)
|
|
58
58
|
|
|
59
59
|
只在从“访前尽调”入口创建的专属 Session 内执行。首先调用 `previsit_begin`,传入用户提供的企业检索词和 fast / standard / deep 档位;提示中含“访前任务 ID:PV-...”时,必须原样作为 `requestId` 传入。用户发送任务已经构成本任务在固定业务路由内连续执行的明确授权,禁止再询问是否允许调用企查查 MCP,也不要弹出调用次数或额度二次确认。插件不设 8 / 18 / 40 次硬上限。
|
|
60
60
|
|