dsh-pre-duediligence 0.1.26 → 0.1.28
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 +12 -0
- package/README.md +6 -6
- package/docs/HOME-STANDARD-1.5.4.md +15 -0
- package/docs/MARKETPLACE.md +3 -3
- package/docs/RELEASE-0.1.27.md +7 -0
- package/docs/RELEASE-0.1.28.md +9 -0
- package/docs/SEARCH-CONVERSION.md +2 -2
- package/install.sh +1 -1
- package/lib/client.js +156 -23
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/types/previsit-home.d.ts +0 -1
- package/lib/types/submission-reveal.d.ts +30 -0
- package/package.json +1 -1
- package/skills/qcc-previsit-onepager/SKILL.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
## [Unreleased]
|
|
3
3
|
|
|
4
|
+
## [0.1.28] - 2026-09-14
|
|
5
|
+
|
|
6
|
+
- 按 DSH-UX-001 v1.5.5 / UX-48,提示词回填仍保持工作台关闭;任务正式接纳后自动展开当前 Session 的访前尽调工作台。
|
|
7
|
+
- 原生提交与工作台直接发送共用单次展开去重;失败、历史恢复、后台晚到事件和持续轮询不触发展开,用户手动收起后不会被同一提交重复拉开。
|
|
8
|
+
- 可选工作台缺失或禁用时提示任务已提交、可在原生会话查看进展,避免重复发送。保留 0.1.27 首页规范修复。
|
|
9
|
+
|
|
10
|
+
## [0.1.27] - 2026-09-14
|
|
11
|
+
|
|
12
|
+
- 移除首页用途副标题及占位,紧接原生工作区/模式与输入框,采用共享规范 v1.5.4。
|
|
13
|
+
- 限制标题桥接范围并保留自定义文本;离开 Hero 恢复原状,监听页面阶段变化。
|
|
14
|
+
- 保留固定菜单与真实处理状态;175 测试及4组隔离 Chromium 页面验证通过,真实DSH四包同装另验。
|
|
15
|
+
|
|
4
16
|
## [0.1.26] - 2026-09-13
|
|
5
17
|
|
|
6
18
|
- 首页标题改为“访前尽调智能体”,适配当前 Host 的 blank / awaitingFirstTurn 状态,并由原生 Hero DOM 限定可逆标题桥接,不再依赖已移除的 composerPhase。
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# dsh-pre-duediligence
|
|
2
2
|
|
|
3
|
-
0.1.
|
|
3
|
+
0.1.28 按 DSH-UX-001 v1.5.5 / UX-48,在任务正式接纳后自动展开对应工作台;菜单进入和提示词回填仍保持关闭。重复回调、历史恢复及后台事件不会重复拉开手动收起的面板,并保留 0.1.27 首页规范修复。详见 [发布说明](docs/RELEASE-0.1.28.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.28
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
请先满足下文的 DSH 与连接器要求;Better Sidebar 仅在需要可视化工作台时选装。安装后完整停止并重启对应 Profile。
|
|
@@ -23,7 +23,7 @@ dsh plugin --profile web add dsh-pre-duediligence@0.1.26
|
|
|
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.28**。本版保持 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.28`:提供左侧智能体入口、原生会话首页、提示词生成器、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.28 --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.28`;仅在显式启用或原本已安装时看到 Sidebar。重启 DSH 并连接“企查查·企业工商”后,在左侧菜单点击“访前尽调”进入初始原生会话。无 Sidebar 时可继续生成提示词、回填/发送草稿并在会话中阅读报告;五项流程按钮会提示安装可选工作台。安装 Sidebar 时,右侧工作台初始仍关闭;点击业务按钮可主动打开,正式发送任务被接纳后会自动展开一次。仅回填不会展开,收起后轮询不会重新打开。其右侧“+”菜单不会列出该入口,普通会话保持 DSH 原样。
|
|
140
140
|
|
|
141
141
|
### 无 Sidebar 功能边界
|
|
142
142
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 首页统一采用 DSH-UX-001 v1.5.4
|
|
2
|
+
|
|
3
|
+
日期:2026-09-14。依据用户五张首页截图及共享规范 `/Users/qcc/Documents/DuHu/QCC/beichacha_doc/云聚接口/MCP/AI-设计/DSH智能体开发交互规范方案.md`。
|
|
4
|
+
|
|
5
|
+
基线:远端 main 已只读核验为 fe799ec532ba323828a44ad7c3e6ea9985adc7a1(0.1.26)。本轮新补丁版本为 0.1.27,禁止复用基线 npm 版本。
|
|
6
|
+
|
|
7
|
+
- 删除首页用途副标题及 CSS,不删除产品 description、向导说明和按需错误提示。
|
|
8
|
+
- 大标题保持“访前尽调智能体”,左侧入口固定“访前尽调”,沿用既有 busy 禁用和重复点击保护。
|
|
9
|
+
- 标题桥接仅识别宿主默认标题或本产品标记,不覆盖自定义文字;祖先查找及 observer 不扩展至 body/html。监听 data-phase,离开 Hero 恢复自有修改,保留新版 blank/awaitingFirstTurn 兼容。
|
|
10
|
+
- 保留既有 processingStatus 的运行、待确认、已停止未完成、同步异常区分;不新增首页状态副标题,不将倒计时或旧查询记录当运行证据。
|
|
11
|
+
- 未改变 Session 持久化标题、草稿、历史/导出、付费工具授权或可选侧栏行为。
|
|
12
|
+
|
|
13
|
+
验证:Node 24.19,pnpm check 通过(175测试、typecheck、build)。页面 fixture 增加原生工作区/模式控件顺序及零副标题断言。最终页面回归结果见 `_scratch/ui-layout/results.json` 与 `/tmp/previsit-home154-ui.log`;这是 React + 合成宿主 DOM 的 Chromium 验证,不等于真实 DSH 最终包及四产品共存验收。
|
|
14
|
+
|
|
15
|
+
本轮隔离工作树 `/Users/qcc/Documents/Codex/dsh-previsit-home-20260914`;未修改生产 ~/.dsh、开发验证阶段未操作生产;用户随后授权按PR/main CI/新Tag/OIDC流程发布。真正 Host 的 DOM 重挂载、跨四业务切换、HMR 与最终安装包仍需独立验收。
|
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.28`,发布后核验 `latest=0.1.28` |
|
|
11
|
+
| Release | `https://github.com/duhu2000/dsh-pre-duediligence/releases/tag/v0.1.28` |
|
|
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.28 保持显式 TaskScope 与旧记录只读边界,并补齐稳定菜单名、Host 新版首页标题定位及历史详情/报告下载;生产账号端到端复测仍以用户验收为准,市场描述不把待验项写成已通过。
|
|
32
32
|
|
|
33
33
|
## 安装、卸载与截图证据
|
|
34
34
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# 0.1.28 · 任务接纳后自动展开工作台
|
|
2
|
+
|
|
3
|
+
采用 DSH-UX-001 v1.5.5 / UX-48。菜单进入、输入和提示词回填不展开;原生 `beginSubmission.onRetire` 的 `observed` 接纳事件与直接发送成功,共用单次展开控制器。工作台绑定提交所属 Session,不切换或影响其他 Session。
|
|
4
|
+
|
|
5
|
+
失败不展开;重复回调、历史恢复、工具与轮询更新不重新拉开用户收起的工作台。没有可选工作台时,提示原生会话可继续查看进展、无需重复提交。
|
|
6
|
+
|
|
7
|
+
本次保留已发布的 0.1.27 首页修复。回归覆盖现代 Host、旧 Host 延迟节点、去重、失败、切换会话和卸载。隔离浏览器使用合成 Host;真实 DSH 生产同装验收仍需升级并重启后执行,不将合成门禁等同于生产验证。
|
|
8
|
+
|
|
9
|
+
通过精确提交的 main CI 后创建注释 Tag,使用既有 GitHub OIDC 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.28 已获得发布授权;完成仓库 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.28
|
|
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.28}"
|
|
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
|
@@ -977,6 +977,106 @@ function PrevisitFields(props) {
|
|
|
977
977
|
] });
|
|
978
978
|
}
|
|
979
979
|
|
|
980
|
+
// src/submission-reveal.ts
|
|
981
|
+
function installSubmissionReveal(host, reveal) {
|
|
982
|
+
let alive = true;
|
|
983
|
+
let stopFace = () => {
|
|
984
|
+
};
|
|
985
|
+
let attached;
|
|
986
|
+
let current;
|
|
987
|
+
const direct = /* @__PURE__ */ new Map();
|
|
988
|
+
const awaitingLegacy = /* @__PURE__ */ new Map();
|
|
989
|
+
const accept = (sessionId, ticket) => {
|
|
990
|
+
if (ticket.handled) return;
|
|
991
|
+
ticket.handled = true;
|
|
992
|
+
if (!alive || host.sessions.list?.getSnapshot().current !== sessionId || !isPrevisitSession(sessionId)) return;
|
|
993
|
+
try {
|
|
994
|
+
reveal(sessionId);
|
|
995
|
+
} catch {
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
const connect = () => {
|
|
999
|
+
const id = host.sessions.list?.getSnapshot().current;
|
|
1000
|
+
const face = id ? host.sessions.binding?.(id)?.session : void 0;
|
|
1001
|
+
if (id === current && face === attached) return;
|
|
1002
|
+
stopFace();
|
|
1003
|
+
stopFace = () => {
|
|
1004
|
+
};
|
|
1005
|
+
current = id;
|
|
1006
|
+
attached = face;
|
|
1007
|
+
if (!id || !face || !isPrevisitSession(id)) return;
|
|
1008
|
+
const original = face.beginSubmission;
|
|
1009
|
+
if (typeof original === "function") {
|
|
1010
|
+
const wrapped = function(input) {
|
|
1011
|
+
const pending = direct.get(id);
|
|
1012
|
+
const ticket = pending?.text === input.text ? pending : { text: input.text, handled: false };
|
|
1013
|
+
return original.call(this, { ...input, onRetire(result) {
|
|
1014
|
+
try {
|
|
1015
|
+
input.onRetire?.(result);
|
|
1016
|
+
} finally {
|
|
1017
|
+
if (result.reason === "observed") accept(id, ticket);
|
|
1018
|
+
}
|
|
1019
|
+
} });
|
|
1020
|
+
};
|
|
1021
|
+
face.beginSubmission = wrapped;
|
|
1022
|
+
stopFace = () => {
|
|
1023
|
+
if (face.beginSubmission === wrapped) face.beginSubmission = original;
|
|
1024
|
+
};
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
let previous = face.getSnapshot();
|
|
1028
|
+
let baseline = previous.nodes?.length ?? 0;
|
|
1029
|
+
let highest = Math.max(0, ...(previous.nodes ?? []).map((node) => node.seq ?? 0));
|
|
1030
|
+
stopFace = face.subscribe(() => {
|
|
1031
|
+
const next = face.getSnapshot();
|
|
1032
|
+
const nodes = next.nodes ?? [];
|
|
1033
|
+
const added = highest ? nodes.filter((node) => (node.seq ?? 0) > highest) : nodes.slice(baseline);
|
|
1034
|
+
highest = Math.max(highest, ...nodes.map((node) => node.seq ?? 0));
|
|
1035
|
+
baseline = nodes.length;
|
|
1036
|
+
const restoring = previous.openState !== void 0 && previous.openState !== "open" || next.loadingOlder;
|
|
1037
|
+
previous = next;
|
|
1038
|
+
if (restoring) return;
|
|
1039
|
+
for (const node of added) {
|
|
1040
|
+
if (node.kind === "user" || node.role === "user" || node.message?.role === "user") {
|
|
1041
|
+
const content = node.message?.content ?? node.content ?? node.parts;
|
|
1042
|
+
const text = node.text ?? (typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => typeof part === "string" ? part : part?.text ?? "").join("\n") : "");
|
|
1043
|
+
const pending = awaitingLegacy.get(id) ?? [];
|
|
1044
|
+
const index = pending.findIndex((ticket2) => ticket2.text === text);
|
|
1045
|
+
const ticket = index >= 0 ? pending.splice(index, 1)[0] : direct.get(id) ?? { text, handled: false };
|
|
1046
|
+
ticket.observed = true;
|
|
1047
|
+
accept(id, ticket);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
};
|
|
1052
|
+
connect();
|
|
1053
|
+
const stopList = host.sessions.list?.subscribe?.(connect);
|
|
1054
|
+
connect();
|
|
1055
|
+
return {
|
|
1056
|
+
async submit(sessionId, text, send) {
|
|
1057
|
+
const ticket = { text, handled: false };
|
|
1058
|
+
direct.set(sessionId, ticket);
|
|
1059
|
+
try {
|
|
1060
|
+
const result = await send();
|
|
1061
|
+
if (result === false) throw new Error("\u4EFB\u52A1\u672A\u88AB\u63A5\u7EB3\uFF0C\u8BF7\u91CD\u8BD5");
|
|
1062
|
+
accept(sessionId, ticket);
|
|
1063
|
+
if (!ticket.observed && typeof host.sessions.binding?.(sessionId)?.session.beginSubmission !== "function") {
|
|
1064
|
+
awaitingLegacy.set(sessionId, [...awaitingLegacy.get(sessionId) ?? [], ticket]);
|
|
1065
|
+
}
|
|
1066
|
+
} finally {
|
|
1067
|
+
if (direct.get(sessionId) === ticket) direct.delete(sessionId);
|
|
1068
|
+
}
|
|
1069
|
+
},
|
|
1070
|
+
dispose() {
|
|
1071
|
+
alive = false;
|
|
1072
|
+
stopList?.();
|
|
1073
|
+
stopFace();
|
|
1074
|
+
direct.clear();
|
|
1075
|
+
awaitingLegacy.clear();
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
|
|
980
1080
|
// src/tool-outcome.ts
|
|
981
1081
|
var TOOL_OUTCOME_LABELS = {
|
|
982
1082
|
running: "\u67E5\u8BE2\u4E2D",
|
|
@@ -1478,7 +1578,6 @@ var import_react5 = require("react");
|
|
|
1478
1578
|
var import_react_dom4 = require("react-dom");
|
|
1479
1579
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1480
1580
|
var PREVISIT_HOME_TITLE = "\u8BBF\u524D\u5C3D\u8C03\u667A\u80FD\u4F53";
|
|
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";
|
|
1482
1581
|
var PREVISIT_HOME_FLOWS = [
|
|
1483
1582
|
{ view: "target", label: "\u5BF9\u8C61\u4E0E\u76EE\u6807", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
1484
1583
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "10.5", cy: "10.5", r: "5.5" }),
|
|
@@ -1547,15 +1646,17 @@ function resolvePrevisitHeroChrome(anchor) {
|
|
|
1547
1646
|
if (phaseRoot?.getAttribute?.("data-phase") === "active") return null;
|
|
1548
1647
|
const direct = anchor.closest('[data-phase="hero"]');
|
|
1549
1648
|
const findTitle = (scope2) => {
|
|
1649
|
+
const eligible = (node) => ["\u63A2\u7D22\u672A\u81F3\u4E4B\u5883", "Into the Unknown"].includes(node.textContent?.trim() ?? "") || node.dataset.previsitHeroTitle === "true" && node.textContent === PREVISIT_HOME_TITLE;
|
|
1550
1650
|
const spans = [...scope2.querySelectorAll("span")];
|
|
1551
|
-
|
|
1651
|
+
const title = spans.find(eligible) ?? scope2.querySelector('[class*="headlineText"]');
|
|
1652
|
+
return title !== null && title !== void 0 && eligible(title) ? title : null;
|
|
1552
1653
|
};
|
|
1553
1654
|
if (direct !== null) {
|
|
1554
1655
|
const title = findTitle(direct);
|
|
1555
1656
|
if (title !== null) return { scope: direct, title };
|
|
1556
1657
|
}
|
|
1557
1658
|
let scope = anchor.closest("[data-composer-seat]") ?? anchor.parentElement;
|
|
1558
|
-
while (scope !== null) {
|
|
1659
|
+
while (scope !== null && scope.tagName !== "BODY" && scope.tagName !== "HTML") {
|
|
1559
1660
|
const title = findTitle(scope);
|
|
1560
1661
|
if (title !== null) return { scope, title };
|
|
1561
1662
|
scope = scope.parentElement;
|
|
@@ -1569,9 +1670,33 @@ function setPrevisitHeadline(anchor) {
|
|
|
1569
1670
|
const originalRows = /* @__PURE__ */ new Map();
|
|
1570
1671
|
const logos = /* @__PURE__ */ new Map();
|
|
1571
1672
|
let observer = null;
|
|
1673
|
+
const restore = () => {
|
|
1674
|
+
for (const logo of logos.values()) logo.remove();
|
|
1675
|
+
for (const [mark, display] of originalMarks) mark.style.display = display;
|
|
1676
|
+
for (const [badge, display] of originalBadges) {
|
|
1677
|
+
badge.style.display = display;
|
|
1678
|
+
delete badge.dataset.previsitHeroBadge;
|
|
1679
|
+
}
|
|
1680
|
+
for (const [row, flag] of originalRows) {
|
|
1681
|
+
if (flag === null) row.removeAttribute("data-previsit-hero-row");
|
|
1682
|
+
else row.setAttribute("data-previsit-hero-row", flag);
|
|
1683
|
+
}
|
|
1684
|
+
for (const [title, text] of originalTitles) {
|
|
1685
|
+
if (title.textContent === PREVISIT_HOME_TITLE) title.textContent = text;
|
|
1686
|
+
delete title.dataset.previsitHeroTitle;
|
|
1687
|
+
}
|
|
1688
|
+
originalTitles.clear();
|
|
1689
|
+
originalMarks.clear();
|
|
1690
|
+
originalBadges.clear();
|
|
1691
|
+
originalRows.clear();
|
|
1692
|
+
logos.clear();
|
|
1693
|
+
};
|
|
1572
1694
|
const sync = () => {
|
|
1573
1695
|
const chrome = resolvePrevisitHeroChrome(anchor);
|
|
1574
|
-
if (chrome === null)
|
|
1696
|
+
if (chrome === null) {
|
|
1697
|
+
restore();
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1575
1700
|
const { scope, title } = chrome;
|
|
1576
1701
|
if (!originalTitles.has(title)) originalTitles.set(title, title.textContent);
|
|
1577
1702
|
title.dataset.previsitHeroTitle = "true";
|
|
@@ -1601,25 +1726,12 @@ function setPrevisitHeadline(anchor) {
|
|
|
1601
1726
|
}
|
|
1602
1727
|
};
|
|
1603
1728
|
sync();
|
|
1604
|
-
const observationRoot = resolvePrevisitHeroChrome(anchor)?.scope ?? (
|
|
1729
|
+
const observationRoot = resolvePrevisitHeroChrome(anchor)?.scope ?? anchor.closest("[data-phase]") ?? anchor.closest("[data-composer-seat]")?.parentElement ?? anchor.parentElement;
|
|
1605
1730
|
observer = typeof MutationObserver === "function" ? new MutationObserver(sync) : null;
|
|
1606
|
-
if (observationRoot !== null) observer?.observe(observationRoot, { childList: true, subtree: true });
|
|
1731
|
+
if (observationRoot !== null && observationRoot.tagName !== "BODY" && observationRoot.tagName !== "HTML") observer?.observe(observationRoot, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-phase"] });
|
|
1607
1732
|
return () => {
|
|
1608
1733
|
observer?.disconnect();
|
|
1609
|
-
|
|
1610
|
-
for (const [mark, display] of originalMarks) mark.style.display = display;
|
|
1611
|
-
for (const [badge, display] of originalBadges) {
|
|
1612
|
-
badge.style.display = display;
|
|
1613
|
-
delete badge.dataset.previsitHeroBadge;
|
|
1614
|
-
}
|
|
1615
|
-
for (const [row, flag] of originalRows) {
|
|
1616
|
-
if (flag === null) row.removeAttribute("data-previsit-hero-row");
|
|
1617
|
-
else row.setAttribute("data-previsit-hero-row", flag);
|
|
1618
|
-
}
|
|
1619
|
-
for (const [title, text] of originalTitles) {
|
|
1620
|
-
if (title.textContent === PREVISIT_HOME_TITLE) title.textContent = text;
|
|
1621
|
-
delete title.dataset.previsitHeroTitle;
|
|
1622
|
-
}
|
|
1734
|
+
restore();
|
|
1623
1735
|
};
|
|
1624
1736
|
}
|
|
1625
1737
|
function PrevisitHome({ sessionId, useSession, openWorkbench: openWorkbench2 }) {
|
|
@@ -1650,7 +1762,6 @@ function PrevisitHome({ sessionId, useSession, openWorkbench: openWorkbench2 })
|
|
|
1650
1762
|
} });
|
|
1651
1763
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: marker, className: `qccPrevisitExperience${blank ? " is-home" : ""}`, "data-session-id": sessionId, children: [
|
|
1652
1764
|
menuMount === null ? menu : (0, import_react_dom4.createPortal)(menu, menuMount),
|
|
1653
|
-
blank ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "qccPrevisitHomeSummary", children: PREVISIT_HOME_SUMMARY }) : null,
|
|
1654
1765
|
error === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { role: "status", children: error })
|
|
1655
1766
|
] });
|
|
1656
1767
|
}
|
|
@@ -1878,7 +1989,6 @@ var WORKBENCH_CSS = String.raw`
|
|
|
1878
1989
|
.qccPrevisitHeroLogo{display:inline-flex;color:#128BED;flex:none}
|
|
1879
1990
|
html[data-theme="dark"] .qccPrevisitHeroLogo,html.dark .qccPrevisitHeroLogo{color:#55ADFF}
|
|
1880
1991
|
.qccPrevisitExperience{width:100%;box-sizing:border-box;text-align:center}
|
|
1881
|
-
.qccPrevisitHomeSummary{max-width:620px;margin:0 auto 18px;color:var(--qcc-secondary);font-size:14px;line-height:1.7}
|
|
1882
1992
|
.qccPrevisitCapabilityMount{width:100%;padding:8px 0;box-sizing:border-box;flex:none}
|
|
1883
1993
|
.qccPrevisitCapabilities{display:flex;align-items:center;justify-content:safe center;gap:8px;width:100%;max-width:var(--dsh-composer-card-max-width,780px);margin:0 auto;padding:2px 16px 0;box-sizing:border-box;overflow-x:auto;scrollbar-width:none}
|
|
1884
1994
|
.qccPrevisitCapabilities::-webkit-scrollbar{display:none}
|
|
@@ -3123,9 +3233,11 @@ function apply(ctx) {
|
|
|
3123
3233
|
let active = true;
|
|
3124
3234
|
const shared = createPrevisitStore();
|
|
3125
3235
|
const reveal = createRevealController();
|
|
3236
|
+
let unavailableNotice;
|
|
3126
3237
|
ctx.effect(() => () => {
|
|
3127
3238
|
active = false;
|
|
3128
3239
|
reveal.dispose();
|
|
3240
|
+
unavailableNotice?.remove();
|
|
3129
3241
|
});
|
|
3130
3242
|
const startPrompt = async (sessionId, prompt) => {
|
|
3131
3243
|
if (!active || !isPrevisitSession(sessionId)) throw new Error("\u8BBF\u524D\u4F1A\u8BDD\u4E0D\u53EF\u7528");
|
|
@@ -3134,7 +3246,7 @@ function apply(ctx) {
|
|
|
3134
3246
|
const conversation = ctx.sessions.scope?.(sessionId)?.get("conversation");
|
|
3135
3247
|
if (conversation === void 0) throw new Error("conversation unavailable");
|
|
3136
3248
|
const baseline = ctx.sessions.binding?.(sessionId)?.session.getSnapshot().nodes?.length ?? 0;
|
|
3137
|
-
await conversation.send(prompt);
|
|
3249
|
+
await submissions.submit(sessionId, prompt, () => conversation.send(prompt));
|
|
3138
3250
|
if (active && draft !== void 0) clearSubmittedDraft(input, draft);
|
|
3139
3251
|
return baseline;
|
|
3140
3252
|
};
|
|
@@ -3144,6 +3256,27 @@ function apply(ctx) {
|
|
|
3144
3256
|
if (!openWorkbench(service, { sessionId }, reveal)) throw new Error("\u8BBF\u524D\u5DE5\u4F5C\u53F0\u5DF2\u5728 Sidebar \u8BBE\u7F6E\u4E2D\u7981\u7528\uFF1B\u5F53\u524D\u8349\u7A3F\u548C\u4E1A\u52A1\u72B6\u6001\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u542F\u7528\u540E\u91CD\u8BD5\u3002");
|
|
3145
3257
|
if (view !== void 0) locatePrevisitView(shared, sessionId, view);
|
|
3146
3258
|
};
|
|
3259
|
+
const submissions = installSubmissionReveal(ctx, (sessionId) => {
|
|
3260
|
+
try {
|
|
3261
|
+
openForSession(sessionId, "collect");
|
|
3262
|
+
} catch (cause) {
|
|
3263
|
+
unavailableNotice?.remove();
|
|
3264
|
+
const notice = document.createElement("div");
|
|
3265
|
+
notice.setAttribute("role", "status");
|
|
3266
|
+
notice.style.cssText = "position:fixed;right:20px;bottom:20px;z-index:9999;max-width:420px;padding:16px;border:1px solid #94a3b8;border-radius:12px;background:#fff;color:#334155;box-shadow:0 4px 20px #0002";
|
|
3267
|
+
const detail = cause instanceof Error ? cause.message : "\u5DE5\u4F5C\u53F0\u6682\u4E0D\u53EF\u7528";
|
|
3268
|
+
const message = document.createElement("p");
|
|
3269
|
+
message.textContent = `\u4EFB\u52A1\u5DF2\u63D0\u4EA4\uFF0C\u4F46\u53F3\u4FA7\u5DE5\u4F5C\u53F0\u672A\u5C55\u5F00\u3002${detail} \u8BF7\u5728\u539F\u751F\u4F1A\u8BDD\u4E2D\u7EE7\u7EED\u67E5\u770B\u8FDB\u5C55\uFF0C\u65E0\u9700\u91CD\u590D\u63D0\u4EA4\u3002`;
|
|
3270
|
+
const dismiss = document.createElement("button");
|
|
3271
|
+
dismiss.type = "button";
|
|
3272
|
+
dismiss.textContent = "\u77E5\u9053\u4E86";
|
|
3273
|
+
dismiss.addEventListener("click", () => notice.remove());
|
|
3274
|
+
notice.append(message, dismiss);
|
|
3275
|
+
document.body.append(notice);
|
|
3276
|
+
unavailableNotice = notice;
|
|
3277
|
+
}
|
|
3278
|
+
});
|
|
3279
|
+
ctx.effect(() => () => submissions.dispose(), "dsh-pre-duediligence: accepted submission reveal");
|
|
3147
3280
|
ctx.effect(() => installStyles(), "dsh-pre-duediligence: QCC blue UI styles");
|
|
3148
3281
|
ctx.effect(
|
|
3149
3282
|
() => installOrdinarySessionGuard(ctx, ctx.workspaces ?? {}),
|
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.28",
|
|
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.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"],
|
|
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.28\",\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,7 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import type { PrevisitView } from "./previsit-store.js";
|
|
3
3
|
export declare const PREVISIT_HOME_TITLE = "\u8BBF\u524D\u5C3D\u8C03\u667A\u80FD\u4F53";
|
|
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
4
|
export type PrevisitHomeProps = {
|
|
6
5
|
sessionId: string;
|
|
7
6
|
useSession<T>(selector: (state: {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type Submission = {
|
|
2
|
+
text: string;
|
|
3
|
+
onRetire?: (result: {
|
|
4
|
+
reason: string;
|
|
5
|
+
}) => void;
|
|
6
|
+
};
|
|
7
|
+
type Face = {
|
|
8
|
+
beginSubmission?: (input: Submission) => unknown;
|
|
9
|
+
getSnapshot(): unknown;
|
|
10
|
+
subscribe(listener: () => void): () => void;
|
|
11
|
+
};
|
|
12
|
+
type Host = {
|
|
13
|
+
sessions: {
|
|
14
|
+
list?: {
|
|
15
|
+
getSnapshot(): {
|
|
16
|
+
current?: string;
|
|
17
|
+
};
|
|
18
|
+
subscribe?(listener: () => void): () => void;
|
|
19
|
+
};
|
|
20
|
+
binding?(id: string): {
|
|
21
|
+
session: Face;
|
|
22
|
+
} | undefined;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/** Observe admission, never input edits, runtime heartbeats or restored history. */
|
|
26
|
+
export declare function installSubmissionReveal(host: Host, reveal: (sessionId: string) => void): {
|
|
27
|
+
submit(sessionId: string, text: string, send: () => Promise<unknown>): Promise<void>;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-pre-duediligence",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
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.28
|
|
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.28)
|
|
58
58
|
|
|
59
59
|
只在从“访前尽调”入口创建的专属 Session 内执行。首先调用 `previsit_begin`,传入用户提供的企业检索词和 fast / standard / deep 档位;提示中含“访前任务 ID:PV-...”时,必须原样作为 `requestId` 传入。用户发送任务已经构成本任务在固定业务路由内连续执行的明确授权,禁止再询问是否允许调用企查查 MCP,也不要弹出调用次数或额度二次确认。插件不设 8 / 18 / 40 次硬上限。
|
|
60
60
|
|