dsh-pre-duediligence 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # 更新日志
2
2
  ## [Unreleased]
3
3
 
4
+ ## [0.1.22] - 2026-09-13
5
+
6
+ - “任务历史”改为汇总当前 DSH Profile 内所有访前尽调 Session 的 Host 持久记录,重新打开插件或进入新会话后仍可查看既有任务。
7
+ - “当前任务”继续严格按当前 Session 隔离;历史卡片补充来源 Workspace / Session,完整对话和证据引用仍保留在原生会话中。
8
+
4
9
  ## [0.1.21] - 2026-09-13
5
10
 
6
11
  - 资料采集与证据核验新增实时执行卡,显示当前查询维度、真实查询/闭环/无数据/无需执行/失败数量、最近完成项与累计耗时;查询间隙明确提示正在研判整理,不使用虚构百分比。
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # dsh-pre-duediligence
2
2
 
3
- 0.1.21 补齐长任务的可见进度与状态解释:资料采集和证据核验持续显示当前查询、真实查询/闭环数量、无需执行项及耗时;同一业务维度只采用最新运行,旧待处理记录不再误染黄色;流程待复核改用中性色,经营状态明确区分当前研判、非当前研判、未形成结论与待研判。已发布版本与验证边界见 [发布说明](docs/RELEASE-0.1.21.md)、[采用记录](docs/DSH-UX-001-ADOPTION.md)和[兼容矩阵](docs/COMPATIBILITY.md)。
3
+ 0.1.22 修复重新进入访前尽调后历史为空的问题:任务历史汇总当前 DSH Profile 内所有访前尽调 Session 的 Host 持久记录,并标注来源 Workspace / Session;当前任务与执行进度仍严格绑定当前 Session,不会误认领旧任务。已发布版本与验证边界见 [发布说明](docs/RELEASE-0.1.22.md)、[采用记录](docs/DSH-UX-001-ADOPTION.md)和[兼容矩阵](docs/COMPATIBILITY.md)。
4
4
 
5
5
  ## 安装与三分钟上手
6
6
 
7
7
  访前尽调智能体:面向拜访前调查,提供企业尽调、客户尽调、客户背景调查、工商核验与风险信息整理;使用企查查 MCP,提供固定路由连续调用、主体确认、动态进度和可下载报告。
8
8
 
9
9
  ```sh
10
- dsh plugin --profile web add dsh-pre-duediligence@0.1.21
10
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.22
11
11
  ```
12
12
 
13
13
  请先满足下文的 DSH 与连接器要求;Better Sidebar 仅在需要可视化工作台时选装。安装后完整停止并重启对应 Profile。
@@ -23,7 +23,7 @@ dsh plugin --profile web add dsh-pre-duediligence@0.1.21
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.21**。本版保持 DSH-UX-001 v1.5.2 的 Workspace / Session 隔离、工作台初始关闭和核验完成门,并让长任务持续显示当前真实查询、已闭环数量、最近完成项和耗时。相同业务维度按最新运行聚合,已完成或无需执行的结果显示绿色;流程待复核使用中性色,不再与风险预警黄色混同。经营状态为互斥研判结果,每张卡片明确说明当前研判、非当前研判、未形成结论或待研判。本轮实现来自真实界面问题样本,自动化仍使用合成 Provider 数据,不声明生产账号全链路验收通过。详见 [更新日志](CHANGELOG.md)、[采用记录](docs/DSH-UX-001-ADOPTION.md)、[兼容与验收矩阵](docs/COMPATIBILITY.md)和[市场投稿登记](docs/MARKETPLACE.md)。
26
+ 版本:**0.1.22**。本版保持 DSH-UX-001 v1.5.2 的 Workspace / Session 隔离、工作台初始关闭和核验完成门;当前任务、阶段进度、报告回写及下载仍绑定当前 Session,任务历史则从 Host 持久库汇总当前 Profile 的全部访前 Session。每条历史记录显示来源 Workspace / Session,完整消息与证据仍保留在来源原生会话。本轮自动化使用合成 Host 记录,不声明生产账号全链路验收通过。详见 [更新日志](CHANGELOG.md)、[采用记录](docs/DSH-UX-001-ADOPTION.md)、[兼容与验收矩阵](docs/COMPATIBILITY.md)和[市场投稿登记](docs/MARKETPLACE.md)。
27
27
 
28
28
  面向 DeepSeek Harness 的 Session 级访前尽调智能体。业务人员从左侧菜单进入,在会话级工作台定义一次拜访;Agent 调用企查查五类 MCP,使用机会与风险双引擎完成经营状态识别、假设与反证、风险核验,最终交付可追溯的访前尽调报告。
29
29
 
@@ -49,7 +49,8 @@ dsh plugin --profile web add dsh-pre-duediligence@0.1.21
49
49
  - 快捷菜单与阶段导航只切换视图,不伪造执行状态;只有捕获到完整报告后才显示“报告已生成”
50
50
  - Better Sidebar 的 Session 单例 Tab 是唯一工作台容器;展开、收起、宽度、停靠和浮窗只由宿主管理,业务内容不提供同义箭头、X 或返回/关闭工作台按钮
51
51
  - 宿主收起只改变可见性,宿主 Tab X 只移除业务 Tab;两者都不取消任务或删除历史、制品,从输入框下方同一组快捷按钮可聚焦或恢复并定位对应视图
52
- - 进度与工具记录只投影当前 Session 的 Host 任务与真实高层工具调用;异步轮询会动态更新资料采集、证据核验、用量和错误状态
52
+ - 当前任务、进度与工具记录只投影当前 Session 的 Host 任务与真实高层工具调用;异步轮询会动态更新资料采集、证据核验、用量和错误状态
53
+ - 任务历史从 Host 持久库汇总当前 DSH Profile 内所有访前尽调 Session,并标注来源 Workspace / Session;重新打开插件或进入新会话不会隐藏既有记录,也不会把旧任务认领为当前任务
53
54
  - 完整报告保留在 DSH 原生会话中,并通过 `previsit_finalize` 存为 Host 报告制品;如果 Agent 遗漏完成工具,前端会将已捕获的合格八段报告回写 Host
54
55
 
55
56
  ### 点选拼句器
@@ -91,7 +92,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-pre-duediligenc
91
92
  安装脚本默认向 DSH Web profile 安装两个基础 Bundle:
92
93
 
93
94
  1. `dsh-mcp-connector@0.2.32`:提供通用 MCP 连接器与市场,通过“企查查·企业工商”完成 OAuth,并动态挂载企业、风险、知产、经营、历史和董监高 MCP。
94
- 2. `dsh-pre-duediligence@0.1.21`:提供左侧智能体入口、原生会话首页、提示词生成器、Host 执行门和 Skill。
95
+ 2. `dsh-pre-duediligence@0.1.22`:提供左侧智能体入口、原生会话首页、提示词生成器、Host 执行门和 Skill。
95
96
 
96
97
  默认不会安装 Better Sidebar。需要 Session 级可视化工作台时显式开启:
97
98
 
@@ -119,7 +120,7 @@ dsh web
119
120
 
120
121
  ~~~bash
121
122
  dsh plugin --profile web add dsh-mcp-connector@0.2.32
122
- dsh plugin --profile web add dsh-pre-duediligence@0.1.21 --allow-build=node-pty,dsh-pre-duediligence
123
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.22 --allow-build=node-pty,dsh-pre-duediligence
123
124
  dsh web
124
125
  ~~~
125
126
 
@@ -135,7 +136,7 @@ dsh web
135
136
  dsh plugin --profile web list --depth 0
136
137
  ~~~
137
138
 
138
- 应始终看到所选基线的 Connector 和 `dsh-pre-duediligence@0.1.21`;仅在显式启用或原本已安装时看到 Sidebar。重启 DSH 并连接“企查查·企业工商”后,在左侧菜单点击“访前尽调”进入初始原生会话。无 Sidebar 时可继续生成提示词、回填/发送草稿并在会话中阅读报告;五项流程按钮会提示安装可选工作台。安装 Sidebar 时,右侧工作台仍默认关闭,点击业务按钮后才打开对应视图;其右侧“+”菜单不会列出该入口,普通会话保持 DSH 原样。
139
+ 应始终看到所选基线的 Connector 和 `dsh-pre-duediligence@0.1.22`;仅在显式启用或原本已安装时看到 Sidebar。重启 DSH 并连接“企查查·企业工商”后,在左侧菜单点击“访前尽调”进入初始原生会话。无 Sidebar 时可继续生成提示词、回填/发送草稿并在会话中阅读报告;五项流程按钮会提示安装可选工作台。安装 Sidebar 时,右侧工作台仍默认关闭,点击业务按钮后才打开对应视图;其右侧“+”菜单不会列出该入口,普通会话保持 DSH 原样。
139
140
 
140
141
  ### 无 Sidebar 功能边界
141
142
 
@@ -1,5 +1,14 @@
1
1
  # 访前尽调兼容与验收记录
2
2
 
3
+ ## 0.1.22:跨 Session 历史汇总(2026-09-13)
4
+
5
+ | 实测问题 | 修复契约 | 验证边界 |
6
+ | --- | --- | --- |
7
+ | 重新进入访前尽调后历史为空 | 历史视图不再附带当前 `sessionId` 过滤,读取当前 DSH Profile 的全部 Host 访前任务 | 只汇总本 Profile 的任务元数据和制品,不跨 Profile,也不复制原生会话正文 |
8
+ | 历史汇总可能污染当前任务 | 当前任务发现、进度、报告回写和下载仍严格携带当前 `sessionId` | 历史卡片仅供查看并标注来源 Workspace / Session,不会重新认领旧任务 |
9
+
10
+ 本地 Node 24.18.0 发布门禁通过严格类型检查、23 个测试文件 / 163 项测试、Host/Client 构建、浅色/深色 × 桌面/窄屏 4 个隔离 Chrome 场景及 npm `0.1.22` 包清单 dry-run。现有 Host 持久记录会恢复展示,但本版不会凭空恢复从未写入 Host 的旧版浏览器瞬时状态。
11
+
3
12
  ## 0.1.21:长任务反馈与最新运行聚合(2026-09-13)
4
13
 
5
14
  | 实测问题 | 修复契约 | 验证边界 |
@@ -163,7 +172,7 @@ Node:22.19+(22 LTS)或 24 LTS。CI 使用四组有效配对:两个 Node
163
172
  - 草稿仅通过宿主会话输入机读写。发送后只清除原会话中仍等于已提交文本的草稿;不使用 document-wide textarea 或延迟 setter。
164
173
  - 报告来自已认领任务后的助手快照,支持真实 DSH 的 `blocks[kind=text]`,忽略 reasoning、context、interrupted 前缀;不从页面 DOM 抓取报告。输出结构按完整报告/转发摘要分别校验。
165
174
  - Native composer 的归属来自专属 Session ID;工具名称不作为业务归属。窗口索引变化导致任务定位失败时不导出旧报告,需重新打开/恢复原生会话核对。
166
- - 当前任务历史仍以 DSH 会话为准;清空和“新的尽调”的本地视图状态不承诺跨重启恢复。
175
+ - 当前任务和进度仍按 Session 隔离;任务历史读取 Host 持久库并跨访前尽调 Session 汇总。清空和“新的尽调”只重置本地当前视图,不删除 Host 历史或制品。
167
176
  - Host 任务授权按 Agent / Session / cwd 绑定;批准记录由 DSH approval service 持久记录。内存预算授权不跨重启复用,恢复查询必须重新确认。
168
177
  - 高层业务工具检查真实注册 schema,Provider 缺失或参数契约不匹配时未执行。主体确认要求同一条结构化搜索记录中的全称和信用代码;非结构化搜索响应暂不适配,禁止猜测。
169
178
  - 风险明细只接受扫描结构中对应 dimension / 原始工具名下明确的非负整数计数(数字或 count);其它 Provider 扫描结构需真链路采样、脱敏契约补齐后支持。未知计数不解释成零。
@@ -1,5 +1,15 @@
1
1
  # DSH-UX-001 采纳与验收记录
2
2
 
3
+ ## 0.1.22:跨 Session 历史汇总
4
+
5
+ 日期:2026-09-13。
6
+
7
+ | 交互要求 | 当前实现 |
8
+ | --- | --- |
9
+ | 重新打开插件后仍能查看历史任务 | “任务历史”读取当前 DSH Profile 内全部访前尽调 Host 记录,不再只筛选当前 Session |
10
+ | 历史与当前任务不得混淆 | 当前任务、阶段进度、报告回写和下载仍严格绑定当前 Session;历史卡片显示来源 Workspace / Session |
11
+ | 原生对话归属必须可追溯 | 历史只汇总任务状态与报告制品,完整消息和证据引用仍保留在来源 DSH 原生会话中 |
12
+
3
13
  ## 0.1.21:真实进度与状态语义分层
4
14
 
5
15
  日期:2026-09-13。
@@ -157,7 +167,7 @@
157
167
  | 中央首页 | 标题“访前尽调一页纸智能体”,同排建筑 LOGO,独立说明行 | 只在插件创建的空白 Session 可逆改写;卸载时恢复宿主标题、图标和预览标识 |
158
168
  | 原生输入区 | Composer 下方五项数据清洗补全式描边卡片快捷菜单(上图标、下短标签);左上角“提示词生成” | 不替换输入框、模型、权限和原生发送按钮;窄屏只在菜单内部横向滚动,不撑宽页面 |
159
169
  | 提示词向导 | 对象 → 角色场景 → 范围深度 → 确认输出 | Portal 到 body;回填不发送;已有草稿显式选择替换、追加或取消 |
160
- | 右侧工作台 | 当前任务 / 任务历史;招投标式五步等宽导航(上图标、下短标题、无描述);由输入框下方快捷按钮打开和恢复 | Better Sidebar 是唯一权威工作台;关闭不取消会话任务;不另设会话头打开入口;窄屏菜单不横向越界 |
170
+ | 右侧工作台 | 当前任务 / 任务历史;当前任务按 Session 隔离,历史汇总本 Profile 全部访前 Session;招投标式五步等宽导航(上图标、下短标题、无描述);由输入框下方快捷按钮打开和恢复 | Better Sidebar 是唯一权威工作台;关闭不取消会话任务;历史卡片标注来源 Workspace / Session;不另设会话头打开入口;窄屏菜单不横向越界 |
161
171
  | 下载材料 | 自包含 HTML 一页纸报告 | 只使用已捕获的当前 Session 报告,不跨 Session 合并 |
162
172
 
163
173
  ## 菜单与阶段映射
@@ -192,7 +202,7 @@
192
202
 
193
203
  - 依赖 DSH `>=0.1.1-rc.2` 与 `dsh-better-sidebar@0.17.1` 的公开能力。
194
204
  - 首页标题因宿主暂未提供会话级 headline slot,采用已知 `[data-phase="hero"]` / headline DOM 的局部、可逆兼容桥;未知结构下保留宿主原样。
195
- - 本轮不新增跨会话持久化历史库;“任务历史”只展示当前 Session 已认领任务,完整历史以 DSH 原生会话为准。
205
+ - 任务历史复用现有 Host 持久库,汇总当前 DSH Profile 内所有访前尽调 Session;不另建浏览器历史库。完整消息和证据引用仍以来源 DSH 原生会话为准。
196
206
  - 尚未在所有未来 DSH 版本和所有第三方插件组合中完成实机视觉验收,兼容范围不外推。
197
207
 
198
208
  ## 自动化证据
@@ -7,8 +7,8 @@
7
7
  | 项目 | 已核验值 |
8
8
  | --- | --- |
9
9
  | 仓库 | `https://github.com/duhu2000/dsh-pre-duediligence`,公开、未归档 |
10
- | npm | `dsh-pre-duediligence@0.1.21`,发布后核验 `latest=0.1.21` |
11
- | Release | `https://github.com/duhu2000/dsh-pre-duediligence/releases/tag/v0.1.21` |
10
+ | npm | `dsh-pre-duediligence@0.1.22`,发布后核验 `latest=0.1.22` |
11
+ | Release | `https://github.com/duhu2000/dsh-pre-duediligence/releases/tag/v0.1.22` |
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.21 已补齐长任务实时反馈、最新运行聚合、流程/风险颜色分层和互斥经营状态说明,并完成自动化契约、构建和隔离 UI 回归;生产账号端到端复测仍以用户验收为准,市场描述不把待验项写成已通过。
31
+ - 0.1.22 已补齐跨访前 Session 的 Host 历史汇总,同时保持当前任务严格按 Session 隔离,并完成自动化契约、构建和隔离 UI 回归;生产账号端到端复测仍以用户验收为准,市场描述不把待验项写成已通过。
32
32
 
33
33
  ## 安装、卸载与截图证据
34
34
 
@@ -0,0 +1,26 @@
1
+ # dsh-pre-duediligence 0.1.22
2
+
3
+ Version: **0.1.22**
4
+ Date: **2026-09-13**
5
+
6
+ ## 本次修复
7
+
8
+ - “任务历史”不再被当前 `sessionId` 过滤,改为汇总当前 DSH Profile 内全部访前尽调 Session 的 Host 持久记录。
9
+ - 每条历史卡片显示来源 Workspace / Session,便于回到对应原生会话核对完整消息和证据引用。
10
+ - “当前任务”、阶段进度、报告回写及下载继续严格携带当前 `sessionId`,历史汇总不会把旧任务重新认领为当前任务。
11
+ - 空状态文案明确为 Profile 级历史;重新打开插件或进入新的访前会话,不会再隐藏已经写入 Host 的历史记录。
12
+
13
+ ## 验证与边界
14
+
15
+ - Node 24.18.0 下严格类型检查、23 个测试文件 / 163 项测试、Host/Client 构建、浅色/深色 × 桌面/窄屏 4 个隔离 Chrome 场景和 npm 包清单 dry-run 通过。
16
+ - Host API 回归同时覆盖带 `sessionId` 的当前任务查询和不带 `sessionId` 的全历史查询,确认两种作用域互不混淆。
17
+ - 本版复用既有 Host 持久库,不创建浏览器历史副本;可恢复已经写入 Host 的记录,但不能恢复从未持久化的旧版瞬时 UI 状态。
18
+ - 历史范围限定在当前 DSH Profile,不跨 Profile 汇总,也不复制来源会话的完整对话正文。
19
+
20
+ ## 升级
21
+
22
+ ```sh
23
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.22
24
+ ```
25
+
26
+ 安装后完整停止并重启对应 DSH Web Profile。Better Sidebar 仍为可选工作台;stable 与 candidate 必须使用 README 列出的成套版本。
@@ -12,14 +12,14 @@ npm keywords 已在包清单内更新。GitHub description 建议使用上述英
12
12
 
13
13
  验收:使用实时完整目录和原版搜索函数对比登记描述更新前后;记录语言、命中位置、结果数与版本。独立目录缺失条目必须标为未上线,不能把模拟添加的结果当作上线结果。
14
14
 
15
- 发布清单:0.1.21 已获得发布授权;完成仓库 check、README 与包清单差异审核后,以新的不可变 tag 和 npm 版本发布,不覆盖旧版本或移动旧 tag。仅登记 YAML 更新仍无需重复发布 npm。
15
+ 发布清单:0.1.22 已获得发布授权;完成仓库 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.21
22
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.22
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.21}"
8
+ readonly PREVISIT_SPEC="${DSH_PRE_DUEDILIGENCE_SPEC:-dsh-pre-duediligence@0.1.22}"
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
@@ -1405,6 +1405,22 @@ function PrevisitHome({ sessionId, useSession, openWorkbench: openWorkbench2 })
1405
1405
  ] });
1406
1406
  }
1407
1407
 
1408
+ // src/hosted-task-api.ts
1409
+ async function fetchHostedTask(taskId, sessionId) {
1410
+ const response = await fetch(`/previsit/api/tasks/${encodeURIComponent(taskId)}?sessionId=${encodeURIComponent(sessionId)}`, { headers: { accept: "application/json" } });
1411
+ if (response.status === 404) return null;
1412
+ const payload = await response.json();
1413
+ if (!response.ok || payload.ok === false || payload.task === void 0) throw new Error(payload.message ?? `\u4EFB\u52A1\u72B6\u6001\u8BFB\u53D6\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
1414
+ return payload.task;
1415
+ }
1416
+ async function fetchHostedTasks(sessionId) {
1417
+ const scope = sessionId === void 0 ? "" : `?sessionId=${encodeURIComponent(sessionId)}`;
1418
+ const response = await fetch(`/previsit/api/tasks${scope}`, { headers: { accept: "application/json" } });
1419
+ const payload = await response.json();
1420
+ if (!response.ok || payload.ok === false || !Array.isArray(payload.tasks)) throw new Error(payload.message ?? `\u4EFB\u52A1\u5386\u53F2\u8BFB\u53D6\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
1421
+ return payload.tasks;
1422
+ }
1423
+
1408
1424
  // src/hosted-task-sync.ts
1409
1425
  var HOSTED_TERMINAL = /* @__PURE__ */ new Set(["completed", "partial", "failed"]);
1410
1426
  var VERIFY_RUN = /risk|dishonest|enforcement|terminated|freeze|exception|penalty|tax|judicial|executive/;
@@ -1869,6 +1885,7 @@ html[data-theme="dark"] .qccPrevisitHeroLogo,html.dark .qccPrevisitHeroLogo{colo
1869
1885
  .qccPwSteps{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:0;padding:0;list-style:none}.qccPwStep{display:flex;align-items:flex-start;gap:8px;padding:10px;border:1px solid var(--qcc-border);border-radius:9px;background:var(--qcc-surface)}.qccPwStepDot{display:grid;place-items:center;width:23px;height:23px;border-radius:50%;background:var(--qcc-page);color:var(--qcc-secondary);font-size:10px;flex:none}.qccPwStep[data-state="done"]{border-color:var(--qcc-success)}.qccPwStep[data-state="done"] .qccPwStepDot{background:var(--qcc-success-bg);color:var(--qcc-success)}.qccPwStep[data-state="active"]{border-color:var(--qcc-brand)}.qccPwStep[data-state="active"] .qccPwStepDot{background:var(--qcc-selected);color:var(--qcc-action)}.qccPwStep[data-state="review"]{border-color:var(--qcc-review)}.qccPwStep[data-state="review"] .qccPwStepDot{background:var(--qcc-review-bg);color:var(--qcc-review)}.qccPwStep[data-state="failed"]{border-color:var(--qcc-danger)}.qccPwStep[data-state="failed"] .qccPwStepDot{background:var(--qcc-danger-bg);color:var(--qcc-danger)}.qccPwStepCopy{display:grid;gap:3px}.qccPwStepCopy b{font-size:11px}.qccPwStepCopy small{color:var(--qcc-secondary);font-size:9px}
1870
1886
  .qccPwDims{display:flex;flex-wrap:wrap;gap:7px}.qccPwDim{padding:5px 8px;border:1px solid var(--qcc-border);border-radius:7px;color:var(--qcc-secondary);font-size:11px}.qccPwDim[data-status="done"],.qccPwDim[data-status="no-data"],.qccPwDim[data-status="skipped"]{border-color:var(--qcc-success);background:var(--qcc-success-bg);color:var(--qcc-success)}.qccPwDim[data-status="running"]{border-color:var(--qcc-brand);background:var(--qcc-selected);color:var(--qcc-action)}.qccPwDim[data-status="unknown"],.qccPwDim[data-status="no-permission"],.qccPwDim[data-status="not-executed"]{border-color:var(--qcc-review);background:var(--qcc-review-bg);color:var(--qcc-review)}.qccPwDim[data-status="failed"]{border-color:var(--qcc-danger);background:var(--qcc-danger-bg);color:var(--qcc-danger)}.qccPwDim[data-status="not-executed"]{border-style:dashed}
1871
1887
  .qccPwEmpty,.qccPwNote{margin:0;color:var(--qcc-secondary);font-size:12px;line-height:1.65}.qccPwNote{padding:10px 12px;border-left:3px solid var(--qcc-brand);background:var(--qcc-selected)}
1888
+ .qccPwHistorySource{max-width:min(72vw,680px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1872
1889
  .qccPwStateStrip{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.qccPwState{display:grid;gap:3px;padding:7px;border:1px solid var(--qcc-border);border-radius:7px;background:var(--qcc-surface);color:var(--qcc-secondary);text-align:center}.qccPwState b{font-size:10px;font-weight:600}.qccPwState small{font-size:8px;color:var(--qcc-secondary)}.qccPwState[data-state="pending"]{border-style:dashed}.qccPwState[data-state="selected"]{border-color:var(--qcc-brand);background:var(--qcc-selected);color:var(--qcc-action)}.qccPwState[data-state="selected"] b{font-weight:700}.qccPwState[data-state="selected"] small{color:var(--qcc-action)}.qccPwState[data-state="excluded"]{background:var(--qcc-page)}.qccPwState[data-state="undetermined"]{border-color:var(--qcc-review);background:var(--qcc-review-bg);color:var(--qcc-review)}.qccPwState[data-state="undetermined"] small{color:var(--qcc-review)}
1873
1890
  .qccPwHypos{display:grid;gap:8px;margin:0;padding:0;list-style:none}.qccPwHypos li{display:grid;grid-template-columns:auto auto 1fr;align-items:start;gap:8px;padding:9px;border-radius:8px;background:var(--qcc-page);font-size:11px;line-height:1.55}.qccPwPri{padding:2px 5px;border-radius:5px;background:var(--qcc-table-head);color:var(--qcc-secondary)}.qccPwPri[data-p="P0"]{background:var(--qcc-danger-bg);color:var(--qcc-danger)}.qccPwPri[data-p="P1"]{background:var(--qcc-review-bg);color:var(--qcc-review)}
1874
1891
  .qccPwRiskTiles{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.qccPwRiskTile{padding:11px;border:1px solid var(--qcc-border);border-radius:9px;background:var(--qcc-page)}.qccPwRiskTileTop{display:flex;justify-content:space-between;gap:8px}.qccPwRiskTileTop b,.qccPwRiskTileTop strong{font-size:12px}.qccPwRiskTile p{margin:6px 0 0;color:var(--qcc-secondary);font-size:10px;line-height:1.5}.qccPwRiskTile[data-clear="true"]{border-color:var(--qcc-success);background:var(--qcc-success-bg)}.qccPwRiskTile[data-clear="true"] .qccPwRiskTileTop{color:var(--qcc-success)}.qccPwRiskTile[data-level="红线"]:not([data-empty="true"]){border-color:var(--qcc-danger);background:var(--qcc-danger-bg)}.qccPwRiskTile[data-level="关注"]:not([data-empty="true"]){border-color:var(--qcc-review);background:var(--qcc-review-bg)}.qccPwRiskTile[data-level="信息"]:not([data-empty="true"]){border-color:var(--qcc-brand);background:var(--qcc-selected)}.qccPwRiskBoundary{grid-column:1/-1;margin:0;color:var(--qcc-secondary);font-size:10px;line-height:1.5}
@@ -2101,19 +2118,6 @@ var STATUS_LABELS = {
2101
2118
  ready: "\u62A5\u544A\u5DF2\u751F\u6210",
2102
2119
  failed: "\u9700\u8981\u5904\u7406"
2103
2120
  };
2104
- async function fetchHostedTask(taskId, sessionId) {
2105
- const response = await fetch(`/previsit/api/tasks/${encodeURIComponent(taskId)}?sessionId=${encodeURIComponent(sessionId)}`, { headers: { accept: "application/json" } });
2106
- if (response.status === 404) return null;
2107
- const payload = await response.json();
2108
- if (!response.ok || payload.ok === false || payload.task === void 0) throw new Error(payload.message ?? `\u4EFB\u52A1\u72B6\u6001\u8BFB\u53D6\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
2109
- return payload.task;
2110
- }
2111
- async function fetchHostedHistory(sessionId) {
2112
- const response = await fetch(`/previsit/api/tasks?sessionId=${encodeURIComponent(sessionId)}`, { headers: { accept: "application/json" } });
2113
- const payload = await response.json();
2114
- if (!response.ok || payload.ok === false || !Array.isArray(payload.tasks)) throw new Error(payload.message ?? `\u4EFB\u52A1\u5386\u53F2\u8BFB\u53D6\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
2115
- return payload.tasks;
2116
- }
2117
2121
  var EMPTY_RUNTIME = {
2118
2122
  running: false,
2119
2123
  partial: false,
@@ -2514,7 +2518,7 @@ function HistoryPanel(props) {
2514
2518
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("header", { className: "qccPwPageHeading", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2515
2519
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwEyebrow", children: "HISTORY" }),
2516
2520
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { children: "\u4EFB\u52A1\u5386\u53F2" }),
2517
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "\u4EFB\u52A1\u72B6\u6001\u4E0E\u62A5\u544A\u5236\u54C1\u7531 Host \u4FDD\u5B58\uFF1B\u5B8C\u6574\u6D88\u606F\u548C\u8BC1\u636E\u5F15\u7528\u4ECD\u4FDD\u7559\u5728 DSH \u539F\u751F\u4F1A\u8BDD\u3002" })
2521
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "\u6C47\u603B\u5F53\u524D DSH Profile \u4E2D\u6240\u6709\u8BBF\u524D\u5C3D\u8C03 Session\uFF1B\u4EFB\u52A1\u72B6\u6001\u4E0E\u62A5\u544A\u5236\u54C1\u7531 Host \u4FDD\u5B58\u3002" })
2518
2522
  ] }) }),
2519
2523
  props.hosted.length > 0 ? props.hosted.map((item) => {
2520
2524
  const status = hostedStatus(item);
@@ -2526,6 +2530,12 @@ function HistoryPanel(props) {
2526
2530
  item.id,
2527
2531
  " \xB7 ",
2528
2532
  new Date(item.createdAt).toLocaleString("zh-CN")
2533
+ ] }),
2534
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "qccPwHistorySource", title: `${item.workspace} \xB7 ${item.sessionId}`, children: [
2535
+ "\u6765\u6E90\uFF1A",
2536
+ item.workspace,
2537
+ " \xB7 ",
2538
+ item.sessionId
2529
2539
  ] })
2530
2540
  ] }),
2531
2541
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "qccPwStatus", "data-status": status, children: STATUS_LABELS[status] })
@@ -2539,7 +2549,7 @@ function HistoryPanel(props) {
2539
2549
  item.completedAt === void 0 ? "" : ` \xB7 \u5B8C\u6210\u4E8E ${new Date(item.completedAt).toLocaleString("zh-CN")}`
2540
2550
  ] })
2541
2551
  ] }, item.id);
2542
- }) : props.task === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Feedback, { tone: "notice", title: "\u5F53\u524D\u6CA1\u6709\u5DF2\u8BA4\u9886\u4EFB\u52A1", children: "\u4ECE\u63D0\u793A\u8BCD\u751F\u6210\u5668\u56DE\u586B\u5E76\u53D1\u9001\uFF0C\u6216\u5728\u4F1A\u8BDD\u4E2D\u76F4\u63A5\u53D1\u8D77\u8BBF\u524D\u5C3D\u8C03\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u5F53\u524D\u4EFB\u52A1\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwCard", children: [
2552
+ }) : props.task === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Feedback, { tone: "notice", title: "\u6682\u65E0\u5386\u53F2\u4EFB\u52A1", children: "\u4ECE\u63D0\u793A\u8BCD\u751F\u6210\u5668\u56DE\u586B\u5E76\u53D1\u9001\uFF0C\u6216\u5728\u4EFB\u4E00\u8BBF\u524D\u4F1A\u8BDD\u4E2D\u76F4\u63A5\u53D1\u8D77\u5C3D\u8C03\u540E\uFF0C\u8FD9\u91CC\u4F1A\u6C47\u603B\u663E\u793A\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwCard", children: [
2543
2553
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "qccPwCardHeader", children: [
2544
2554
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2545
2555
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: props.task.id }),
@@ -2549,7 +2559,7 @@ function HistoryPanel(props) {
2549
2559
  ] }),
2550
2560
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { className: "qccPwPrompt", children: props.task.prompt })
2551
2561
  ] }),
2552
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwNote", children: "\u5DE5\u4F5C\u53F0\u4E0D\u521B\u5EFA\u8131\u79BB\u4F1A\u8BDD\u7684\u6D4F\u89C8\u5668\u5386\u53F2\u5E93\uFF0C\u4E5F\u4E0D\u4F1A\u628A\u5176\u5B83 Session \u7684\u4EFB\u52A1\u5408\u5E76\u5230\u8FD9\u91CC\u3002" })
2562
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "qccPwNote", children: "\u8FD9\u91CC\u8DE8\u8BBF\u524D Session \u6C47\u603B Host \u4EFB\u52A1\u8BB0\u5F55\uFF1B\u5B8C\u6574\u5BF9\u8BDD\u548C\u8BC1\u636E\u5F15\u7528\u4ECD\u4FDD\u7559\u5728\u5404\u81EA\u7684 DSH \u539F\u751F\u4F1A\u8BDD\u4E2D\u3002" })
2553
2563
  ] });
2554
2564
  }
2555
2565
  function PrevisitWorkbenchTab(props) {
@@ -2583,7 +2593,7 @@ function PrevisitWorkbenchTab(props) {
2583
2593
  const refresh = async () => {
2584
2594
  try {
2585
2595
  const current = props.shared.get(sessionId);
2586
- const records = await fetchHostedHistory(sessionId);
2596
+ const records = await fetchHostedTasks(sessionId);
2587
2597
  const summary = selectHostedTask(records, current.task, current.dismissedTaskIds);
2588
2598
  const record = summary?.reportReady === true ? await fetchHostedTask(summary.id, sessionId) ?? summary : summary;
2589
2599
  if (disposed) return;
@@ -2620,7 +2630,7 @@ function PrevisitWorkbenchTab(props) {
2620
2630
  let timer;
2621
2631
  const refresh = async () => {
2622
2632
  try {
2623
- const records = await fetchHostedHistory(sessionId);
2633
+ const records = await fetchHostedTasks();
2624
2634
  if (disposed) return;
2625
2635
  setHostedHistory(records);
2626
2636
  setHostError(void 0);
package/lib/index.js CHANGED
@@ -980,7 +980,7 @@ function loadBundledSkill() {
980
980
  },
981
981
  metadata: {
982
982
  author: "QCC",
983
- version: "0.1.21",
983
+ version: "0.1.22",
984
984
  industry: "enterprise-services",
985
985
  mcpServers: ["qcc-company", "qcc-risk", "qcc-ipr", "qcc-operation", "qcc-executive"]
986
986
  }
package/lib/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/skill-file.ts", "../src/previsit-session.ts", "../src/report-export.ts", "../src/previsit-workflow.ts", "../src/previsit-web.ts", "../src/previsit-tools.ts", "../src/tool-outcome.ts"],
4
- "sourcesContent": ["import { readFileSync } from \"node:fs\"\nimport { fileURLToPath } from \"node:url\"\n\nimport { parseSkillFile } from \"./skill-file.js\"\nimport { mountPrevisitWebRoutes, type WebServer } from \"./previsit-web.js\"\nimport { registerPrevisitTools, type ToolHost } from \"./previsit-tools.js\"\nimport { PrevisitWorkflowStore, type StorageDomain } from \"./previsit-workflow.js\"\n\nexport const inject = [\"skills\", \"tools\"] as const\n\nexport const SKILL_NAME = \"qcc-previsit-onepager\"\nexport const SKILL_DESCRIPTION =\n \"\u8C03\u7528\u4F01\u67E5\u67E5\u4E94\u7C7B MCP \u6267\u884C\u4F01\u4E1A\u8BBF\u524D\u5C3D\u8C03\uFF0C\u4EE5\u673A\u4F1A\u4E0E\u98CE\u9669\u53CC\u5F15\u64CE\u8BC6\u522B\u7ECF\u8425\u72B6\u6001\u3001\u5EFA\u7ACB\u5E76\u53CD\u8BC1\u4E1A\u52A1\u5047\u8BBE\uFF0C\u4EA4\u4ED8\u53EF\u8FFD\u6EAF\u7684\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\u3002\"\n\ntype SkillRegistration = {\n name: string\n description: string\n whenToUse: string\n content: string\n source: \"bundled\"\n resourceBase: {\n kind: \"directory\"\n path: string\n }\n metadata: Readonly<Record<string, unknown>>\n}\n\nexport type HostContext = ToolHost & {\n effect(setup: () => void | (() => void)): unknown\n inject?(deps: string[], setup: (ctx: HostContext & { webServer: WebServer; storageDomain: StorageDomain }) => void): unknown\n logger?: { info?(message: string): void; warn?(message: string): void }\n skills: {\n register(skill: SkillRegistration): () => void\n }\n}\n\nexport function loadBundledSkill(): SkillRegistration {\n const skillDirectoryUrl = new URL(\"../skills/qcc-previsit-onepager/\", import.meta.url)\n const skillSource = readFileSync(new URL(\"SKILL.md\", skillDirectoryUrl), \"utf8\")\n const { content } = parseSkillFile(skillSource)\n\n return {\n name: SKILL_NAME,\n description: SKILL_DESCRIPTION,\n whenToUse: \"\u7528\u6237\u8981\u6C42\u51C6\u5907\u5BA2\u6237\u62DC\u8BBF\u3001\u8BBF\u524D\u5C3D\u8C03\u3001\u4E00\u9875\u7EB8\u7B80\u62A5\u3001\u6388\u4FE1\u9762\u8C08\u3001\u5546\u52A1\u8C08\u5224\u3001\u7B7E\u7EA6\u6838\u67E5\u3001\u590D\u8BBF\u66F4\u65B0\u3001\u89E6\u8FBE\u8DEF\u5F84\u6216\u5F53\u9762\u63D0\u95EE\u6E05\u5355\u65F6\u4F7F\u7528\u3002\",\n content,\n source: \"bundled\",\n resourceBase: {\n kind: \"directory\",\n path: fileURLToPath(skillDirectoryUrl),\n },\n metadata: {\n author: \"QCC\",\n version: \"0.1.21\",\n industry: \"enterprise-services\",\n mcpServers: [\"qcc-company\", \"qcc-risk\", \"qcc-ipr\", \"qcc-operation\", \"qcc-executive\"],\n },\n }\n}\n\nexport function apply(ctx: HostContext): void {\n const workflow = new PrevisitWorkflowStore()\n ctx.effect(() => registerPrevisitTools(ctx, workflow))\n ctx.skills.register(loadBundledSkill())\n try {\n ctx.inject?.([\"webServer\", \"storageDomain\"], webCtx => {\n void workflow.attach(webCtx.storageDomain, ctx.logger)\n webCtx.effect(() => mountPrevisitWebRoutes(webCtx.webServer, workflow))\n })\n } catch (error) {\n ctx.logger?.warn?.(`[dsh-pre-duediligence] Host task routes unavailable: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n", "export type ParsedSkillFile = {\n content: string\n frontmatter: string | null\n}\n\nexport function parseSkillFile(source: string): ParsedSkillFile {\n const normalized = source.replaceAll(\"\\r\\n\", \"\\n\")\n\n if (!normalized.startsWith(\"---\\n\")) {\n return { content: normalized.trim(), frontmatter: null }\n }\n\n const closingMarker = normalized.indexOf(\"\\n---\\n\", 4)\n\n if (closingMarker === -1) {\n throw new Error(\"SKILL.md \u7684 YAML frontmatter \u7F3A\u5C11\u7ED3\u675F\u6807\u8BB0\")\n }\n\n return {\n frontmatter: normalized.slice(4, closingMarker).trim(),\n content: normalized.slice(closingMarker + 5).trim(),\n }\n}\n", "export const PREVISIT_SESSION_ID_PREFIX = \"session-dsh-pre-duediligence-\"\n\nexport function isPrevisitSession(sessionId: string): boolean {\n return /^session-dsh-pre-duediligence-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(sessionId)\n}\n\nexport type PrevisitSessionHost = {\n sessions: {\n list?: { getSnapshot(): { current?: string } }\n create?(options: { workspaceId: string; sessionId: string }): Promise<string>\n open?(sessionId: string): void\n }\n workspaces?: {\n list?: { getSnapshot(): {\n items?: Array<{ workspaceId: string; path?: string; sessionIds?: string[] }>\n recentWorkspaceId?: string\n } }\n }\n}\n\nexport function resolvePrevisitWorkspaceId(ctx: PrevisitSessionHost): string | undefined {\n const workspace = ctx.workspaces?.list?.getSnapshot()\n const current = ctx.sessions.list?.getSnapshot().current\n const items = workspace?.items ?? []\n return items.find(item => current !== undefined && item.sessionIds?.includes(current))?.workspaceId\n ?? items.find(item => item.workspaceId === workspace?.recentWorkspaceId)?.workspaceId\n ?? items[0]?.workspaceId\n}\n\nexport async function createPrevisitSession(ctx: PrevisitSessionHost): Promise<string> {\n const workspaceId = resolvePrevisitWorkspaceId(ctx)\n if (!workspaceId) throw new Error(\"\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u518D\u6253\u5F00\u8BBF\u524D\u5C3D\u8C03\")\n if (typeof ctx.sessions.create !== \"function\" || typeof ctx.sessions.open !== \"function\") {\n throw new Error(\"\u5F53\u524D DSH \u7248\u672C\u6CA1\u6709\u53EF\u7528\u7684\u4F1A\u8BDD\u521B\u5EFA\u80FD\u529B\")\n }\n if (typeof globalThis.crypto?.randomUUID !== \"function\") {\n throw new Error(\"\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u5B89\u5168\u4F1A\u8BDD\u6807\u8BC6\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u6700\u65B0\u7248\u6D4F\u89C8\u5668\")\n }\n const requested = PREVISIT_SESSION_ID_PREFIX + globalThis.crypto.randomUUID()\n // Preserve the runtime receiver: create() uses this.manager.\n const created = await ctx.sessions.create({ workspaceId, sessionId: requested })\n if (created !== requested) throw new Error(\"\u8BBF\u524D\u5C3D\u8C03\u4F1A\u8BDD\u6807\u8BC6\u4E0D\u5339\u914D\uFF0C\u8BF7\u91CD\u8BD5\")\n return created\n}\n", "// \u4F5C\u6218\u5361 Markdown \u2192 \u53EF\u4E0B\u8F7D\u7684\u81EA\u5305\u542B\u6837\u5F0F\u5316 HTML \u62A5\u544A\u3002\n// \u914D\u8272\u3001\u5B57\u53F7\u3001\u6807\u9898\u89C4\u8303\u5168\u90E8\u9501\u5B9A\u4E3A\u7528\u6237\u63D0\u4F9B\u7684 demo \u8BBE\u8BA1\uFF0C\u6A21\u578B/\u524D\u7AEF\u90FD\u4E0D\u518D\u81EA\u884C\u914D\u8272\u3002\n// \u7EAF\u51FD\u6570\uFF0C\u65E0 DOM \u4F9D\u8D56\uFF0C\u4FBF\u4E8E\u5355\u6D4B\u3002\nimport { isPrevisitSession } from \"./previsit-session.js\"\n\nexport type ReportMeta = {\n company?: string\n generatedAt?: string\n}\n\n// \u4F1A\u8BDD\u8282\u70B9\u7684\u6700\u5C0F\u7ED3\u6784\uFF08DSH \u8282\u70B9\u5177\u4F53\u5F62\u6001\u672A\u77E5\uFF0C\u5B57\u6BB5\u5168\u90E8\u53EF\u9009\uFF09\nexport type CardNode = {\n id?: string\n seq?: number\n interrupted?: boolean\n blocks?: Array<{ kind: string; text?: string }>\n kind?: string\n role?: string\n text?: string\n content?: unknown\n message?: { role?: string; content?: unknown } | null\n parts?: Array<{ text?: string; type?: string } | string>\n call?: { name?: string } | null\n}\n\nexport type CardSnapshot = { nodes?: CardNode[]; running?: boolean }\n\nfunction contentText(value: unknown): string {\n if (typeof value === \"string\") return value\n if (!Array.isArray(value)) return \"\"\n return value.map(block => {\n if (typeof block === \"string\") return block\n if (block === null || typeof block !== \"object\") return \"\"\n const typed = block as { type?: unknown; kind?: unknown; text?: unknown }\n if ((typeof typed.type === \"string\" && typed.type !== \"text\") || (typeof typed.kind === \"string\" && typed.kind !== \"text\")) return \"\"\n return typeof typed.text === \"string\" ? typed.text : \"\"\n }).join(\"\")\n}\n\n// \u9632\u5FA1\u5F0F\u63D0\u53D6\u52A9\u624B\u8F93\u51FA\u7684\u4F5C\u6218\u5361\u6B63\u6587\uFF1ADSH \u4F1A\u8BDD\u8282\u70B9\u5F62\u6001\u672A\u77E5\uFF0C\u9010\u79CD\u53EF\u80FD\u5B57\u6BB5\u5C1D\u8BD5\u3002\nfunction nodeText(node: CardNode): string {\n if (Array.isArray(node.blocks)) return node.blocks.filter(b => b.kind === \"text\").map(b => b.text ?? \"\").join(\"\\n\")\n if (typeof node.text === \"string\") return node.text\n if (typeof node.content === \"string\") return node.content\n if (node.message) { const text = contentText(node.message.content); if (text !== \"\") return text }\n if (Array.isArray(node.content)) return contentText(node.content)\n if (Array.isArray(node.parts)) {\n return node.parts.map(pt => (typeof pt === \"string\" ? pt : (pt && typeof pt.text === \"string\" ? pt.text : \"\"))).join(\"\")\n }\n return \"\"\n}\n\n// \u4F5C\u6218\u5361\u8D77\u70B9\uFF1A\u4E00\u7EA7\u6807\u9898\u300C\u62DC\u8BBF\u4F5C\u6218\u5361 \u00B7 \u4F01\u4E1A\u540D\u300D\u6216\u9996\u6BB5\u300C\u2460 \u6838\u5FC3\u7814\u5224\u300D\nconst CARD_START = /^#{1,3}\\s*(\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361)[^\\n]*$|^#{1,3}\\s*[\u24601]?\\s*\u6838\u5FC3\u7814\u5224[^\\n]*$/m\n// \u5361\u7247\u7ED3\u6784\u7279\u5F81\uFF1A\u81F3\u5C11\u6709\u6838\u5FC3\u7814\u5224 + \u73B0\u573A\u5FC5\u95EE/\u8986\u76D6\u8BF4\u660E\u4E24\u7C7B\u6BB5\u843D\nconst CARD_SHAPE = (text: string): boolean => /\u6838\u5FC3\u7814\u5224/.test(text) && /\u73B0\u573A\u5FC5\u95EE|\u8986\u76D6\u8BF4\u660E|\u8986\u76D6\u5EA6/.test(text)\n// \u6CE8\u5165\u5185\u5BB9\uFF08\u6280\u80FD\u6E05\u5355\u3001\u7CFB\u7EDF\u63D0\u9192\u7B49\uFF09\u7EDD\u4E0D\u8FDB\u62A5\u544A\nconst JUNK = /<system-reminder>|<available_skills>|<command-name>|<\\/?antml/i\nconst isTool = (node: CardNode): boolean => node.kind === \"tool-result\" || node.kind === \"tool-call\"\nconst nodeRole = (node: CardNode): string => node.role ?? node.message?.role ?? node.kind ?? \"\"\nconst isUserish = (node: CardNode): boolean => /user|human|system|context|steering/i.test(`${nodeRole(node)} ${node.kind ?? \"\"}`) || node.interrupted === true\n\n// \u4ECE\u4F1A\u8BDD\u91CC\u8BA4\u9886\u4EFB\u52A1\uFF1A\u627E\u6700\u540E\u4E00\u6761\u5E26\u201C\u8BBF\u524D\u4EFB\u52A1 ID\uFF1APV-xxx\u201D\u7684\u6D88\u606F\uFF08\u5DE5\u4F5C\u53F0/\u8BBE\u5B9A\u6761\u53D1\u51FA\u7684\u6B63\u5F0F\u63D0\u4EA4\uFF09\uFF1B\n// \u6CA1\u6709\u6807\u8BB0\u4F46\u5DF2\u51FA\u73B0\u4F01\u67E5\u67E5\u5DE5\u5177\u8C03\u7528\u65F6\uFF0C\u89C6\u4E3A\u201C\u4F1A\u8BDD\u5185\u76F4\u63A5\u53D1\u8D77\u7684\u5C3D\u8C03\u201D\uFF0C\u4ECE\u5934\u8DDF\u8E2A\u3002\nexport function adoptTaskFromSnapshot(snapshot: CardSnapshot, sessionId: string, minimumBaseline = 0): { id: string; prompt: string; nodeBaseline: number } | null {\n if (!isPrevisitSession(sessionId)) return null\n const nodes = snapshot.nodes ?? []\n for (let idx = nodes.length - 1; idx >= minimumBaseline; idx--) {\n const node = nodes[idx]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) continue\n const text = nodeText(node)\n const m = /\u8BBF\u524D\u4EFB\u52A1 ID[\uFF1A:]\\s*(PV-[A-Z0-9-]+)/.exec(text)\n if (m !== null && m[1] !== undefined) return { id: m[1], prompt: text.trim(), nodeBaseline: idx }\n }\n // Native composer submissions inherit the explicitly owned Session. Never infer\n // ownership from QCC calls (shared by cleaning, form-fill and tender plugins).\n for (let idx = nodes.length - 1; idx >= minimumBaseline; idx--) {\n const node = nodes[idx]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) continue\n const prompt = nodeText(node).trim()\n if (prompt !== \"\" && !JUNK.test(prompt)) return { id: `turn:${node.seq ?? node.id ?? idx}`, prompt, nodeBaseline: idx }\n }\n return null\n}\n\nconst FULL_REPORT_SECTIONS = [\"\u6838\u5FC3\u7814\u5224\", \"\u4EA7\u4E1A\u5B9A\u4F4D\", \"\u8FD1\u671F\u52A8\u6001\", \"\u4E1A\u52A1\u5047\u8BBE\", \"\u7EA2\u7EBF\u63D0\u793A\", \"\u73B0\u573A\u5FC5\u95EE\", \"\u89E6\u8FBE\u5F00\u573A\", \"\u8986\u76D6\u8BF4\u660E\"]\nexport function captureTaskReport(snapshot: CardSnapshot, sessionId: string, task: { id: string; nodeBaseline: number; prompt: string }): string | null {\n if (!isPrevisitSession(sessionId) || snapshot.running === true) return null\n const node = snapshot.nodes?.[task.nodeBaseline]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) return null\n const adopted = adoptTaskFromSnapshot({ nodes: (snapshot.nodes ?? []).slice(0, task.nodeBaseline + 1) }, sessionId, task.nodeBaseline)\n if (adopted?.id !== task.id) return null\n // A later explicitly submitted task is a hard boundary, including after reload.\n const nodes = snapshot.nodes ?? []\n const next = nodes.findIndex((n, i) => i > task.nodeBaseline && /^(user|human)$/i.test(nodeRole(n)) && /\u8BBF\u524D\u4EFB\u52A1 ID[\uFF1A:]\\s*PV-/.test(nodeText(n)))\n const text = extractCardText({ nodes: next === -1 ? nodes : nodes.slice(0, next) }, task.nodeBaseline + 1)\n if (text === null) return null\n const sections = [...text.matchAll(/^#{1,4}\\s+(.+)$/gm)].map(m => normalizeHeading(m[1] ?? \"\").replace(/^\\d+\u3001\\s*/, \"\").replace(/\\*\\*/g, \"\").trim())\n return FULL_REPORT_SECTIONS.every(s => sections.some(title => title === s || title.startsWith(s + \"\uFF08\") || title.startsWith(s + \"\uFF1A\"))) ? text : null\n}\n\nexport function extractCardText(snapshot: CardSnapshot, baseline: number): string | null {\n const nodes = (snapshot.nodes ?? []).slice(baseline)\n // \u4ECE\u672B\u5C3E\u627E\u6700\u540E\u4E00\u4E2A\u5305\u542B\u4F5C\u6218\u5361\u8D77\u70B9\u7684\u8282\u70B9\uFF0C\u4ECE\u8D77\u70B9\u5207\u5230\u8BE5\u8282\u70B9\u672B\u5C3E\n for (let idx = nodes.length - 1; idx >= 0; idx--) {\n const node = nodes[idx]\n if (node === undefined || isTool(node) || isUserish(node)) continue\n const text = nodeText(node)\n if (JUNK.test(text)) continue\n const m = CARD_START.exec(text)\n if (m === null || m.index === undefined) continue\n let card = text.slice(m.index).trim()\n // \u540E\u7EED\u7D27\u90BB\u8282\u70B9\u82E5\u662F\u540C\u4E00\u5F20\u5361\u7684\u5EF6\u7EED\uFF08\u4EE5 Markdown \u6BB5\u6807\u9898\u5F00\u5934\u3001\u65E0\u6CE8\u5165\u5185\u5BB9\uFF09\uFF0C\u62FC\u63A5\n for (let j = idx + 1; j < nodes.length; j++) {\n const next = nodes[j]\n if (next === undefined || isTool(next) || isUserish(next)) break\n const t = nodeText(next).trim()\n if (t === \"\" || JUNK.test(t) || !/^(#{2,4}\\s|\\||-\\s|\\d+\\.\\s|\\*\\*)/.test(t)) break\n card += \"\\n\\n\" + t\n }\n if (CARD_SHAPE(card)) return card\n }\n // \u515C\u5E95\uFF1A\u5355\u8282\u70B9\u6574\u4F53\u7B26\u5408\u5361\u7247\u7ED3\u6784\n for (let idx = nodes.length - 1; idx >= 0; idx--) {\n const node = nodes[idx]\n if (node === undefined || isTool(node) || isUserish(node)) continue\n const text = nodeText(node).trim()\n if (text.length > 40 && !JUNK.test(text) && CARD_SHAPE(text)) return text\n }\n return null\n}\n\nconst esc = (s: string): string =>\n s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\n\n// \u8BFB\u8005\u89C6\u89D2\u7684\u6E05\u6D17\uFF1A\u4E8B\u5B9E\u7F16\u53F7 [F-013] \u4E0E\u8BC1\u636E\u5C42\u7EA7\u4EE3\u53F7 L1/L2 \u53EA\u5BF9\u6A21\u578B\u8FFD\u6EAF\u6709\u7528\uFF0C\u62A5\u544A\u91CC\u7FFB\u8BD1\u6210\u4EBA\u8BDD\u6216\u53BB\u6389\nconst TIER_WORDS: Record<string, string> = { L1: \"\u8D22\u62A5\u62AB\u9732\", L2: \"\u516C\u5F00\u8BB0\u5F55\", L3: \"\u5173\u8054\u5173\u7CFB\", L4: \"\u884C\u4E1A\u63A8\u65AD\", L5: \"\u73B0\u573A\u6838\u5B9E\" }\nexport function humanizeCell(text: string): string {\n const t = text.trim()\n if (/^L[1-5](\\s*[\\/\uFF0F\u3001]\\s*L[1-5])*$/.test(t)) {\n return t.split(/\\s*[\\/\uFF0F\u3001]\\s*/).map(x => TIER_WORDS[x] ?? x).join(\" / \")\n }\n if (t === \"\u6765\u6E90\u5C42\u7EA7\") return \"\u6765\u6E90\"\n return text\n}\nexport function stripFactIds(text: string): string {\n // \u652F\u6301 [F-013]\u3001[F-008/F-009/F-013]\u3001[F-002\u3001F-008]\u3001[F-1/F-2] \u7B49\u5199\u6CD5\n return text\n .replace(/\\s*\\[F-?\\d{1,4}(?:\\s*[\\/\u3001,\uFF0C;\uFF1B|]\\s*F-?\\d{1,4})*\\]/g, \"\")\n .replace(/[ \\t]+([\uFF0C\u3002\uFF1B\uFF1A\uFF09])/g, \"$1\")\n}\n// \u5047\u8BBE\u7F16\u53F7\u4E0E\u4F18\u5148\u7EA7\uFF1AH2 \u00B7 P1 \u2192 \u5047\u8BBE 2\uFF08P1\uFF09\uFF1B\u6B63\u6587\u91CC\u201C\u56DE\u6307 H2\u201D\u2192\u201C\u56DE\u6307\u5047\u8BBE 2\u201D\nconst PRIORITY_WORDS: Record<string, string> = { P0: \"\u4F18\u5148\", P1: \"\u91CD\u8981\", P2: \"\u5907\u9009\" }\nexport function humanizeHypothesis(text: string): string {\n return text\n .replace(/^\\s*H(\\d+)\\s*[\u00B7\u30FB:\uFF1A\\-\u2013]\\s*(P[012])\\s*[\u00B7\u30FB:\uFF1A\\-\u2013]\\s*/, (_m, n: string, p: string) => `\u5047\u8BBE ${n}\uFF08${PRIORITY_WORDS[p] ?? p}\uFF09`)\n .replace(/(?<![A-Za-z0-9])H(\\d{1,2})(?![A-Za-z0-9])/g, \"\u5047\u8BBE $1\")\n}\n\n// \u884C\u5185\uFF1A**\u52A0\u7C97**\uFF1B\u4E8B\u5B9E\u7F16\u53F7\u53BB\u6389\nfunction inline(text: string): string {\n let html = esc(humanizeHypothesis(stripFactIds(text)))\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, \"<strong>$1</strong>\")\n return html\n}\n\nconst CIRCLED: Record<string, string> = { \"\u2460\": \"1\", \"\u2461\": \"2\", \"\u2462\": \"3\", \"\u2463\": \"4\", \"\u2464\": \"5\", \"\u2465\": \"6\", \"\u2466\": \"7\", \"\u2467\": \"8\", \"\u2468\": \"9\", \"\u2469\": \"10\" }\nexport function normalizeHeading(text: string): string {\n return text.trim().replace(/^([\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469])\\s*/, (_m, c: string) => `${CIRCLED[c] ?? c}\u3001`).replace(/^(\\d+)[.\uFF0E]\\s*/, \"$1\u3001\")\n}\n\n// \u62A5\u544A Markdown \u5B50\u96C6 \u2192 HTML \u4E3B\u4F53\u3002\u652F\u6301\uFF1A# ## ### \u6807\u9898\u3001| \u8868\u683C |\u3001- \u5217\u8868\u30011. \u6709\u5E8F\u3001--- \u5206\u9694\u3001\u6BB5\u843D\u3002\nexport function renderCardMarkdown(md: string): string {\n const lines = md.replace(/\\r\\n/g, \"\\n\").split(\"\\n\")\n const out: string[] = []\n let i = 0\n const flushList = (items: string[], ordered: boolean) => {\n if (!items.length) return\n const tag = ordered ? \"ol\" : \"ul\"\n out.push(`<${tag}>${items.map(it => `<li>${inline(it)}</li>`).join(\"\")}</${tag}>`)\n }\n while (i < lines.length) {\n const line = lines[i] ?? \"\"\n const t = line.trim()\n if (t === \"\") { i++; continue }\n if (/^---+$/.test(t)) { out.push(\"<hr/>\"); i++; continue }\n // \u8868\u683C\uFF1A\u8FDE\u7EED\u4EE5 | \u5F00\u5934\u7684\u884C\uFF0C\u7B2C\u4E8C\u884C\u662F\u5206\u9694\u7B26\n const sep = lines[i + 1] ?? \"\"\n if (t.startsWith(\"|\") && /^\\|[\\s:|-]+\\|?$/.test(sep.trim())) {\n const header = t.split(\"|\").slice(1, -1).map(c => humanizeCell(c.trim()))\n i += 2\n const rows: string[][] = []\n while (i < lines.length && (lines[i] ?? \"\").trim().startsWith(\"|\")) {\n rows.push((lines[i] ?? \"\").trim().split(\"|\").slice(1, -1).map(c => humanizeCell(c.trim())))\n i++\n }\n const thead = `<thead><tr>${header.map(h => `<th>${inline(h)}</th>`).join(\"\")}</tr></thead>`\n const tbody = `<tbody>${rows.map(r => `<tr>${r.map(c => `<td>${inline(c)}</td>`).join(\"\")}</tr>`).join(\"\")}</tbody>`\n out.push(`<div class=\"tw\"><table>${thead}${tbody}</table></div>`)\n continue\n }\n // \u6807\u9898\uFF1A\u5708\u53F7 \u2460\u2461 \u7EDF\u4E00\u6210\u201C1\u3001\u201D\u683C\u5F0F\n const h = /^(#{1,4})\\s+(.*)$/.exec(t)\n if (h) {\n const level = (h[1] ?? \"##\").length\n out.push(`<h${level}>${inline(normalizeHeading(h[2] ?? \"\"))}</h${level}>`)\n i++\n continue\n }\n // \u6807\u9898\u4E0B\u7684\u5C0F\u8BF4\u660E\uFF08> \u8BF4\u660E\uFF1A\u2026\uFF09\n if (t.startsWith(\">\")) {\n out.push(`<p class=\"note\">${inline(t.replace(/^>\\s?/, \"\"))}</p>`)\n i++\n continue\n }\n // \u65E0\u5E8F\u5217\u8868\n if (/^[-*]\\s+/.test(t)) {\n const items: string[] = []\n while (i < lines.length && /^\\s*[-*]\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").trim().replace(/^[-*]\\s+/, \"\"))\n i++\n }\n flushList(items, false)\n continue\n }\n // \u6709\u5E8F\u5217\u8868\n if (/^\\d+\\.\\s+/.test(t)) {\n const items: string[] = []\n while (i < lines.length && /^\\s*\\d+\\.\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").trim().replace(/^\\d+\\.\\s+/, \"\"))\n i++\n }\n flushList(items, true)\n continue\n }\n // \u6BB5\u843D\n out.push(`<p>${inline(t)}</p>`)\n i++\n }\n return out.join(\"\\n\")\n}\n\n// demo \u9501\u5B9A\u914D\u8272\u4E0E\u5B57\u53F7\uFF1B\u6807\u9898\u514B\u5236\u3001\u56DB\u5B57\u4F18\u5148\uFF0C\u6807\u9898\u4E0B\u53EF\u5E26\u5C0F\u8BF4\u660E\u6A21\u5757\u3002\nconst REPORT_CSS = `\n:root{\n --blue:#128BED;--blue-dark:#0875D1;--ink:#202C3B;--body:#3D4A5C;--muted:#626F80;\n --line:#DCE4EC;--line-blue:#CDE9FC;--hero:#F2F9FC;\n --ok:#16a34a;--ok-bg:#f0fdf4;--warn:#d97706;--warn-bg:#fff7ed;--risk:#ef4444;--risk-bg:#fef2f2;\n}\n*{box-sizing:border-box}\nbody{margin:0;background:#f1f5f9;color:var(--body);\n font:15px/1.75 -apple-system,BlinkMacSystemFont,\"PingFang SC\",\"Microsoft YaHei\",\"Noto Sans SC\",sans-serif;\n -webkit-font-smoothing:antialiased}\n.sheet{max-width:860px;margin:24px auto;background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden;\n box-shadow:0 1px 2px rgba(15,23,42,.04),0 12px 32px -20px rgba(15,23,42,.25)}\n.hero{background:var(--hero);border-bottom:1px solid var(--line-blue);padding:22px 26px 20px}\n.hero .eyebrow{font-size:11px;font-weight:800;letter-spacing:.12em;color:var(--blue);margin:0 0 6px}\n.hero h1{margin:0;color:var(--ink);font-size:22px;font-weight:900;line-height:1.4}\n.hero .meta{margin:8px 0 0;color:var(--muted);font-size:12.5px;line-height:1.7}\n.body{padding:8px 26px 26px}\n/* \u5927\u6807\u9898\uFF1A\u84DD\u8272 + \u6807\u9898\u4E0B\u77ED\u6A2A\u7EBF\uFF1B\u5C0F\u6807\u9898\uFF1A\u6D45\u7070\u5E95 + \u5DE6\u4FA7\u84DD\u7AD6\u7EBF */\n.body h2{position:relative;margin:26px 0 12px;padding:0 0 8px;color:var(--blue-dark);font-size:16px;font-weight:800;letter-spacing:.01em}\n.body h2::after{content:\"\";position:absolute;left:0;bottom:0;width:32px;height:3px;border-radius:2px;background:var(--blue)}\n.body h2:first-of-type{margin-top:10px}\n.body h3{margin:16px 0 8px;padding:6px 10px;border-left:3px solid var(--blue);border-radius:0 4px 4px 0;background:#f5f7fa;color:var(--ink);font-size:13.5px;font-weight:800}\n.body h4{margin:12px 0 4px;color:var(--ink);font-size:13px;font-weight:700}\n.body h1{display:none}\n.note{margin:-4px 0 10px;color:var(--muted);font-size:12px;line-height:1.7}\nblockquote{margin:-4px 0 10px;padding:0;border:0;color:var(--muted);font-size:12px;line-height:1.7}\nblockquote p{margin:0}\np{margin:6px 0}\nstrong{color:var(--ink)}\n.fid{color:var(--blue);font:600 10px ui-monospace,monospace;margin-left:2px}\nul,ol{margin:6px 0;padding-left:20px}\nli{margin:3px 0;line-height:1.7}\nhr{border:0;border-top:1px solid var(--line);margin:16px 0}\n.tw{overflow-x:auto;margin:8px 0}\ntable{border-collapse:collapse;width:100%;font-size:13px}\nth,td{border:1px solid var(--line);padding:8px 11px;text-align:left;vertical-align:top;line-height:1.6;word-break:break-word}\nth{background:#f8fafc;color:var(--muted);font-weight:700;font-size:12px;white-space:nowrap}\n/* \u9996\u5217\u591A\u4E3A\u201C\u7B49\u7EA7/\u65E5\u671F/\u63A8\u8350\u7EA7\u201D\u8FD9\u7C7B\u77ED\u6807\u7B7E\uFF1A\u4E0D\u6362\u884C\u3001\u4E0D\u88AB\u6324\u6241\uFF1B\u957F\u6587\u672C\u5217\u81EA\u9002\u5E94 */\ntd:first-child,th:first-child{white-space:nowrap;width:1%}\ntd:nth-child(2){min-width:180px}\n/* \u72B6\u6001\u8272\uFF1A\u51FA\u73B0\u7EA2\u7EBF/\u5173\u6CE8/\u786E\u8BA4\u7B49\u8BCD\u81EA\u52A8\u67D3\u8272\u7531\u5185\u5BB9\u51B3\u5B9A\uFF0C\u8FD9\u91CC\u7ED9\u51FA\u53EF\u7528\u7C7B */\n.tag-ok{color:var(--ok);background:var(--ok-bg)}\n.tag-warn{color:var(--warn);background:var(--warn-bg)}\n.tag-risk{color:var(--risk);background:var(--risk-bg)}\nfooter{padding:14px 26px 20px;border-top:1px solid var(--line);color:var(--muted);font-size:11.5px;line-height:1.7}\n@media print{body{background:#fff}.sheet{border:0;box-shadow:none;margin:0}}\n`\n\n// \u4ECE\u4F5C\u6218\u5361\u9996\u884C/\u951A\u5B9A\u884C\u63D0\u53D6\u4F01\u4E1A\u540D\u4E0E\u751F\u6210\u65F6\u95F4\u505A hero\nfunction deriveMeta(md: string, meta?: ReportMeta): { company: string; anchor: string; when: string } {\n const first = /^#?\\s*(?:\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361)\\s*\u00B7\\s*([^\\n\u951A]+)/m.exec(md)\n const anchorLine = /\u951A\u5B9A\u4E3B\u4F53\uFF1A([^\\n]+?)(?:\\s*\u751F\u6210\u65F6\u95F4|$)/m.exec(md)\n const whenLine = /\u751F\u6210\u65F6\u95F4[:\uFF1A]\\s*([0-9-]+)/.exec(md)\n return {\n company: meta?.company || (first?.[1]?.trim() ?? \"\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\"),\n anchor: (anchorLine?.[1] ?? \"\").trim().replace(/[\uFF5C|\u00B7\\s]+$/, \"\"),\n when: meta?.generatedAt || (whenLine?.[1] ?? \"\"),\n }\n}\n\nexport type ReportHero = { company: string; anchor: string; when: string }\n\n// \u7EDF\u4E00\u5916\u58F3\uFF1Ahero + body + footer\nexport function wrapPrevisitReport(bodyHtml: string, m: ReportHero): string {\n const heroMeta = [m.anchor, m.when ? `\u751F\u6210\u65F6\u95F4\uFF1A${m.when}` : \"\"].filter(Boolean).join(\"\u3000\u00B7\u3000\")\n return `<!doctype html><html lang=\"zh\"><head><meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A \u00B7 ${esc(m.company)}</title><style>${REPORT_CSS}</style></head>\n<body><div class=\"sheet\">\n<div class=\"hero\"><p class=\"eyebrow\">\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A</p><h1>${esc(m.company)}</h1>${heroMeta ? `<p class=\"meta\">${esc(heroMeta)}</p>` : \"\"}</div>\n<div class=\"body\">${bodyHtml}</div>\n<footer>\u6570\u636E\u6765\u6E90\uFF1A\u4F01\u67E5\u67E5\u3002\u5404\u7EF4\u5EA6\u65F6\u6548\u4E0D\u540C\uFF0C\u4E0D\u627F\u8BFA\u5B9E\u65F6\u3002\u672C\u62A5\u544A\u4E3A\u8BBF\u524D\u8F85\u52A9\u6750\u6599\uFF0C\u4E0D\u6784\u6210\u6388\u4FE1\u3001\u5408\u4F5C\u6216\u4EFB\u4F55\u5546\u4E1A\u51B3\u7B56\u4F9D\u636E\uFF0C\u4EE5\u5B98\u65B9\u516C\u793A\u4E3A\u51C6\u3002</footer>\n</div></body></html>`\n}\n\nexport function buildPrevisitReportHtml(cardMarkdown: string, meta?: ReportMeta): string {\n const m = deriveMeta(cardMarkdown, meta)\n // \u53BB\u6389\u6B63\u6587\u91CC\u7684\u4E00\u7EA7\u6807\u9898\u548C\u951A\u5B9A\u884C\uFF08\u5DF2\u8FDB hero\uFF09\uFF0C\u5176\u4F59\u8FDB body\n const body = cardMarkdown\n .replace(/^#\\s*(?:\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361).*$/m, \"\")\n .replace(/^\u951A\u5B9A\u4E3B\u4F53\uFF1A.*$/m, \"\")\n .trim()\n return wrapPrevisitReport(renderCardMarkdown(body), m)\n}\n\n// \u515C\u5E95\uFF1A\u76F4\u63A5\u7528\u4F1A\u8BDD\u91CC\u5DF2\u6E32\u67D3\u597D\u7684\u62A5\u544A HTML\uFF08\u53BB\u6389\u6837\u5F0F/\u7C7B\u540D/\u63A7\u4EF6\uFF09\uFF0C\u5957\u540C\u4E00\u5916\u58F3\nexport function buildPrevisitReportFromRenderedHtml(bodyHtml: string, plainText: string, meta?: ReportMeta): string {\n const m = deriveMeta(plainText, meta)\n const body = humanizeHypothesis(stripFactIds(bodyHtml))\n .replace(/<(td|th)([^>]*)>\\s*([^<]{1,20})\\s*<\\/\\1>/g, (_m, tag: string, attrs: string, cell: string) => `<${tag}${attrs}>${esc(humanizeCell(cell))}</${tag}>`)\n return wrapPrevisitReport(body, m)\n}\n", "import { randomUUID } from \"node:crypto\"\n\nimport type { ToolOutcome } from \"./tool-outcome.js\"\n\nexport const PREVISIT_TASK_STATES = [\n \"needs-entity-search\",\n \"needs-entity-confirmation\",\n \"entity-confirmed\",\n \"running\",\n \"finalizing\",\n \"completed\",\n \"partial\",\n \"failed\",\n] as const\n\nexport type PrevisitTaskState = (typeof PREVISIT_TASK_STATES)[number]\nexport type PrevisitTaskStage = \"target\" | \"scope\" | \"collect\" | \"verify\" | \"output\"\n\nexport const PREVISIT_TERMINAL_STATES: ReadonlySet<PrevisitTaskState> = new Set([\"completed\", \"partial\", \"failed\"])\n\nexport type PrevisitRun = {\n id: string\n dimension: string\n toolName?: string\n status: ToolOutcome\n quotaUsed: boolean\n startedAt: string\n completedAt?: string\n message?: string\n}\n\nexport type PrevisitArtifact = {\n id: string\n format: \"html\"\n fileName: string\n mediaType: \"text/html; charset=utf-8\"\n createdAt: string\n}\n\nexport type PrevisitTaskRecord = {\n id: string\n schemaVersion: 1\n revision: number\n sessionId: string\n workspace: string\n query: string\n depth: \"fast\" | \"standard\" | \"deep\"\n limit: number\n used: number\n state: PrevisitTaskState\n stage: PrevisitTaskStage\n entity?: { fullName: string; creditCode: string }\n runs: PrevisitRun[]\n reportMarkdown?: string\n artifact?: PrevisitArtifact\n lastError?: string\n createdAt: string\n updatedAt: string\n completedAt?: string\n}\n\nexport type PrevisitVerificationClosure = {\n gaps: string[]\n partialRequired: boolean\n}\n\nconst VERIFICATION_DETAIL_LABELS = {\n dishonest: \"\u5931\u4FE1\u660E\u7EC6\",\n enforcement: \"\u88AB\u6267\u884C\u660E\u7EC6\",\n terminated_cases: \"\u7EC8\u672C\u6848\u4EF6\u660E\u7EC6\",\n equity_freeze: \"\u80A1\u6743\u51BB\u7ED3\u660E\u7EC6\",\n business_exception: \"\u7ECF\u8425\u5F02\u5E38\u660E\u7EC6\",\n administrative_penalty: \"\u884C\u653F\u5904\u7F5A\u660E\u7EC6\",\n tax_abnormal: \"\u7A0E\u52A1\u5F02\u5E38\u660E\u7EC6\",\n judicial_documents: \"\u88C1\u5224\u6587\u4E66\u660E\u7EC6\",\n} as const\n\nconst UNRESOLVED_VERIFICATION_STATUSES: ReadonlySet<ToolOutcome> = new Set([\n \"failed\",\n \"no-permission\",\n \"not-executed\",\n \"unknown\",\n])\n\n/**\n * Shared verification gate for explicit tool finalization and the UI report\n * reconciliation fallback. `skipped` is a resolved green state; synthetic\n * `previsit-pending-*` runs remain open until a real Provider result replaces them.\n */\nexport function previsitVerificationClosure(task: PrevisitTaskRecord): PrevisitVerificationClosure {\n const verificationDimensions = new Set<string>([\n \"risk_scan\",\n \"personnel\",\n \"executive_risk\",\n ...Object.keys(VERIFICATION_DETAIL_LABELS),\n ])\n const latest = new Map<string, PrevisitRun>()\n for (const run of task.runs) if (verificationDimensions.has(run.dimension)) latest.set(run.dimension, run)\n const gaps: string[] = []\n const isPending = (run: PrevisitRun | undefined) => run === undefined\n || run.status === \"running\"\n || run.id.startsWith(\"previsit-pending-\")\n\n const riskScan = latest.get(\"risk_scan\")\n if (isPending(riskScan)) gaps.push(\"\u98CE\u9669\u626B\u63CF\u672A\u5B8C\u6210\")\n else if (riskScan?.status === \"done\" || riskScan?.status === \"no-data\" || riskScan?.status === \"skipped\") {\n for (const [dimension, label] of Object.entries(VERIFICATION_DETAIL_LABELS)) {\n if (isPending(latest.get(dimension))) gaps.push(`${label}\u672A\u95ED\u73AF`)\n }\n }\n\n const personnel = latest.get(\"personnel\")\n if (isPending(personnel)) gaps.push(\"\u5173\u952E\u4EBA\u5458\u67E5\u8BE2\u672A\u5B8C\u6210\")\n else if (personnel?.status === \"done\" || personnel?.status === \"unknown\" || personnel?.status === \"no-data\") {\n if (isPending(latest.get(\"executive_risk\"))) gaps.push(\"\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\u672A\u95ED\u73AF\")\n }\n\n return {\n gaps: [...new Set(gaps)],\n partialRequired: [...latest.values()].some(run => UNRESOLVED_VERIFICATION_STATUSES.has(run.status)),\n }\n}\n\ntype StorageTable = {\n get(key: string): unknown | Promise<unknown>\n put(key: string, value: unknown): unknown | Promise<unknown>\n entries(): Iterable<[string, unknown]>\n}\n\nexport type StorageDomain = {\n open(spec: object): Promise<{ table(name: string): StorageTable }>\n}\n\nconst DOMAIN_SPEC = {\n name: \"previsit_tasks_v1\",\n version: 1,\n tables: {\n tasks: {\n valueSchema: {\n parse: (value: unknown) => value,\n safeParse: (value: unknown) => ({ success: true, data: value }),\n },\n },\n },\n}\n\nconst VALID_REQUEST_ID = /^PV-\\d{8}-[A-Z0-9-]{4,40}$/\nconst nowIso = () => new Date().toISOString()\n\nfunction isRecord(value: unknown): value is PrevisitTaskRecord {\n if (value === null || typeof value !== \"object\") return false\n const record = value as Partial<PrevisitTaskRecord>\n return typeof record.id === \"string\"\n && record.schemaVersion === 1\n && typeof record.sessionId === \"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}\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()) if (isRecord(value)) this.records.set(id, normalizeTerminalRecord(value))\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 if (!isRecord(value)) return undefined\n const normalized = normalizeTerminalRecord(value)\n if (normalized !== value) await this.put(normalized)\n else this.records.set(id, normalized)\n return normalized\n }\n\n async put(record: PrevisitTaskRecord): Promise<PrevisitTaskRecord> {\n this.records.set(record.id, record)\n await this.table?.put(record.id, record)\n return record\n }\n\n async create(input: {\n id?: string\n sessionId: string\n workspace: string\n query: string\n depth: \"fast\" | \"standard\" | \"deep\"\n limit?: number\n }): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n const id = normalizePrevisitRequestId(input.id) ?? createPrevisitHostTaskId()\n const existing = await this.get(id)\n if (existing !== undefined && existing.sessionId !== input.sessionId) throw new Error(\"\u4EFB\u52A1\u6807\u8BC6\u5DF2\u5C5E\u4E8E\u5176\u4ED6\u4F1A\u8BDD\")\n if (existing !== undefined) {\n assertTaskOpen(existing)\n return this.update(id, current => {\n const { entity: _entity, lastError: _lastError, reportMarkdown: _report, artifact: _artifact, completedAt: _completedAt, ...retained } = current\n return {\n ...retained,\n query: input.query,\n depth: input.depth,\n limit: 0,\n state: \"needs-entity-search\",\n stage: \"target\",\n }\n })\n }\n return this.put({\n id,\n schemaVersion: 1,\n revision: 1,\n sessionId: input.sessionId,\n workspace: input.workspace,\n query: input.query,\n depth: input.depth,\n limit: 0,\n used: 0,\n state: \"needs-entity-search\",\n stage: \"target\",\n runs: [],\n createdAt: timestamp,\n updatedAt: timestamp,\n })\n }\n\n async update(id: string, updater: (record: PrevisitTaskRecord) => PrevisitTaskRecord): Promise<PrevisitTaskRecord> {\n const current = await this.get(id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n const proposed = updater(current)\n const next: PrevisitTaskRecord = {\n ...proposed,\n id: current.id,\n schemaVersion: 1,\n revision: current.revision + 1,\n updatedAt: nowIso(),\n }\n return this.put(next)\n }\n\n async startRun(id: string, input: { runId: string; dimension: string; toolName?: string; quotaUsed: boolean }): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n return this.update(id, current => {\n assertTaskOpen(current)\n const { lastError: _lastError, ...retained } = current\n return {\n ...retained,\n used: current.used + (input.quotaUsed ? 1 : 0),\n state: \"running\",\n stage: input.dimension === \"entity_search\" ? \"target\" : /risk|dishonest|enforcement|terminated|freeze|exception|penalty|tax|judicial|executive/.test(input.dimension) ? \"verify\" : \"collect\",\n runs: [...current.runs, {\n id: input.runId,\n dimension: input.dimension,\n ...(input.toolName === undefined ? {} : { toolName: input.toolName }),\n status: \"running\" as const,\n quotaUsed: input.quotaUsed,\n startedAt: timestamp,\n }].slice(-160),\n }\n })\n }\n\n async finishRun(id: string, runId: string, status: ToolOutcome, message?: string): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n return this.update(id, current => {\n const runs: PrevisitRun[] = current.runs.map(run => run.id === runId ? {\n ...run,\n status,\n completedAt: timestamp,\n ...(message === undefined ? {} : { message: message.slice(0, 500) }),\n } : run)\n const run = runs.find(item => item.id === runId)\n if (PREVISIT_TERMINAL_STATES.has(current.state) || (current.reportMarkdown?.trim() ?? \"\") !== \"\") {\n return { ...current, runs }\n }\n const nextState = run?.dimension === \"entity_search\"\n ? (status === \"done\" || status === \"unknown\" ? \"needs-entity-confirmation\" : \"needs-entity-search\")\n : current.state\n return {\n ...current,\n runs,\n state: nextState,\n stage: current.stage,\n ...(status === \"failed\" ? { lastError: message ?? \"\u67E5\u8BE2\u5931\u8D25\" } : {}),\n }\n })\n }\n\n async confirmEntity(id: string, entity: { fullName: string; creditCode: string }): Promise<PrevisitTaskRecord> {\n return this.update(id, current => {\n assertTaskOpen(current)\n const { lastError: _lastError, ...retained } = current\n return { ...retained, entity, state: \"entity-confirmed\", stage: \"scope\" }\n })\n }\n\n async finalize(id: string, reportMarkdown: string, state: \"completed\" | \"partial\"): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n const current = await this.get(id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n if (PREVISIT_TERMINAL_STATES.has(current.state) && current.reportMarkdown !== undefined) return current\n const report = validatePrevisitReport(reportMarkdown, current.entity?.fullName)\n const artifact = reportArtifactFor(current, timestamp)\n return this.update(id, record => {\n const { lastError: _lastError, ...retained } = record\n return {\n ...retained,\n state,\n stage: \"output\",\n reportMarkdown: report,\n artifact,\n completedAt: timestamp,\n }\n })\n }\n}\n", "import { buildPrevisitReportHtml } from \"./report-export.js\"\nimport { isPrevisitSession } from \"./previsit-session.js\"\nimport { normalizePrevisitRequestId, previsitVerificationClosure, validatePrevisitReport, type PrevisitTaskRecord, type PrevisitWorkflowStore } from \"./previsit-workflow.js\"\n\ntype RequestLike = AsyncIterable<Uint8Array> & {\n method?: string\n url?: string\n headers: Record<string, string | string[] | undefined>\n}\n\ntype ResponseLike = {\n writeHead(status: number, headers?: Record<string, string>): void\n end(body?: string | Uint8Array): void\n}\n\nexport type WebServer = {\n register(input: { kind: \"prefix\"; path: string; handler(req: RequestLike, res: ResponseLike): unknown }): () => void\n}\n\nfunction isTrusted(req: RequestLike): boolean {\n const fetchSite = String(req.headers[\"sec-fetch-site\"] ?? \"\")\n if (fetchSite === \"cross-site\") return false\n const origin = req.headers.origin\n if (typeof origin === \"string\") {\n try {\n const parsed = new URL(origin)\n if (parsed.hostname !== \"127.0.0.1\" && parsed.hostname !== \"localhost\") return false\n } catch { return false }\n }\n return true\n}\n\nfunction writeJson(res: ResponseLike, status: number, payload: unknown): void {\n res.writeHead(status, {\n \"content-type\": \"application/json; charset=utf-8\",\n \"cache-control\": \"no-store\",\n \"x-content-type-options\": \"nosniff\",\n \"referrer-policy\": \"no-referrer\",\n })\n res.end(JSON.stringify(payload))\n}\n\nasync function readJson(req: RequestLike): Promise<Record<string, unknown>> {\n const chunks: Uint8Array[] = []\n let size = 0\n for await (const chunk of req) {\n size += chunk.length\n if (size > 256_000) throw new Error(\"report payload too large\")\n chunks.push(chunk)\n }\n const text = Buffer.concat(chunks).toString(\"utf8\")\n const value = text === \"\" ? {} : JSON.parse(text)\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"invalid JSON body\")\n return value as Record<string, unknown>\n}\n\nfunction publicTask(task: PrevisitTaskRecord): Omit<PrevisitTaskRecord, \"reportMarkdown\"> & { reportReady: boolean } {\n const { reportMarkdown, ...record } = task\n return { ...record, reportReady: typeof reportMarkdown === \"string\" && reportMarkdown.length > 0 }\n}\n\nexport function mountPrevisitWebRoutes(webServer: WebServer, workflow: PrevisitWorkflowStore): () => void {\n return webServer.register({\n kind: \"prefix\",\n path: \"/previsit/api/tasks\",\n async handler(req, res) {\n if (!isTrusted(req)) return writeJson(res, 403, { ok: false, code: \"PREVISIT_UNTRUSTED\", message: \"untrusted origin\" })\n try {\n const url = new URL(req.url ?? \"/previsit/api/tasks\", \"http://127.0.0.1\")\n const segments = url.pathname.split(\"/\").filter(Boolean)\n const tasksIndex = segments.indexOf(\"tasks\")\n const rest = tasksIndex === -1 ? [] : segments.slice(tasksIndex + 1)\n if (rest.length === 0) {\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET required\" })\n const sessionId = url.searchParams.get(\"sessionId\") ?? undefined\n if (sessionId !== undefined && !isPrevisitSession(sessionId)) return writeJson(res, 400, { ok: false, code: \"PREVISIT_SESSION\", message: \"invalid previsit session\" })\n const tasks = await workflow.list(sessionId)\n return writeJson(res, 200, { ok: true, marker: \"previsit-workflow-v1\", tasks: tasks.map(publicTask) })\n }\n const taskId = decodeURIComponent(rest[0] ?? \"\")\n if (normalizePrevisitRequestId(taskId) === undefined && !/^PVT-[a-f0-9-]{36}$/i.test(taskId)) {\n return writeJson(res, 400, { ok: false, code: \"PREVISIT_TASK_ID\", message: \"invalid task id\" })\n }\n const task = await workflow.get(taskId)\n if (task === undefined) return writeJson(res, 404, { ok: false, code: \"PREVISIT_NOT_FOUND\", message: \"task not found\" })\n const sessionId = url.searchParams.get(\"sessionId\")\n if (sessionId === null || !isPrevisitSession(sessionId) || sessionId !== task.sessionId) {\n return writeJson(res, 403, { ok: false, code: \"PREVISIT_TASK_SCOPE\", message: \"task does not belong to this session\" })\n }\n if (rest.length === 1) {\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET required\" })\n return writeJson(res, 200, {\n ok: true,\n marker: \"previsit-workflow-v1\",\n task: { ...publicTask(task), ...(task.reportMarkdown === undefined ? {} : { reportMarkdown: task.reportMarkdown }) },\n })\n }\n if (rest.length === 2 && rest[1] === \"report\") {\n if (req.method === \"PUT\") {\n const payload = await readJson(req)\n if (typeof payload.reportMarkdown !== \"string\") return writeJson(res, 400, { ok: false, code: \"PREVISIT_REPORT\", message: \"reportMarkdown required\" })\n const reportMarkdown = validatePrevisitReport(payload.reportMarkdown, task.entity?.fullName)\n const closure = previsitVerificationClosure(task)\n if (closure.gaps.length > 0) {\n return writeJson(res, 409, {\n ok: false,\n code: \"PREVISIT_VERIFICATION_INCOMPLETE\",\n message: `\u8BC1\u636E\u6838\u9A8C\u672A\u95ED\u73AF\uFF1A${closure.gaps.join(\"\uFF1B\")}`,\n })\n }\n const status = payload.status === \"partial\" || closure.partialRequired ? \"partial\" : \"completed\"\n const completed = await workflow.finalize(task.id, reportMarkdown, status)\n return writeJson(res, 200, { ok: true, marker: \"previsit-workflow-v1\", task: { ...publicTask(completed), reportMarkdown: completed.reportMarkdown } })\n }\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET or PUT required\" })\n if (task.reportMarkdown === undefined || task.artifact === undefined) {\n return writeJson(res, 409, { ok: false, code: \"PREVISIT_REPORT_PENDING\", message: \"report not ready\" })\n }\n const html = buildPrevisitReportHtml(task.reportMarkdown, {\n ...(task.entity?.fullName === undefined ? {} : { company: task.entity.fullName }),\n ...(task.completedAt === undefined ? {} : { generatedAt: task.completedAt.slice(0, 10) }),\n })\n const ascii = task.artifact.fileName.replace(/[^\\x20-\\x7e]/g, \"_\").replace(/[\"\\\\]/g, \"_\")\n res.writeHead(200, {\n \"content-type\": task.artifact.mediaType,\n \"content-disposition\": `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encodeURIComponent(task.artifact.fileName)}`,\n \"cache-control\": \"no-store\",\n \"x-content-type-options\": \"nosniff\",\n \"referrer-policy\": \"no-referrer\",\n })\n return res.end(html)\n }\n return writeJson(res, 404, { ok: false, code: \"PREVISIT_ROUTE\", message: \"route not found\" })\n } catch (error) {\n return writeJson(res, 500, { ok: false, code: \"PREVISIT_HOST\", message: error instanceof Error ? error.message : String(error) })\n }\n },\n })\n}\n", "import { randomUUID } from \"node:crypto\"\nimport { isPrevisitSession } from \"./previsit-session.js\"\nimport { classifyQccProviderOutcome } from \"./tool-outcome.js\"\nimport { normalizePrevisitRequestId, previsitVerificationClosure, PrevisitWorkflowStore, validatePrevisitReport } from \"./previsit-workflow.js\"\n\n// Business vocabulary is fixed here; the browser/model cannot dispatch an arbitrary MCP tool.\nexport const QUERY_ROUTES = {\n entity_search: [\"company\", \"get_company_by_query\"],\n registration: [\"company\", \"get_company_registration_info\"],\n profile: [\"company\", \"get_company_profile\"],\n annual_reports: [\"company\", \"get_annual_reports\"],\n changes: [\"company\", \"get_change_records\"],\n shareholders: [\"company\", \"get_shareholder_info\"],\n beneficiaries: [\"company\", \"get_beneficial_owners\"],\n personnel: [\"company\", \"get_key_personnel\"],\n contacts: [\"company\", \"get_contact_info\"],\n investments: [\"company\", \"get_external_investments\"],\n branches: [\"company\", \"get_branches\"],\n risk_scan: [\"risk\", \"get_company_risk_scan\"],\n dishonest: [\"risk\", \"get_dishonest_info\"],\n enforcement: [\"risk\", \"get_judgment_debtor_info\"],\n terminated_cases: [\"risk\", \"get_terminated_cases\"],\n equity_freeze: [\"risk\", \"get_equity_freeze\"],\n business_exception: [\"risk\", \"get_business_exception\"],\n administrative_penalty: [\"risk\", \"get_administrative_penalty\"],\n tax_abnormal: [\"risk\", \"get_tax_abnormal\"],\n judicial_documents: [\"risk\", \"get_judicial_documents\"],\n patents: [\"ipr\", \"get_patent_info\"],\n software_copyright: [\"ipr\", \"get_software_copyright_info\"],\n financing: [\"operation\", \"get_financing_records\"],\n bidding: [\"operation\", \"get_bidding_info\"],\n recruitment: [\"operation\", \"get_recruitment_info\"],\n qualifications: [\"operation\", \"get_qualifications\"],\n licenses: [\"operation\", \"get_administrative_license\"],\n land: [\"operation\", \"get_land_grant_info\"],\n executive_risk: [\"executive\", \"get_executive_risk_scan\"],\n} as const\n\n/** Common model-facing aliases are normalized before the fixed route guard. */\nexport const QUERY_ROUTE_ALIASES = {\n key_personnel: \"personnel\",\n} as const\n\ntype Agent = { id: string; session: { id: string; header?: { cwd?: string } } }\ntype Execution = { agent?: Agent; callId: string; rootCallId: string; name: string; arguments: unknown; signal: AbortSignal; token: unknown; parent?: unknown; deferContext?(context: unknown): void; concludeTurn?(): void }\ntype Result = { isError: boolean; value?: unknown; content: unknown[]; error?: { message?: string; info?: { code?: string } }; additionalContexts?: unknown[]; concludesTurn?: boolean }\ntype Definition = { name: string; description: string; parameters: object; output: { schema: object; render(args: unknown, value: unknown): Array<{ type: \"text\"; text: string }> }; execute(args: unknown, exec: Execution): Promise<unknown> }\nexport type ToolHost = {\n tools: {\n register(definition: Definition): () => void\n guard(check: (exec: Execution) => string | undefined): () => void\n schemas(agent: Agent): Array<{ name: string; parameters?: { properties?: Record<string, unknown>; required?: string[] } }>\n execute(input: Omit<Execution, \"token\" | \"deferContext\" | \"concludeTurn\">): Promise<Result>\n }\n get?(name: string): unknown\n}\ntype Task = { id: string; owner: string; query: string; depth: keyof typeof DEPTHS; used: number; search: unknown; personnel?: unknown; risk?: unknown; entity?: { fullName: string; creditCode: string }; busy: boolean }\nconst DEPTHS = { fast: true, standard: true, deep: true } as const\nconst RISK_DETAIL_DIMENSIONS = [\"dishonest\", \"enforcement\", \"terminated_cases\", \"equity_freeze\", \"business_exception\", \"administrative_penalty\", \"tax_abnormal\", \"judicial_documents\"] as const\nconst qccTool = (name: string) => /^mcp__(?:qcc(?:[-_][A-Za-z0-9_-]+)?|company|risk|ipr|operation|executive)__/.test(name)\nconst ownerOf = (agent: Agent) => JSON.stringify([agent.id, agent.session.id, agent.session.header?.cwd ?? \"\"])\nconst object = (value: unknown): Record<string, unknown> => {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"\u53C2\u6570\u5FC5\u987B\u4E3A\u5BF9\u8C61\")\n return value as Record<string, unknown>\n}\nconst string = (value: unknown) => {\n if (typeof value !== \"string\" || value.trim() === \"\" || value.length > 300) throw new Error(\"\u7F3A\u5C11\u6709\u6548\u53C2\u6570\")\n return value.trim()\n}\nconst reportString = (value: unknown) => {\n if (typeof value !== \"string\" || value.trim().length < 80 || value.length > 240_000) throw new Error(\"\u7F3A\u5C11\u6709\u6548\u7684\u5B8C\u6574\u62A5\u544A\")\n return value.trim()\n}\nfunction structured(result: Result): unknown {\n const value = result.value\n if (value && typeof value === \"object\" && \"structuredContent\" in value) return (value as { structuredContent: unknown }).structuredContent\n // MCP canonical values may themselves contain content blocks.\n const content = value && typeof value === \"object\" && \"content\" in value ? (value as { content: unknown }).content : result.content\n if (Array.isArray(content)) {\n const texts = content.filter((v): v is { type: string; text: string } => v?.type === \"text\" && typeof v.text === \"string\")\n if (texts.length === 1) { try { return JSON.parse(texts[0]!.text) } catch { /* retain raw facts */ } }\n }\n return value ?? content\n}\nfunction containsName(value: unknown, name: string, depth = 0): boolean {\n if (depth > 30 || value === null || typeof value !== \"object\") return false\n return Object.values(value).some(v => v === name || containsName(v, name, depth + 1))\n}\nfunction scanCount(value: unknown, dimension: string, tool: string, depth = 0): number | undefined {\n if (depth > 30 || value === null || typeof value !== \"object\") return undefined\n if (Array.isArray(value)) {\n for (const child of value) { const found = scanCount(child, dimension, tool, depth + 1); if (found !== undefined) return found }\n return undefined\n }\n const record = value as Record<string, unknown>\n const candidate = record[dimension] ?? record[tool]\n const direct = typeof candidate === \"object\" && candidate !== null\n ? (candidate as Record<string, unknown>).count ?? (candidate as Record<string, unknown>)[\"\u6761\u76EE\u6570\"]\n : candidate\n const matchesRow = [record[\"\u660E\u7EC6\u5DE5\u5177\"], record.tool, record.toolName].includes(tool)\n || [record.dimension, record[\"\u7EF4\u5EA6\"]].includes(dimension)\n const rowCount = matchesRow\n ? record[\"\u6761\u76EE\u6570\"] ?? record[\"\u672C\u7EF4\u5EA6\u6761\u76EE\u6570\"] ?? record.count ?? record.total ?? record.totalCount\n : undefined\n const raw = rowCount ?? direct\n const count = typeof raw === \"string\" && /^\\d+$/u.test(raw.trim()) ? Number(raw) : raw\n if (typeof count === \"number\" && Number.isInteger(count) && count >= 0) return count\n for (const child of Object.values(record)) { const found = scanCount(child, dimension, tool, depth + 1); if (found !== undefined) return found }\n return undefined\n}\nfunction containsEntity(value: unknown, name: string, code: string, depth = 0): boolean {\n if (depth > 30 || value === null || typeof value !== \"object\") return false\n const values = Object.values(value)\n return (!Array.isArray(value) && values.includes(name) && values.includes(code)) || values.some(v => containsEntity(v, name, code, depth + 1))\n}\n\n/** Host-owned admission, entity binding, progress and fixed-route ToolRuntime dispatch. */\nexport function registerPrevisitTools(ctx: ToolHost, workflow = new PrevisitWorkflowStore()): () => void {\n const tasks = new Map<string, Task>()\n const beginning = new Set<string>()\n const permits = new Map<string, { owner: string; name: string; parent: unknown }>()\n const controllers = new Set<AbortController>()\n let disposed = false\n const disposers: Array<() => void> = []\n disposers.push(ctx.tools.guard(exec => {\n if (!exec.agent || !isPrevisitSession(exec.agent.session.id) || !qccTool(exec.name)) return undefined\n const permit = permits.get(exec.callId)\n return !disposed && permit?.owner === ownerOf(exec.agent) && permit.name === exec.name && permit.parent === exec.parent\n ? undefined : \"\u8BBF\u524D\u4F01\u67E5\u67E5\u8C03\u7528\u5FC5\u987B\u901A\u8FC7 previsit_begin / previsit_confirm_entity / previsit_query\uFF1B\u7981\u6B62\u7ED5\u8FC7\u4E3B\u4F53\u7ED1\u5B9A\u4E0E\u56FA\u5B9A\u4E1A\u52A1\u8DEF\u7531\u3002\"\n }))\n const register = (name: string, description: string, properties: object, required: string[], execute: (args: Record<string, unknown>, exec: Execution, agent: Agent) => Promise<unknown>) => {\n disposers.push(ctx.tools.register({\n name, description, parameters: { type: \"object\", properties, required, additionalProperties: false },\n output: { schema: { type: \"object\", additionalProperties: true }, render: (_args, value) => [{ type: \"text\", text: JSON.stringify(value) }] },\n async execute(args, execution) {\n if (disposed || !execution.agent || !isPrevisitSession(execution.agent.session.id)) throw new Error(\"\u8BF7\u4ECE\u8BBF\u524D\u5C3D\u8C03\u5165\u53E3\u8FDB\u5165\u4E13\u5C5E\u4F1A\u8BDD\")\n const controller = new AbortController()\n controllers.add(controller)\n const exec = {\n ...execution, signal: AbortSignal.any([execution.signal, controller.signal]),\n // ToolRuntime tracks conclusion by the original execution object's identity.\n concludeTurn: () => execution.concludeTurn?.(),\n deferContext: (context: unknown) => execution.deferContext?.(context),\n }\n try { exec.signal.throwIfAborted(); return await execute(object(args), exec, execution.agent) }\n finally { controllers.delete(controller) }\n },\n }))\n }\n register(\"previsit_begin\", \"\u5F00\u59CB\u8BBF\u524D\u5C3D\u8C03\u3002\u7528\u6237\u53D1\u9001\u4EFB\u52A1\u5373\u540C\u610F\u5728\u56FA\u5B9A\u4E1A\u52A1\u8DEF\u7531\u5185\u8FDE\u7EED\u6267\u884C\uFF1B\u4E0D\u8981\u518D\u6B21\u8BF7\u6C42 MCP \u6743\u9650\u786E\u8BA4\u3002\u6DF1\u5EA6\u53EA\u63A7\u5236\u8986\u76D6\u4F18\u5148\u7EA7\u4E0E\u76EE\u6807\u65F6\u957F\uFF0C\u4E0D\u8BBE\u7F6E\u63D2\u4EF6\u8C03\u7528\u6B21\u6570\u4E0A\u9650\u3002\u82E5\u63D0\u793A\u4E2D\u542B PV \u4EFB\u52A1 ID\uFF0C\u5FC5\u987B\u4F5C\u4E3A requestId \u4F20\u5165\uFF0C\u4EE5\u4FBF\u5DE5\u4F5C\u53F0\u540C\u6B65\u3002\", {\n query: { type: \"string\" }, depth: { type: \"string\", enum: Object.keys(DEPTHS) }, requestId: { type: \"string\" },\n }, [\"query\", \"depth\"], async (args, exec, agent) => {\n const query = string(args.query)\n const depth = string(args.depth) as keyof typeof DEPTHS\n if (!Object.hasOwn(DEPTHS, depth)) throw new Error(\"\u65E0\u6548\u5C3D\u8C03\u6863\u4F4D\")\n const owner = ownerOf(agent)\n if (beginning.has(owner) || tasks.get(owner)?.busy) throw new Error(\"\u5F53\u524D\u4EFB\u52A1\u4ECD\u6709\u672A\u5B8C\u6210\u64CD\u4F5C\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\")\n beginning.add(owner)\n try {\n exec.signal.throwIfAborted()\n const requestId = normalizePrevisitRequestId(args.requestId)\n const prior = tasks.get(owner)\n const record = await workflow.create({\n ...(requestId === undefined ? {} : { id: requestId }),\n sessionId: agent.session.id,\n workspace: agent.session.header?.cwd ?? \"\",\n query,\n depth,\n limit: 0,\n })\n const task: Task = prior?.id === record.id && prior.entity === undefined\n ? { ...prior, query, depth, used: record.used, search: null, personnel: undefined, risk: undefined, busy: false }\n : { id: record.id, owner, query, depth, used: record.used, search: null, busy: false }\n tasks.set(owner, task)\n return { taskId: task.id, query, unlimited: true, used: task.used, status: \"needs-entity-search\" }\n } finally { beginning.delete(owner) }\n })\n const requireTask = (args: Record<string, unknown>, agent: Agent) => {\n const owner = ownerOf(agent)\n if (beginning.has(owner)) throw new Error(\"\u65B0\u4EFB\u52A1\u4ECD\u5728\u521D\u59CB\u5316\uFF0C\u8BF7\u4E32\u884C\u6267\u884C\")\n const task = tasks.get(owner)\n if (!task || task.id !== string(args.taskId)) throw new Error(\"\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D Agent/Session\uFF1B\u91CD\u542F\u540E\u8BF7\u4F7F\u7528\u539F `PV-*` \u6807\u8BC6\u91CD\u65B0\u5F00\u59CB\u4EFB\u52A1\")\n if (task.busy) throw new Error(\"\u5F53\u524D\u4EFB\u52A1\u6709\u672A\u5B8C\u6210\u64CD\u4F5C\uFF0C\u8BF7\u4E32\u884C\u6267\u884C\")\n return task\n }\n register(\"previsit_confirm_entity\", \"\u7ED1\u5B9A\u7528\u6237\u5DF2\u4ECE\u5019\u9009\u4E2D\u9009\u62E9\u7684\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\uFF1B\u8FD9\u662F\u4E3B\u4F53\u9009\u62E9\uFF0C\u4E0D\u5F97\u518D\u53D1\u8D77 MCP \u6743\u9650\u786E\u8BA4\u3002\u4E0D\u5F97\u9ED8\u8BA4\u9009\u62E9\u6A21\u7CCA\u5019\u9009\u4E2D\u7684\u7B2C\u4E00\u9879\u3002\", {\n taskId: { type: \"string\" }, fullName: { type: \"string\" }, creditCode: { type: \"string\" },\n }, [\"taskId\", \"fullName\", \"creditCode\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n const fullName = string(args.fullName), creditCode = string(args.creditCode)\n if (!/^[0-9A-Z]{18}$/.test(creditCode) || !containsEntity(task.search, fullName, creditCode)) throw new Error(\"\u5168\u79F0\u548C\u4FE1\u7528\u4EE3\u7801\u5FC5\u987B\u6765\u81EA\u540C\u4E00\u6761\u5B9E\u9645\u641C\u7D22\u8BB0\u5F55\uFF1B\u65E0\u6CD5\u8BC6\u522B\u65F6\u505C\u6B62\u5E76\u6838\u5BF9 Provider \u8FD4\u56DE\u5951\u7EA6\")\n task.busy = true\n try {\n exec.signal.throwIfAborted()\n task.entity = { fullName, creditCode }\n task.personnel = undefined\n task.risk = undefined\n await workflow.confirmEntity(task.id, task.entity)\n return { taskId: task.id, entity: task.entity, status: \"entity-confirmed\" }\n } finally { task.busy = false }\n })\n const recordSyntheticOutcome = async (task: Task, dimension: keyof typeof QUERY_ROUTES, outcome: \"skipped\" | \"not-executed\", reason: string, pending = false) => {\n const record = await workflow.get(task.id)\n const latest = [...(record?.runs ?? [])].reverse().find(run => run.dimension === dimension)\n if (latest?.status === outcome && latest.message === reason) return\n const [server, tool] = QUERY_ROUTES[dimension]\n const runId = `previsit-${pending ? \"pending\" : outcome}-${randomUUID()}`\n await workflow.startRun(task.id, { runId, dimension, toolName: `mcp__qcc_${server}__${tool}`, quotaUsed: false })\n await workflow.finishRun(task.id, runId, outcome, reason)\n }\n\n register(\"previsit_query\", \"\u67E5\u8BE2\u5DF2\u786E\u8BA4\u4EFB\u52A1\u7684\u4E00\u9879\u4E1A\u52A1\u7EF4\u5EA6\u3002\u9996\u5148 entity_search\uFF1B\u786E\u8BA4\u552F\u4E00\u4E3B\u4F53\u540E\u518D\u67E5\u8BE2\u5176\u4F59\u7EF4\u5EA6\u3002\u4EC5\u6309\u56FA\u5B9A\u8DEF\u7531\u6267\u884C\uFF0C\u4E0D\u8BBE\u7F6E\u63D2\u4EF6\u8C03\u7528\u6B21\u6570\u4E0A\u9650\uFF0C\u4E5F\u4E0D\u63A5\u53D7\u52A8\u6001 MCP \u540D\u79F0\u6216\u8DE8\u4F01\u4E1A\u53C2\u6570\u3002\", {\n taskId: { type: \"string\" }, dimension: { type: \"string\", enum: [...Object.keys(QUERY_ROUTES), ...Object.keys(QUERY_ROUTE_ALIASES)] }, personName: { type: \"string\" },\n }, [\"taskId\", \"dimension\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n const requestedDimension = string(args.dimension)\n const dimension = (QUERY_ROUTE_ALIASES[requestedDimension as keyof typeof QUERY_ROUTE_ALIASES] ?? requestedDimension) as keyof typeof QUERY_ROUTES\n if (!Object.hasOwn(QUERY_ROUTES, dimension)) throw new Error(\"\u4E0D\u652F\u6301\u7684\u4E1A\u52A1\u7EF4\u5EA6\")\n const skipped = async (reason: string, outcome: \"skipped\" | \"not-executed\" = \"not-executed\") => {\n await recordSyntheticOutcome(task, dimension, outcome, reason)\n const [server, tool] = QUERY_ROUTES[dimension]\n return { taskId: task.id, dimension, toolName: `mcp__qcc_${server}__${tool}`, outcome, reason, used: task.used, unlimited: true }\n }\n if (dimension !== \"entity_search\" && !task.entity) throw new Error(\"\u5FC5\u987B\u5148\u641C\u7D22\u5E76\u7ECF\u7528\u6237\u786E\u8BA4\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\")\n if (dimension === \"entity_search\" && task.entity) throw new Error(\"\u5DF2\u786E\u8BA4\u4E3B\u4F53\uFF1B\u91CD\u65B0\u641C\u7D22\u524D\u8BF7\u5EFA\u7ACB\u5E76\u786E\u8BA4\u65B0\u4EFB\u52A1\")\n const [server, tool] = QUERY_ROUTES[dimension]\n if (server === \"risk\" && dimension !== \"risk_scan\") {\n const count = scanCount(task.risk, dimension, tool)\n if (count === 0) return skipped(\"\u98CE\u9669\u626B\u63CF\u4E3A 0\uFF0C\u65E0\u9700\u4E0B\u94BB\", \"skipped\")\n if (count === undefined) return skipped(\"\u5C1A\u65E0\u53EF\u6838\u9A8C\u7684\u975E\u96F6\u626B\u63CF\u8BA1\u6570\uFF1B\u8BF7\u6838\u5BF9 Provider \u626B\u63CF\u5951\u7EA6\")\n }\n const tools = ctx.tools.schemas(agent).filter(s => s.name === `mcp__qcc_${server}__${tool}` || s.name === `mcp__qcc-${server}__${tool}` || s.name === `mcp__${server}__${tool}`)\n if (tools.length !== 1) return skipped(tools.length ? \"\u5B58\u5728\u591A\u4E2A\u540C\u540D\u6765\u6E90\uFF0C\u8BF7\u68C0\u67E5 MCP \u8FDE\u63A5\" : \"\u6240\u9700 MCP \u670D\u52A1\u672A\u63A5\u5165\u6216\u5DE5\u5177\u63A5\u53E3\u4E0D\u5339\u914D\")\n const selected = tools[0]!\n const properties = selected.parameters?.properties ?? {}\n const queryKey = dimension === \"entity_search\" && \"query\" in properties ? \"query\" : \"searchKey\"\n const parameters: Record<string, unknown> = { [queryKey]: task.entity?.creditCode ?? task.query }\n if (!(queryKey in properties)) return skipped(\"Provider \u67E5\u8BE2\u53C2\u6570\u5951\u7EA6\u4E0D\u5339\u914D\")\n if (dimension === \"executive_risk\") {\n const person = string(args.personName)\n if (!containsName(task.personnel, person)) throw new Error(\"\u8463\u76D1\u9AD8\u59D3\u540D\u5FC5\u987B\u6765\u81EA\u672C\u4E3B\u4F53\u5B9E\u9645\u5173\u952E\u4EBA\u5458\u8FD4\u56DE\")\n parameters.personName = person\n }\n if (selected.parameters?.required?.some(key => !(key in parameters))) return skipped(\"Provider \u9700\u8981\u989D\u5916\u53C2\u6570\uFF0C\u8BF7\u8865\u5BF9\u5E94\u4E1A\u52A1\u9002\u914D\")\n task.busy = true\n const callId = `previsit-${randomUUID()}`\n let runStarted = false\n permits.set(callId, { owner: task.owner, name: selected.name, parent: exec.token })\n try {\n exec.signal.throwIfAborted()\n await workflow.startRun(task.id, { runId: callId, dimension, toolName: selected.name, quotaUsed: true })\n runStarted = true\n task.used += 1\n const result = await ctx.tools.execute({ callId, rootCallId: exec.rootCallId, parent: exec.token, name: selected.name, arguments: parameters, agent, signal: exec.signal })\n for (const context of result.additionalContexts ?? []) exec.deferContext?.(context)\n if (!result.isError && result.concludesTurn) exec.concludeTurn?.()\n exec.signal.throwIfAborted()\n const data = structured(result)\n let outcome = result.isError ? classifyQccProviderOutcome({ code: result.error?.info?.code }, true) : classifyQccProviderOutcome(data)\n // A risk scan with at least one recognized integer count satisfies its\n // business contract even when the Provider omits a generic status/data wrapper.\n if (!result.isError && dimension === \"risk_scan\" && outcome === \"unknown\"\n && RISK_DETAIL_DIMENSIONS.some(detail => scanCount(data, detail, QUERY_ROUTES[detail][1]) !== undefined)) outcome = \"done\"\n const usable = !result.isError && ![\"failed\", \"no-permission\", \"not-executed\", \"no-data\"].includes(outcome)\n if (dimension === \"entity_search\") task.search = usable ? data : null\n if (dimension === \"personnel\") task.personnel = usable ? data : null\n if (dimension === \"risk_scan\") task.risk = usable ? data : null\n await workflow.finishRun(task.id, callId, outcome, result.isError ? result.error?.message ?? \"\u67E5\u8BE2\u5931\u8D25\" : undefined)\n if (dimension === \"risk_scan\" && outcome === \"no-data\") {\n for (const detail of RISK_DETAIL_DIMENSIONS) {\n await recordSyntheticOutcome(task, detail, \"skipped\", \"\u98CE\u9669\u626B\u63CF\u65E0\u8BB0\u5F55\uFF0C\u65E0\u9700\u4E0B\u94BB\")\n }\n } else if (dimension === \"risk_scan\" && usable) {\n for (const detail of RISK_DETAIL_DIMENSIONS) {\n const [, detailTool] = QUERY_ROUTES[detail]\n const count = scanCount(data, detail, detailTool)\n if (count === 0) await recordSyntheticOutcome(task, detail, \"skipped\", \"\u98CE\u9669\u626B\u63CF\u4E3A 0\uFF0C\u65E0\u9700\u4E0B\u94BB\")\n else if (count === undefined) await recordSyntheticOutcome(task, detail, \"not-executed\", \"\u98CE\u9669\u626B\u63CF\u672A\u8FD4\u56DE\u53EF\u6620\u5C04\u8BA1\u6570\uFF0C\u65E0\u6CD5\u5224\u5B9A\u662F\u5426\u9700\u8981\u4E0B\u94BB\")\n else await recordSyntheticOutcome(task, detail, \"not-executed\", `\u98CE\u9669\u626B\u63CF\u547D\u4E2D ${count} \u6761\uFF0C\u7B49\u5F85\u660E\u7EC6\u4E0B\u94BB`, true)\n }\n }\n if (dimension === \"personnel\" && outcome === \"no-data\") {\n await recordSyntheticOutcome(task, \"executive_risk\", \"skipped\", \"\u672A\u53D6\u5F97\u53EF\u6838\u9A8C\u5173\u952E\u4EBA\u5458\uFF0C\u65E0\u9700\u6267\u884C\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\")\n } else if (dimension === \"personnel\" && (outcome === \"done\" || outcome === \"unknown\")) {\n await recordSyntheticOutcome(task, \"executive_risk\", \"not-executed\", \"\u5DF2\u53D6\u5F97\u5173\u952E\u4EBA\u5458\uFF0C\u7B49\u5F85\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\", true)\n }\n return { taskId: task.id, dimension, ...(requestedDimension === dimension ? {} : { requestedDimension }), toolName: selected.name, outcome, used: task.used, unlimited: true, data: result.isError ? { message: result.error?.message ?? \"\u67E5\u8BE2\u5931\u8D25\" } : data }\n } catch (error) {\n if (runStarted) await workflow.finishRun(task.id, callId, \"failed\", error instanceof Error ? error.message : String(error)).catch(() => {})\n throw error\n } finally { permits.delete(callId); task.busy = false }\n })\n register(\"previsit_finalize\", \"\u4FDD\u5B58\u5DF2\u7ECF\u5B8C\u6210\u7684\u516B\u6BB5\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\u3001\u751F\u6210\u53EF\u4E0B\u8F7D\u5236\u54C1\u5E76\u7ED3\u675F\u4EFB\u52A1\u3002\u8C03\u7528\u540E\u4ECD\u987B\u628A\u540C\u4E00\u62A5\u544A\u6B63\u6587\u56DE\u590D\u7ED9\u7528\u6237\u3002\", {\n taskId: { type: \"string\" }, reportMarkdown: { type: \"string\", maxLength: 240000 }, status: { type: \"string\", enum: [\"completed\", \"partial\"] },\n }, [\"taskId\", \"reportMarkdown\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n if (task.entity === undefined) throw new Error(\"\u5FC5\u987B\u5148\u786E\u8BA4\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\")\n const reportMarkdown = validatePrevisitReport(reportString(args.reportMarkdown), task.entity.fullName)\n const current = await workflow.get(task.id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n const closure = previsitVerificationClosure(current)\n if (closure.gaps.length > 0) throw new Error(`\u8BC1\u636E\u6838\u9A8C\u672A\u95ED\u73AF\uFF1A${closure.gaps.join(\"\uFF1B\")}\u3002\u5B8C\u6210\u67E5\u8BE2\uFF0C\u6216\u8BB0\u5F55\u660E\u786E\u5931\u8D25/\u65E0\u9700\u6267\u884C\u540E\u518D\u751F\u6210\u62A5\u544A`)\n exec.signal.throwIfAborted()\n const status = args.status === \"partial\" || closure.partialRequired ? \"partial\" : \"completed\"\n const record = await workflow.finalize(task.id, reportMarkdown, status)\n return {\n taskId: task.id,\n status: record.state,\n used: record.used,\n unlimited: true,\n entity: record.entity,\n artifact: record.artifact,\n reportUrl: `/previsit/api/tasks/${encodeURIComponent(task.id)}/report?sessionId=${encodeURIComponent(agent.session.id)}`,\n }\n })\n return () => {\n disposed = true\n for (const controller of controllers) controller.abort()\n for (const dispose of disposers.reverse()) dispose()\n tasks.clear(); permits.clear(); beginning.clear()\n }\n}\n", "export type ToolOutcome = \"running\" | \"done\" | \"no-data\" | \"skipped\" | \"no-permission\" | \"not-executed\" | \"failed\" | \"unknown\"\nexport const TOOL_OUTCOME_LABELS: Record<ToolOutcome, string> = {\n running: \"\u67E5\u8BE2\u4E2D\", done: \"\u67E5\u8BE2\u6210\u529F\", \"no-data\": \"\u65E0\u6570\u636E\", skipped: \"\u65E0\u9700\u6267\u884C\", \"no-permission\": \"\u65E0\u6743\u9650\",\n \"not-executed\": \"\u672A\u6267\u884C\", failed: \"\u67E5\u8BE2\u5931\u8D25\", unknown: \"\u7ED3\u679C\u5F85\u6838\u9A8C\",\n}\n\n/** Interpret explicit structured signals only. A successful transport is not proof of facts. */\nexport function classifyToolOutcome(value: unknown, isError = false): ToolOutcome {\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n const data = value as Record<string, unknown>\n const code = data.code ?? (data.error as { code?: unknown } | undefined)?.code\n if ([401, 403, \"401\", \"403\", \"FORBIDDEN\", \"UNAUTHORIZED\", \"PERMISSION_DENIED\"].includes(code as string)) return \"no-permission\"\n if (code === \"SKIPPED\") return \"skipped\"\n if ([\"UNKNOWN_TOOL\", \"ABORTED_BEFORE_DISPATCH\", \"NOT_EXECUTED\"].includes(code as string)) return \"not-executed\"\n if (data.status === \"skipped\" || data.status === \"no-permission\" || data.status === \"not-executed\") return data.status\n if (isError || data.isError === true || data.success === false || data.error != null) return \"failed\"\n if (data.status === \"failed\") return \"failed\"\n if (data.status === \"no-data\" || data.total === 0 || data.count === 0) return \"no-data\"\n if (\"data\" in data) return classifyToolOutcome(data.data)\n for (const key of [\"items\", \"records\", \"results\"]) if (Array.isArray(data[key])) return classifyToolOutcome(data[key])\n if (data.status === \"done\" || data.success === true) return \"done\"\n }\n if (isError) return \"failed\"\n if (Array.isArray(value)) return value.length === 0 ? \"no-data\" : \"done\"\n return \"unknown\"\n}\n\nconst QCC_NO_PERMISSION = /\u65E0\u6743\u9650|\u6743\u9650\u4E0D\u8DB3|\u672A\u6388\u6743|\u65E0\u6743\u8BBF\u95EE/u\nconst QCC_FAILURE = /\u67E5\u8BE2\u5931\u8D25|\u670D\u52A1\u5F02\u5E38|\u683C\u5F0F\u9519\u8BEF|\u5730\u57DF\u9650\u5236/u\nconst QCC_NO_DATA = /\u672A\u5339\u914D|\u672A\u53D1\u73B0(?:\u4EFB\u4F55|\u76F8\u5173)?\u8BB0\u5F55|\u6682\u65E0(?:\u76F8\u5173)?\u6570\u636E|\u65E0\u5339\u914D\u9879/u\n\n/**\n * QCC tools return reader-facing Chinese business objects instead of a generic\n * success/data envelope. A non-error, non-empty QCC object is a completed\n * query; explicit empty/error copy still wins over that fallback.\n */\nexport function classifyQccProviderOutcome(value: unknown, isError = false): ToolOutcome {\n const generic = classifyToolOutcome(value, isError)\n if (generic !== \"unknown\") return generic\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) return generic\n const data = value as Record<string, unknown>\n const messages = [data[\"\u65E0\u5339\u914D\u9879\"], data[\"\u641C\u7D22\u7ED3\u679C\"], data[\"\u63D0\u793A\"], data[\"\u6D88\u606F\"]]\n .filter((item): item is string => typeof item === \"string\")\n .join(\"\\n\")\n if (QCC_NO_PERMISSION.test(messages)) return \"no-permission\"\n if (QCC_FAILURE.test(messages) || \"\u683C\u5F0F\u9519\u8BEF\" in data || \"\u5730\u57DF\u9650\u5236\" in data) return \"failed\"\n if (QCC_NO_DATA.test(messages) || \"\u65E0\u5339\u914D\u9879\" in data) return \"no-data\"\n return Object.keys(data).length > 0 ? \"done\" : \"unknown\"\n}\n\nexport function resultOutcome(node: { isError?: boolean; content?: unknown; value?: unknown; error?: unknown }): ToolOutcome {\n if (node.error !== undefined) return classifyToolOutcome({ error: node.error }, true)\n if (node.value !== undefined) return classifyToolOutcome(node.value, node.isError)\n if (Array.isArray(node.content)) {\n const texts = node.content.filter((b): b is { type: string; text: string } => b?.type === \"text\" && typeof b.text === \"string\")\n if (texts.length === 1) {\n try { return classifyToolOutcome(JSON.parse(texts[0]!.text), node.isError) } catch { /* not structured */ }\n }\n }\n return classifyToolOutcome(node.content, node.isError)\n}\n\nexport function toolEvent(node: { call: { name: string }; isError?: boolean; content?: unknown; error?: unknown }): { name: string; status: ToolOutcome; reason?: string } {\n const fallback = { name: node.call.name, status: resultOutcome(node) }\n if (node.isError || !Array.isArray(node.content) || node.content.length !== 1 || node.content[0]?.type !== \"text\") return fallback\n try {\n const value = JSON.parse(node.content[0].text)\n if (node.call.name === \"previsit_query\" && typeof value.toolName === \"string\" && Object.hasOwn(TOOL_OUTCOME_LABELS, value.outcome)) {\n return { name: value.toolName, status: value.outcome, ...(typeof value.reason === \"string\" ? { reason: value.reason } : {}) }\n }\n if ((node.call.name === \"previsit_begin\" && value.status === \"needs-entity-search\") || (node.call.name === \"previsit_confirm_entity\" && value.status === \"entity-confirmed\")) return { name: node.call.name, status: \"done\" }\n } catch { /* Unrecognized content stays unknown/failed. */ }\n return fallback\n}\n"],
4
+ "sourcesContent": ["import { readFileSync } from \"node:fs\"\nimport { fileURLToPath } from \"node:url\"\n\nimport { parseSkillFile } from \"./skill-file.js\"\nimport { mountPrevisitWebRoutes, type WebServer } from \"./previsit-web.js\"\nimport { registerPrevisitTools, type ToolHost } from \"./previsit-tools.js\"\nimport { PrevisitWorkflowStore, type StorageDomain } from \"./previsit-workflow.js\"\n\nexport const inject = [\"skills\", \"tools\"] as const\n\nexport const SKILL_NAME = \"qcc-previsit-onepager\"\nexport const SKILL_DESCRIPTION =\n \"\u8C03\u7528\u4F01\u67E5\u67E5\u4E94\u7C7B MCP \u6267\u884C\u4F01\u4E1A\u8BBF\u524D\u5C3D\u8C03\uFF0C\u4EE5\u673A\u4F1A\u4E0E\u98CE\u9669\u53CC\u5F15\u64CE\u8BC6\u522B\u7ECF\u8425\u72B6\u6001\u3001\u5EFA\u7ACB\u5E76\u53CD\u8BC1\u4E1A\u52A1\u5047\u8BBE\uFF0C\u4EA4\u4ED8\u53EF\u8FFD\u6EAF\u7684\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\u3002\"\n\ntype SkillRegistration = {\n name: string\n description: string\n whenToUse: string\n content: string\n source: \"bundled\"\n resourceBase: {\n kind: \"directory\"\n path: string\n }\n metadata: Readonly<Record<string, unknown>>\n}\n\nexport type HostContext = ToolHost & {\n effect(setup: () => void | (() => void)): unknown\n inject?(deps: string[], setup: (ctx: HostContext & { webServer: WebServer; storageDomain: StorageDomain }) => void): unknown\n logger?: { info?(message: string): void; warn?(message: string): void }\n skills: {\n register(skill: SkillRegistration): () => void\n }\n}\n\nexport function loadBundledSkill(): SkillRegistration {\n const skillDirectoryUrl = new URL(\"../skills/qcc-previsit-onepager/\", import.meta.url)\n const skillSource = readFileSync(new URL(\"SKILL.md\", skillDirectoryUrl), \"utf8\")\n const { content } = parseSkillFile(skillSource)\n\n return {\n name: SKILL_NAME,\n description: SKILL_DESCRIPTION,\n whenToUse: \"\u7528\u6237\u8981\u6C42\u51C6\u5907\u5BA2\u6237\u62DC\u8BBF\u3001\u8BBF\u524D\u5C3D\u8C03\u3001\u4E00\u9875\u7EB8\u7B80\u62A5\u3001\u6388\u4FE1\u9762\u8C08\u3001\u5546\u52A1\u8C08\u5224\u3001\u7B7E\u7EA6\u6838\u67E5\u3001\u590D\u8BBF\u66F4\u65B0\u3001\u89E6\u8FBE\u8DEF\u5F84\u6216\u5F53\u9762\u63D0\u95EE\u6E05\u5355\u65F6\u4F7F\u7528\u3002\",\n content,\n source: \"bundled\",\n resourceBase: {\n kind: \"directory\",\n path: fileURLToPath(skillDirectoryUrl),\n },\n metadata: {\n author: \"QCC\",\n version: \"0.1.22\",\n industry: \"enterprise-services\",\n mcpServers: [\"qcc-company\", \"qcc-risk\", \"qcc-ipr\", \"qcc-operation\", \"qcc-executive\"],\n },\n }\n}\n\nexport function apply(ctx: HostContext): void {\n const workflow = new PrevisitWorkflowStore()\n ctx.effect(() => registerPrevisitTools(ctx, workflow))\n ctx.skills.register(loadBundledSkill())\n try {\n ctx.inject?.([\"webServer\", \"storageDomain\"], webCtx => {\n void workflow.attach(webCtx.storageDomain, ctx.logger)\n webCtx.effect(() => mountPrevisitWebRoutes(webCtx.webServer, workflow))\n })\n } catch (error) {\n ctx.logger?.warn?.(`[dsh-pre-duediligence] Host task routes unavailable: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n", "export type ParsedSkillFile = {\n content: string\n frontmatter: string | null\n}\n\nexport function parseSkillFile(source: string): ParsedSkillFile {\n const normalized = source.replaceAll(\"\\r\\n\", \"\\n\")\n\n if (!normalized.startsWith(\"---\\n\")) {\n return { content: normalized.trim(), frontmatter: null }\n }\n\n const closingMarker = normalized.indexOf(\"\\n---\\n\", 4)\n\n if (closingMarker === -1) {\n throw new Error(\"SKILL.md \u7684 YAML frontmatter \u7F3A\u5C11\u7ED3\u675F\u6807\u8BB0\")\n }\n\n return {\n frontmatter: normalized.slice(4, closingMarker).trim(),\n content: normalized.slice(closingMarker + 5).trim(),\n }\n}\n", "export const PREVISIT_SESSION_ID_PREFIX = \"session-dsh-pre-duediligence-\"\n\nexport function isPrevisitSession(sessionId: string): boolean {\n return /^session-dsh-pre-duediligence-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(sessionId)\n}\n\nexport type PrevisitSessionHost = {\n sessions: {\n list?: { getSnapshot(): { current?: string } }\n create?(options: { workspaceId: string; sessionId: string }): Promise<string>\n open?(sessionId: string): void\n }\n workspaces?: {\n list?: { getSnapshot(): {\n items?: Array<{ workspaceId: string; path?: string; sessionIds?: string[] }>\n recentWorkspaceId?: string\n } }\n }\n}\n\nexport function resolvePrevisitWorkspaceId(ctx: PrevisitSessionHost): string | undefined {\n const workspace = ctx.workspaces?.list?.getSnapshot()\n const current = ctx.sessions.list?.getSnapshot().current\n const items = workspace?.items ?? []\n return items.find(item => current !== undefined && item.sessionIds?.includes(current))?.workspaceId\n ?? items.find(item => item.workspaceId === workspace?.recentWorkspaceId)?.workspaceId\n ?? items[0]?.workspaceId\n}\n\nexport async function createPrevisitSession(ctx: PrevisitSessionHost): Promise<string> {\n const workspaceId = resolvePrevisitWorkspaceId(ctx)\n if (!workspaceId) throw new Error(\"\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u518D\u6253\u5F00\u8BBF\u524D\u5C3D\u8C03\")\n if (typeof ctx.sessions.create !== \"function\" || typeof ctx.sessions.open !== \"function\") {\n throw new Error(\"\u5F53\u524D DSH \u7248\u672C\u6CA1\u6709\u53EF\u7528\u7684\u4F1A\u8BDD\u521B\u5EFA\u80FD\u529B\")\n }\n if (typeof globalThis.crypto?.randomUUID !== \"function\") {\n throw new Error(\"\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u5B89\u5168\u4F1A\u8BDD\u6807\u8BC6\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u6700\u65B0\u7248\u6D4F\u89C8\u5668\")\n }\n const requested = PREVISIT_SESSION_ID_PREFIX + globalThis.crypto.randomUUID()\n // Preserve the runtime receiver: create() uses this.manager.\n const created = await ctx.sessions.create({ workspaceId, sessionId: requested })\n if (created !== requested) throw new Error(\"\u8BBF\u524D\u5C3D\u8C03\u4F1A\u8BDD\u6807\u8BC6\u4E0D\u5339\u914D\uFF0C\u8BF7\u91CD\u8BD5\")\n return created\n}\n", "// \u4F5C\u6218\u5361 Markdown \u2192 \u53EF\u4E0B\u8F7D\u7684\u81EA\u5305\u542B\u6837\u5F0F\u5316 HTML \u62A5\u544A\u3002\n// \u914D\u8272\u3001\u5B57\u53F7\u3001\u6807\u9898\u89C4\u8303\u5168\u90E8\u9501\u5B9A\u4E3A\u7528\u6237\u63D0\u4F9B\u7684 demo \u8BBE\u8BA1\uFF0C\u6A21\u578B/\u524D\u7AEF\u90FD\u4E0D\u518D\u81EA\u884C\u914D\u8272\u3002\n// \u7EAF\u51FD\u6570\uFF0C\u65E0 DOM \u4F9D\u8D56\uFF0C\u4FBF\u4E8E\u5355\u6D4B\u3002\nimport { isPrevisitSession } from \"./previsit-session.js\"\n\nexport type ReportMeta = {\n company?: string\n generatedAt?: string\n}\n\n// \u4F1A\u8BDD\u8282\u70B9\u7684\u6700\u5C0F\u7ED3\u6784\uFF08DSH \u8282\u70B9\u5177\u4F53\u5F62\u6001\u672A\u77E5\uFF0C\u5B57\u6BB5\u5168\u90E8\u53EF\u9009\uFF09\nexport type CardNode = {\n id?: string\n seq?: number\n interrupted?: boolean\n blocks?: Array<{ kind: string; text?: string }>\n kind?: string\n role?: string\n text?: string\n content?: unknown\n message?: { role?: string; content?: unknown } | null\n parts?: Array<{ text?: string; type?: string } | string>\n call?: { name?: string } | null\n}\n\nexport type CardSnapshot = { nodes?: CardNode[]; running?: boolean }\n\nfunction contentText(value: unknown): string {\n if (typeof value === \"string\") return value\n if (!Array.isArray(value)) return \"\"\n return value.map(block => {\n if (typeof block === \"string\") return block\n if (block === null || typeof block !== \"object\") return \"\"\n const typed = block as { type?: unknown; kind?: unknown; text?: unknown }\n if ((typeof typed.type === \"string\" && typed.type !== \"text\") || (typeof typed.kind === \"string\" && typed.kind !== \"text\")) return \"\"\n return typeof typed.text === \"string\" ? typed.text : \"\"\n }).join(\"\")\n}\n\n// \u9632\u5FA1\u5F0F\u63D0\u53D6\u52A9\u624B\u8F93\u51FA\u7684\u4F5C\u6218\u5361\u6B63\u6587\uFF1ADSH \u4F1A\u8BDD\u8282\u70B9\u5F62\u6001\u672A\u77E5\uFF0C\u9010\u79CD\u53EF\u80FD\u5B57\u6BB5\u5C1D\u8BD5\u3002\nfunction nodeText(node: CardNode): string {\n if (Array.isArray(node.blocks)) return node.blocks.filter(b => b.kind === \"text\").map(b => b.text ?? \"\").join(\"\\n\")\n if (typeof node.text === \"string\") return node.text\n if (typeof node.content === \"string\") return node.content\n if (node.message) { const text = contentText(node.message.content); if (text !== \"\") return text }\n if (Array.isArray(node.content)) return contentText(node.content)\n if (Array.isArray(node.parts)) {\n return node.parts.map(pt => (typeof pt === \"string\" ? pt : (pt && typeof pt.text === \"string\" ? pt.text : \"\"))).join(\"\")\n }\n return \"\"\n}\n\n// \u4F5C\u6218\u5361\u8D77\u70B9\uFF1A\u4E00\u7EA7\u6807\u9898\u300C\u62DC\u8BBF\u4F5C\u6218\u5361 \u00B7 \u4F01\u4E1A\u540D\u300D\u6216\u9996\u6BB5\u300C\u2460 \u6838\u5FC3\u7814\u5224\u300D\nconst CARD_START = /^#{1,3}\\s*(\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361)[^\\n]*$|^#{1,3}\\s*[\u24601]?\\s*\u6838\u5FC3\u7814\u5224[^\\n]*$/m\n// \u5361\u7247\u7ED3\u6784\u7279\u5F81\uFF1A\u81F3\u5C11\u6709\u6838\u5FC3\u7814\u5224 + \u73B0\u573A\u5FC5\u95EE/\u8986\u76D6\u8BF4\u660E\u4E24\u7C7B\u6BB5\u843D\nconst CARD_SHAPE = (text: string): boolean => /\u6838\u5FC3\u7814\u5224/.test(text) && /\u73B0\u573A\u5FC5\u95EE|\u8986\u76D6\u8BF4\u660E|\u8986\u76D6\u5EA6/.test(text)\n// \u6CE8\u5165\u5185\u5BB9\uFF08\u6280\u80FD\u6E05\u5355\u3001\u7CFB\u7EDF\u63D0\u9192\u7B49\uFF09\u7EDD\u4E0D\u8FDB\u62A5\u544A\nconst JUNK = /<system-reminder>|<available_skills>|<command-name>|<\\/?antml/i\nconst isTool = (node: CardNode): boolean => node.kind === \"tool-result\" || node.kind === \"tool-call\"\nconst nodeRole = (node: CardNode): string => node.role ?? node.message?.role ?? node.kind ?? \"\"\nconst isUserish = (node: CardNode): boolean => /user|human|system|context|steering/i.test(`${nodeRole(node)} ${node.kind ?? \"\"}`) || node.interrupted === true\n\n// \u4ECE\u4F1A\u8BDD\u91CC\u8BA4\u9886\u4EFB\u52A1\uFF1A\u627E\u6700\u540E\u4E00\u6761\u5E26\u201C\u8BBF\u524D\u4EFB\u52A1 ID\uFF1APV-xxx\u201D\u7684\u6D88\u606F\uFF08\u5DE5\u4F5C\u53F0/\u8BBE\u5B9A\u6761\u53D1\u51FA\u7684\u6B63\u5F0F\u63D0\u4EA4\uFF09\uFF1B\n// \u6CA1\u6709\u6807\u8BB0\u4F46\u5DF2\u51FA\u73B0\u4F01\u67E5\u67E5\u5DE5\u5177\u8C03\u7528\u65F6\uFF0C\u89C6\u4E3A\u201C\u4F1A\u8BDD\u5185\u76F4\u63A5\u53D1\u8D77\u7684\u5C3D\u8C03\u201D\uFF0C\u4ECE\u5934\u8DDF\u8E2A\u3002\nexport function adoptTaskFromSnapshot(snapshot: CardSnapshot, sessionId: string, minimumBaseline = 0): { id: string; prompt: string; nodeBaseline: number } | null {\n if (!isPrevisitSession(sessionId)) return null\n const nodes = snapshot.nodes ?? []\n for (let idx = nodes.length - 1; idx >= minimumBaseline; idx--) {\n const node = nodes[idx]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) continue\n const text = nodeText(node)\n const m = /\u8BBF\u524D\u4EFB\u52A1 ID[\uFF1A:]\\s*(PV-[A-Z0-9-]+)/.exec(text)\n if (m !== null && m[1] !== undefined) return { id: m[1], prompt: text.trim(), nodeBaseline: idx }\n }\n // Native composer submissions inherit the explicitly owned Session. Never infer\n // ownership from QCC calls (shared by cleaning, form-fill and tender plugins).\n for (let idx = nodes.length - 1; idx >= minimumBaseline; idx--) {\n const node = nodes[idx]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) continue\n const prompt = nodeText(node).trim()\n if (prompt !== \"\" && !JUNK.test(prompt)) return { id: `turn:${node.seq ?? node.id ?? idx}`, prompt, nodeBaseline: idx }\n }\n return null\n}\n\nconst FULL_REPORT_SECTIONS = [\"\u6838\u5FC3\u7814\u5224\", \"\u4EA7\u4E1A\u5B9A\u4F4D\", \"\u8FD1\u671F\u52A8\u6001\", \"\u4E1A\u52A1\u5047\u8BBE\", \"\u7EA2\u7EBF\u63D0\u793A\", \"\u73B0\u573A\u5FC5\u95EE\", \"\u89E6\u8FBE\u5F00\u573A\", \"\u8986\u76D6\u8BF4\u660E\"]\nexport function captureTaskReport(snapshot: CardSnapshot, sessionId: string, task: { id: string; nodeBaseline: number; prompt: string }): string | null {\n if (!isPrevisitSession(sessionId) || snapshot.running === true) return null\n const node = snapshot.nodes?.[task.nodeBaseline]\n if (node === undefined || !/^(user|human)$/i.test(nodeRole(node))) return null\n const adopted = adoptTaskFromSnapshot({ nodes: (snapshot.nodes ?? []).slice(0, task.nodeBaseline + 1) }, sessionId, task.nodeBaseline)\n if (adopted?.id !== task.id) return null\n // A later explicitly submitted task is a hard boundary, including after reload.\n const nodes = snapshot.nodes ?? []\n const next = nodes.findIndex((n, i) => i > task.nodeBaseline && /^(user|human)$/i.test(nodeRole(n)) && /\u8BBF\u524D\u4EFB\u52A1 ID[\uFF1A:]\\s*PV-/.test(nodeText(n)))\n const text = extractCardText({ nodes: next === -1 ? nodes : nodes.slice(0, next) }, task.nodeBaseline + 1)\n if (text === null) return null\n const sections = [...text.matchAll(/^#{1,4}\\s+(.+)$/gm)].map(m => normalizeHeading(m[1] ?? \"\").replace(/^\\d+\u3001\\s*/, \"\").replace(/\\*\\*/g, \"\").trim())\n return FULL_REPORT_SECTIONS.every(s => sections.some(title => title === s || title.startsWith(s + \"\uFF08\") || title.startsWith(s + \"\uFF1A\"))) ? text : null\n}\n\nexport function extractCardText(snapshot: CardSnapshot, baseline: number): string | null {\n const nodes = (snapshot.nodes ?? []).slice(baseline)\n // \u4ECE\u672B\u5C3E\u627E\u6700\u540E\u4E00\u4E2A\u5305\u542B\u4F5C\u6218\u5361\u8D77\u70B9\u7684\u8282\u70B9\uFF0C\u4ECE\u8D77\u70B9\u5207\u5230\u8BE5\u8282\u70B9\u672B\u5C3E\n for (let idx = nodes.length - 1; idx >= 0; idx--) {\n const node = nodes[idx]\n if (node === undefined || isTool(node) || isUserish(node)) continue\n const text = nodeText(node)\n if (JUNK.test(text)) continue\n const m = CARD_START.exec(text)\n if (m === null || m.index === undefined) continue\n let card = text.slice(m.index).trim()\n // \u540E\u7EED\u7D27\u90BB\u8282\u70B9\u82E5\u662F\u540C\u4E00\u5F20\u5361\u7684\u5EF6\u7EED\uFF08\u4EE5 Markdown \u6BB5\u6807\u9898\u5F00\u5934\u3001\u65E0\u6CE8\u5165\u5185\u5BB9\uFF09\uFF0C\u62FC\u63A5\n for (let j = idx + 1; j < nodes.length; j++) {\n const next = nodes[j]\n if (next === undefined || isTool(next) || isUserish(next)) break\n const t = nodeText(next).trim()\n if (t === \"\" || JUNK.test(t) || !/^(#{2,4}\\s|\\||-\\s|\\d+\\.\\s|\\*\\*)/.test(t)) break\n card += \"\\n\\n\" + t\n }\n if (CARD_SHAPE(card)) return card\n }\n // \u515C\u5E95\uFF1A\u5355\u8282\u70B9\u6574\u4F53\u7B26\u5408\u5361\u7247\u7ED3\u6784\n for (let idx = nodes.length - 1; idx >= 0; idx--) {\n const node = nodes[idx]\n if (node === undefined || isTool(node) || isUserish(node)) continue\n const text = nodeText(node).trim()\n if (text.length > 40 && !JUNK.test(text) && CARD_SHAPE(text)) return text\n }\n return null\n}\n\nconst esc = (s: string): string =>\n s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\n\n// \u8BFB\u8005\u89C6\u89D2\u7684\u6E05\u6D17\uFF1A\u4E8B\u5B9E\u7F16\u53F7 [F-013] \u4E0E\u8BC1\u636E\u5C42\u7EA7\u4EE3\u53F7 L1/L2 \u53EA\u5BF9\u6A21\u578B\u8FFD\u6EAF\u6709\u7528\uFF0C\u62A5\u544A\u91CC\u7FFB\u8BD1\u6210\u4EBA\u8BDD\u6216\u53BB\u6389\nconst TIER_WORDS: Record<string, string> = { L1: \"\u8D22\u62A5\u62AB\u9732\", L2: \"\u516C\u5F00\u8BB0\u5F55\", L3: \"\u5173\u8054\u5173\u7CFB\", L4: \"\u884C\u4E1A\u63A8\u65AD\", L5: \"\u73B0\u573A\u6838\u5B9E\" }\nexport function humanizeCell(text: string): string {\n const t = text.trim()\n if (/^L[1-5](\\s*[\\/\uFF0F\u3001]\\s*L[1-5])*$/.test(t)) {\n return t.split(/\\s*[\\/\uFF0F\u3001]\\s*/).map(x => TIER_WORDS[x] ?? x).join(\" / \")\n }\n if (t === \"\u6765\u6E90\u5C42\u7EA7\") return \"\u6765\u6E90\"\n return text\n}\nexport function stripFactIds(text: string): string {\n // \u652F\u6301 [F-013]\u3001[F-008/F-009/F-013]\u3001[F-002\u3001F-008]\u3001[F-1/F-2] \u7B49\u5199\u6CD5\n return text\n .replace(/\\s*\\[F-?\\d{1,4}(?:\\s*[\\/\u3001,\uFF0C;\uFF1B|]\\s*F-?\\d{1,4})*\\]/g, \"\")\n .replace(/[ \\t]+([\uFF0C\u3002\uFF1B\uFF1A\uFF09])/g, \"$1\")\n}\n// \u5047\u8BBE\u7F16\u53F7\u4E0E\u4F18\u5148\u7EA7\uFF1AH2 \u00B7 P1 \u2192 \u5047\u8BBE 2\uFF08P1\uFF09\uFF1B\u6B63\u6587\u91CC\u201C\u56DE\u6307 H2\u201D\u2192\u201C\u56DE\u6307\u5047\u8BBE 2\u201D\nconst PRIORITY_WORDS: Record<string, string> = { P0: \"\u4F18\u5148\", P1: \"\u91CD\u8981\", P2: \"\u5907\u9009\" }\nexport function humanizeHypothesis(text: string): string {\n return text\n .replace(/^\\s*H(\\d+)\\s*[\u00B7\u30FB:\uFF1A\\-\u2013]\\s*(P[012])\\s*[\u00B7\u30FB:\uFF1A\\-\u2013]\\s*/, (_m, n: string, p: string) => `\u5047\u8BBE ${n}\uFF08${PRIORITY_WORDS[p] ?? p}\uFF09`)\n .replace(/(?<![A-Za-z0-9])H(\\d{1,2})(?![A-Za-z0-9])/g, \"\u5047\u8BBE $1\")\n}\n\n// \u884C\u5185\uFF1A**\u52A0\u7C97**\uFF1B\u4E8B\u5B9E\u7F16\u53F7\u53BB\u6389\nfunction inline(text: string): string {\n let html = esc(humanizeHypothesis(stripFactIds(text)))\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, \"<strong>$1</strong>\")\n return html\n}\n\nconst CIRCLED: Record<string, string> = { \"\u2460\": \"1\", \"\u2461\": \"2\", \"\u2462\": \"3\", \"\u2463\": \"4\", \"\u2464\": \"5\", \"\u2465\": \"6\", \"\u2466\": \"7\", \"\u2467\": \"8\", \"\u2468\": \"9\", \"\u2469\": \"10\" }\nexport function normalizeHeading(text: string): string {\n return text.trim().replace(/^([\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469])\\s*/, (_m, c: string) => `${CIRCLED[c] ?? c}\u3001`).replace(/^(\\d+)[.\uFF0E]\\s*/, \"$1\u3001\")\n}\n\n// \u62A5\u544A Markdown \u5B50\u96C6 \u2192 HTML \u4E3B\u4F53\u3002\u652F\u6301\uFF1A# ## ### \u6807\u9898\u3001| \u8868\u683C |\u3001- \u5217\u8868\u30011. \u6709\u5E8F\u3001--- \u5206\u9694\u3001\u6BB5\u843D\u3002\nexport function renderCardMarkdown(md: string): string {\n const lines = md.replace(/\\r\\n/g, \"\\n\").split(\"\\n\")\n const out: string[] = []\n let i = 0\n const flushList = (items: string[], ordered: boolean) => {\n if (!items.length) return\n const tag = ordered ? \"ol\" : \"ul\"\n out.push(`<${tag}>${items.map(it => `<li>${inline(it)}</li>`).join(\"\")}</${tag}>`)\n }\n while (i < lines.length) {\n const line = lines[i] ?? \"\"\n const t = line.trim()\n if (t === \"\") { i++; continue }\n if (/^---+$/.test(t)) { out.push(\"<hr/>\"); i++; continue }\n // \u8868\u683C\uFF1A\u8FDE\u7EED\u4EE5 | \u5F00\u5934\u7684\u884C\uFF0C\u7B2C\u4E8C\u884C\u662F\u5206\u9694\u7B26\n const sep = lines[i + 1] ?? \"\"\n if (t.startsWith(\"|\") && /^\\|[\\s:|-]+\\|?$/.test(sep.trim())) {\n const header = t.split(\"|\").slice(1, -1).map(c => humanizeCell(c.trim()))\n i += 2\n const rows: string[][] = []\n while (i < lines.length && (lines[i] ?? \"\").trim().startsWith(\"|\")) {\n rows.push((lines[i] ?? \"\").trim().split(\"|\").slice(1, -1).map(c => humanizeCell(c.trim())))\n i++\n }\n const thead = `<thead><tr>${header.map(h => `<th>${inline(h)}</th>`).join(\"\")}</tr></thead>`\n const tbody = `<tbody>${rows.map(r => `<tr>${r.map(c => `<td>${inline(c)}</td>`).join(\"\")}</tr>`).join(\"\")}</tbody>`\n out.push(`<div class=\"tw\"><table>${thead}${tbody}</table></div>`)\n continue\n }\n // \u6807\u9898\uFF1A\u5708\u53F7 \u2460\u2461 \u7EDF\u4E00\u6210\u201C1\u3001\u201D\u683C\u5F0F\n const h = /^(#{1,4})\\s+(.*)$/.exec(t)\n if (h) {\n const level = (h[1] ?? \"##\").length\n out.push(`<h${level}>${inline(normalizeHeading(h[2] ?? \"\"))}</h${level}>`)\n i++\n continue\n }\n // \u6807\u9898\u4E0B\u7684\u5C0F\u8BF4\u660E\uFF08> \u8BF4\u660E\uFF1A\u2026\uFF09\n if (t.startsWith(\">\")) {\n out.push(`<p class=\"note\">${inline(t.replace(/^>\\s?/, \"\"))}</p>`)\n i++\n continue\n }\n // \u65E0\u5E8F\u5217\u8868\n if (/^[-*]\\s+/.test(t)) {\n const items: string[] = []\n while (i < lines.length && /^\\s*[-*]\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").trim().replace(/^[-*]\\s+/, \"\"))\n i++\n }\n flushList(items, false)\n continue\n }\n // \u6709\u5E8F\u5217\u8868\n if (/^\\d+\\.\\s+/.test(t)) {\n const items: string[] = []\n while (i < lines.length && /^\\s*\\d+\\.\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").trim().replace(/^\\d+\\.\\s+/, \"\"))\n i++\n }\n flushList(items, true)\n continue\n }\n // \u6BB5\u843D\n out.push(`<p>${inline(t)}</p>`)\n i++\n }\n return out.join(\"\\n\")\n}\n\n// demo \u9501\u5B9A\u914D\u8272\u4E0E\u5B57\u53F7\uFF1B\u6807\u9898\u514B\u5236\u3001\u56DB\u5B57\u4F18\u5148\uFF0C\u6807\u9898\u4E0B\u53EF\u5E26\u5C0F\u8BF4\u660E\u6A21\u5757\u3002\nconst REPORT_CSS = `\n:root{\n --blue:#128BED;--blue-dark:#0875D1;--ink:#202C3B;--body:#3D4A5C;--muted:#626F80;\n --line:#DCE4EC;--line-blue:#CDE9FC;--hero:#F2F9FC;\n --ok:#16a34a;--ok-bg:#f0fdf4;--warn:#d97706;--warn-bg:#fff7ed;--risk:#ef4444;--risk-bg:#fef2f2;\n}\n*{box-sizing:border-box}\nbody{margin:0;background:#f1f5f9;color:var(--body);\n font:15px/1.75 -apple-system,BlinkMacSystemFont,\"PingFang SC\",\"Microsoft YaHei\",\"Noto Sans SC\",sans-serif;\n -webkit-font-smoothing:antialiased}\n.sheet{max-width:860px;margin:24px auto;background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden;\n box-shadow:0 1px 2px rgba(15,23,42,.04),0 12px 32px -20px rgba(15,23,42,.25)}\n.hero{background:var(--hero);border-bottom:1px solid var(--line-blue);padding:22px 26px 20px}\n.hero .eyebrow{font-size:11px;font-weight:800;letter-spacing:.12em;color:var(--blue);margin:0 0 6px}\n.hero h1{margin:0;color:var(--ink);font-size:22px;font-weight:900;line-height:1.4}\n.hero .meta{margin:8px 0 0;color:var(--muted);font-size:12.5px;line-height:1.7}\n.body{padding:8px 26px 26px}\n/* \u5927\u6807\u9898\uFF1A\u84DD\u8272 + \u6807\u9898\u4E0B\u77ED\u6A2A\u7EBF\uFF1B\u5C0F\u6807\u9898\uFF1A\u6D45\u7070\u5E95 + \u5DE6\u4FA7\u84DD\u7AD6\u7EBF */\n.body h2{position:relative;margin:26px 0 12px;padding:0 0 8px;color:var(--blue-dark);font-size:16px;font-weight:800;letter-spacing:.01em}\n.body h2::after{content:\"\";position:absolute;left:0;bottom:0;width:32px;height:3px;border-radius:2px;background:var(--blue)}\n.body h2:first-of-type{margin-top:10px}\n.body h3{margin:16px 0 8px;padding:6px 10px;border-left:3px solid var(--blue);border-radius:0 4px 4px 0;background:#f5f7fa;color:var(--ink);font-size:13.5px;font-weight:800}\n.body h4{margin:12px 0 4px;color:var(--ink);font-size:13px;font-weight:700}\n.body h1{display:none}\n.note{margin:-4px 0 10px;color:var(--muted);font-size:12px;line-height:1.7}\nblockquote{margin:-4px 0 10px;padding:0;border:0;color:var(--muted);font-size:12px;line-height:1.7}\nblockquote p{margin:0}\np{margin:6px 0}\nstrong{color:var(--ink)}\n.fid{color:var(--blue);font:600 10px ui-monospace,monospace;margin-left:2px}\nul,ol{margin:6px 0;padding-left:20px}\nli{margin:3px 0;line-height:1.7}\nhr{border:0;border-top:1px solid var(--line);margin:16px 0}\n.tw{overflow-x:auto;margin:8px 0}\ntable{border-collapse:collapse;width:100%;font-size:13px}\nth,td{border:1px solid var(--line);padding:8px 11px;text-align:left;vertical-align:top;line-height:1.6;word-break:break-word}\nth{background:#f8fafc;color:var(--muted);font-weight:700;font-size:12px;white-space:nowrap}\n/* \u9996\u5217\u591A\u4E3A\u201C\u7B49\u7EA7/\u65E5\u671F/\u63A8\u8350\u7EA7\u201D\u8FD9\u7C7B\u77ED\u6807\u7B7E\uFF1A\u4E0D\u6362\u884C\u3001\u4E0D\u88AB\u6324\u6241\uFF1B\u957F\u6587\u672C\u5217\u81EA\u9002\u5E94 */\ntd:first-child,th:first-child{white-space:nowrap;width:1%}\ntd:nth-child(2){min-width:180px}\n/* \u72B6\u6001\u8272\uFF1A\u51FA\u73B0\u7EA2\u7EBF/\u5173\u6CE8/\u786E\u8BA4\u7B49\u8BCD\u81EA\u52A8\u67D3\u8272\u7531\u5185\u5BB9\u51B3\u5B9A\uFF0C\u8FD9\u91CC\u7ED9\u51FA\u53EF\u7528\u7C7B */\n.tag-ok{color:var(--ok);background:var(--ok-bg)}\n.tag-warn{color:var(--warn);background:var(--warn-bg)}\n.tag-risk{color:var(--risk);background:var(--risk-bg)}\nfooter{padding:14px 26px 20px;border-top:1px solid var(--line);color:var(--muted);font-size:11.5px;line-height:1.7}\n@media print{body{background:#fff}.sheet{border:0;box-shadow:none;margin:0}}\n`\n\n// \u4ECE\u4F5C\u6218\u5361\u9996\u884C/\u951A\u5B9A\u884C\u63D0\u53D6\u4F01\u4E1A\u540D\u4E0E\u751F\u6210\u65F6\u95F4\u505A hero\nfunction deriveMeta(md: string, meta?: ReportMeta): { company: string; anchor: string; when: string } {\n const first = /^#?\\s*(?:\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361)\\s*\u00B7\\s*([^\\n\u951A]+)/m.exec(md)\n const anchorLine = /\u951A\u5B9A\u4E3B\u4F53\uFF1A([^\\n]+?)(?:\\s*\u751F\u6210\u65F6\u95F4|$)/m.exec(md)\n const whenLine = /\u751F\u6210\u65F6\u95F4[:\uFF1A]\\s*([0-9-]+)/.exec(md)\n return {\n company: meta?.company || (first?.[1]?.trim() ?? \"\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\"),\n anchor: (anchorLine?.[1] ?? \"\").trim().replace(/[\uFF5C|\u00B7\\s]+$/, \"\"),\n when: meta?.generatedAt || (whenLine?.[1] ?? \"\"),\n }\n}\n\nexport type ReportHero = { company: string; anchor: string; when: string }\n\n// \u7EDF\u4E00\u5916\u58F3\uFF1Ahero + body + footer\nexport function wrapPrevisitReport(bodyHtml: string, m: ReportHero): string {\n const heroMeta = [m.anchor, m.when ? `\u751F\u6210\u65F6\u95F4\uFF1A${m.when}` : \"\"].filter(Boolean).join(\"\u3000\u00B7\u3000\")\n return `<!doctype html><html lang=\"zh\"><head><meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A \u00B7 ${esc(m.company)}</title><style>${REPORT_CSS}</style></head>\n<body><div class=\"sheet\">\n<div class=\"hero\"><p class=\"eyebrow\">\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A</p><h1>${esc(m.company)}</h1>${heroMeta ? `<p class=\"meta\">${esc(heroMeta)}</p>` : \"\"}</div>\n<div class=\"body\">${bodyHtml}</div>\n<footer>\u6570\u636E\u6765\u6E90\uFF1A\u4F01\u67E5\u67E5\u3002\u5404\u7EF4\u5EA6\u65F6\u6548\u4E0D\u540C\uFF0C\u4E0D\u627F\u8BFA\u5B9E\u65F6\u3002\u672C\u62A5\u544A\u4E3A\u8BBF\u524D\u8F85\u52A9\u6750\u6599\uFF0C\u4E0D\u6784\u6210\u6388\u4FE1\u3001\u5408\u4F5C\u6216\u4EFB\u4F55\u5546\u4E1A\u51B3\u7B56\u4F9D\u636E\uFF0C\u4EE5\u5B98\u65B9\u516C\u793A\u4E3A\u51C6\u3002</footer>\n</div></body></html>`\n}\n\nexport function buildPrevisitReportHtml(cardMarkdown: string, meta?: ReportMeta): string {\n const m = deriveMeta(cardMarkdown, meta)\n // \u53BB\u6389\u6B63\u6587\u91CC\u7684\u4E00\u7EA7\u6807\u9898\u548C\u951A\u5B9A\u884C\uFF08\u5DF2\u8FDB hero\uFF09\uFF0C\u5176\u4F59\u8FDB body\n const body = cardMarkdown\n .replace(/^#\\s*(?:\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A|\u62DC\u8BBF\u4F5C\u6218\u5361).*$/m, \"\")\n .replace(/^\u951A\u5B9A\u4E3B\u4F53\uFF1A.*$/m, \"\")\n .trim()\n return wrapPrevisitReport(renderCardMarkdown(body), m)\n}\n\n// \u515C\u5E95\uFF1A\u76F4\u63A5\u7528\u4F1A\u8BDD\u91CC\u5DF2\u6E32\u67D3\u597D\u7684\u62A5\u544A HTML\uFF08\u53BB\u6389\u6837\u5F0F/\u7C7B\u540D/\u63A7\u4EF6\uFF09\uFF0C\u5957\u540C\u4E00\u5916\u58F3\nexport function buildPrevisitReportFromRenderedHtml(bodyHtml: string, plainText: string, meta?: ReportMeta): string {\n const m = deriveMeta(plainText, meta)\n const body = humanizeHypothesis(stripFactIds(bodyHtml))\n .replace(/<(td|th)([^>]*)>\\s*([^<]{1,20})\\s*<\\/\\1>/g, (_m, tag: string, attrs: string, cell: string) => `<${tag}${attrs}>${esc(humanizeCell(cell))}</${tag}>`)\n return wrapPrevisitReport(body, m)\n}\n", "import { randomUUID } from \"node:crypto\"\n\nimport type { ToolOutcome } from \"./tool-outcome.js\"\n\nexport const PREVISIT_TASK_STATES = [\n \"needs-entity-search\",\n \"needs-entity-confirmation\",\n \"entity-confirmed\",\n \"running\",\n \"finalizing\",\n \"completed\",\n \"partial\",\n \"failed\",\n] as const\n\nexport type PrevisitTaskState = (typeof PREVISIT_TASK_STATES)[number]\nexport type PrevisitTaskStage = \"target\" | \"scope\" | \"collect\" | \"verify\" | \"output\"\n\nexport const PREVISIT_TERMINAL_STATES: ReadonlySet<PrevisitTaskState> = new Set([\"completed\", \"partial\", \"failed\"])\n\nexport type PrevisitRun = {\n id: string\n dimension: string\n toolName?: string\n status: ToolOutcome\n quotaUsed: boolean\n startedAt: string\n completedAt?: string\n message?: string\n}\n\nexport type PrevisitArtifact = {\n id: string\n format: \"html\"\n fileName: string\n mediaType: \"text/html; charset=utf-8\"\n createdAt: string\n}\n\nexport type PrevisitTaskRecord = {\n id: string\n schemaVersion: 1\n revision: number\n sessionId: string\n workspace: string\n query: string\n depth: \"fast\" | \"standard\" | \"deep\"\n limit: number\n used: number\n state: PrevisitTaskState\n stage: PrevisitTaskStage\n entity?: { fullName: string; creditCode: string }\n runs: PrevisitRun[]\n reportMarkdown?: string\n artifact?: PrevisitArtifact\n lastError?: string\n createdAt: string\n updatedAt: string\n completedAt?: string\n}\n\nexport type PrevisitVerificationClosure = {\n gaps: string[]\n partialRequired: boolean\n}\n\nconst VERIFICATION_DETAIL_LABELS = {\n dishonest: \"\u5931\u4FE1\u660E\u7EC6\",\n enforcement: \"\u88AB\u6267\u884C\u660E\u7EC6\",\n terminated_cases: \"\u7EC8\u672C\u6848\u4EF6\u660E\u7EC6\",\n equity_freeze: \"\u80A1\u6743\u51BB\u7ED3\u660E\u7EC6\",\n business_exception: \"\u7ECF\u8425\u5F02\u5E38\u660E\u7EC6\",\n administrative_penalty: \"\u884C\u653F\u5904\u7F5A\u660E\u7EC6\",\n tax_abnormal: \"\u7A0E\u52A1\u5F02\u5E38\u660E\u7EC6\",\n judicial_documents: \"\u88C1\u5224\u6587\u4E66\u660E\u7EC6\",\n} as const\n\nconst UNRESOLVED_VERIFICATION_STATUSES: ReadonlySet<ToolOutcome> = new Set([\n \"failed\",\n \"no-permission\",\n \"not-executed\",\n \"unknown\",\n])\n\n/**\n * Shared verification gate for explicit tool finalization and the UI report\n * reconciliation fallback. `skipped` is a resolved green state; synthetic\n * `previsit-pending-*` runs remain open until a real Provider result replaces them.\n */\nexport function previsitVerificationClosure(task: PrevisitTaskRecord): PrevisitVerificationClosure {\n const verificationDimensions = new Set<string>([\n \"risk_scan\",\n \"personnel\",\n \"executive_risk\",\n ...Object.keys(VERIFICATION_DETAIL_LABELS),\n ])\n const latest = new Map<string, PrevisitRun>()\n for (const run of task.runs) if (verificationDimensions.has(run.dimension)) latest.set(run.dimension, run)\n const gaps: string[] = []\n const isPending = (run: PrevisitRun | undefined) => run === undefined\n || run.status === \"running\"\n || run.id.startsWith(\"previsit-pending-\")\n\n const riskScan = latest.get(\"risk_scan\")\n if (isPending(riskScan)) gaps.push(\"\u98CE\u9669\u626B\u63CF\u672A\u5B8C\u6210\")\n else if (riskScan?.status === \"done\" || riskScan?.status === \"no-data\" || riskScan?.status === \"skipped\") {\n for (const [dimension, label] of Object.entries(VERIFICATION_DETAIL_LABELS)) {\n if (isPending(latest.get(dimension))) gaps.push(`${label}\u672A\u95ED\u73AF`)\n }\n }\n\n const personnel = latest.get(\"personnel\")\n if (isPending(personnel)) gaps.push(\"\u5173\u952E\u4EBA\u5458\u67E5\u8BE2\u672A\u5B8C\u6210\")\n else if (personnel?.status === \"done\" || personnel?.status === \"unknown\" || personnel?.status === \"no-data\") {\n if (isPending(latest.get(\"executive_risk\"))) gaps.push(\"\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\u672A\u95ED\u73AF\")\n }\n\n return {\n gaps: [...new Set(gaps)],\n partialRequired: [...latest.values()].some(run => UNRESOLVED_VERIFICATION_STATUSES.has(run.status)),\n }\n}\n\ntype StorageTable = {\n get(key: string): unknown | Promise<unknown>\n put(key: string, value: unknown): unknown | Promise<unknown>\n entries(): Iterable<[string, unknown]>\n}\n\nexport type StorageDomain = {\n open(spec: object): Promise<{ table(name: string): StorageTable }>\n}\n\nconst DOMAIN_SPEC = {\n name: \"previsit_tasks_v1\",\n version: 1,\n tables: {\n tasks: {\n valueSchema: {\n parse: (value: unknown) => value,\n safeParse: (value: unknown) => ({ success: true, data: value }),\n },\n },\n },\n}\n\nconst VALID_REQUEST_ID = /^PV-\\d{8}-[A-Z0-9-]{4,40}$/\nconst nowIso = () => new Date().toISOString()\n\nfunction isRecord(value: unknown): value is PrevisitTaskRecord {\n if (value === null || typeof value !== \"object\") return false\n const record = value as Partial<PrevisitTaskRecord>\n return typeof record.id === \"string\"\n && record.schemaVersion === 1\n && typeof record.sessionId === \"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}\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()) if (isRecord(value)) this.records.set(id, normalizeTerminalRecord(value))\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 if (!isRecord(value)) return undefined\n const normalized = normalizeTerminalRecord(value)\n if (normalized !== value) await this.put(normalized)\n else this.records.set(id, normalized)\n return normalized\n }\n\n async put(record: PrevisitTaskRecord): Promise<PrevisitTaskRecord> {\n this.records.set(record.id, record)\n await this.table?.put(record.id, record)\n return record\n }\n\n async create(input: {\n id?: string\n sessionId: string\n workspace: string\n query: string\n depth: \"fast\" | \"standard\" | \"deep\"\n limit?: number\n }): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n const id = normalizePrevisitRequestId(input.id) ?? createPrevisitHostTaskId()\n const existing = await this.get(id)\n if (existing !== undefined && existing.sessionId !== input.sessionId) throw new Error(\"\u4EFB\u52A1\u6807\u8BC6\u5DF2\u5C5E\u4E8E\u5176\u4ED6\u4F1A\u8BDD\")\n if (existing !== undefined) {\n assertTaskOpen(existing)\n return this.update(id, current => {\n const { entity: _entity, lastError: _lastError, reportMarkdown: _report, artifact: _artifact, completedAt: _completedAt, ...retained } = current\n return {\n ...retained,\n query: input.query,\n depth: input.depth,\n limit: 0,\n state: \"needs-entity-search\",\n stage: \"target\",\n }\n })\n }\n return this.put({\n id,\n schemaVersion: 1,\n revision: 1,\n sessionId: input.sessionId,\n workspace: input.workspace,\n query: input.query,\n depth: input.depth,\n limit: 0,\n used: 0,\n state: \"needs-entity-search\",\n stage: \"target\",\n runs: [],\n createdAt: timestamp,\n updatedAt: timestamp,\n })\n }\n\n async update(id: string, updater: (record: PrevisitTaskRecord) => PrevisitTaskRecord): Promise<PrevisitTaskRecord> {\n const current = await this.get(id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n const proposed = updater(current)\n const next: PrevisitTaskRecord = {\n ...proposed,\n id: current.id,\n schemaVersion: 1,\n revision: current.revision + 1,\n updatedAt: nowIso(),\n }\n return this.put(next)\n }\n\n async startRun(id: string, input: { runId: string; dimension: string; toolName?: string; quotaUsed: boolean }): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n return this.update(id, current => {\n assertTaskOpen(current)\n const { lastError: _lastError, ...retained } = current\n return {\n ...retained,\n used: current.used + (input.quotaUsed ? 1 : 0),\n state: \"running\",\n stage: input.dimension === \"entity_search\" ? \"target\" : /risk|dishonest|enforcement|terminated|freeze|exception|penalty|tax|judicial|executive/.test(input.dimension) ? \"verify\" : \"collect\",\n runs: [...current.runs, {\n id: input.runId,\n dimension: input.dimension,\n ...(input.toolName === undefined ? {} : { toolName: input.toolName }),\n status: \"running\" as const,\n quotaUsed: input.quotaUsed,\n startedAt: timestamp,\n }].slice(-160),\n }\n })\n }\n\n async finishRun(id: string, runId: string, status: ToolOutcome, message?: string): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n return this.update(id, current => {\n const runs: PrevisitRun[] = current.runs.map(run => run.id === runId ? {\n ...run,\n status,\n completedAt: timestamp,\n ...(message === undefined ? {} : { message: message.slice(0, 500) }),\n } : run)\n const run = runs.find(item => item.id === runId)\n if (PREVISIT_TERMINAL_STATES.has(current.state) || (current.reportMarkdown?.trim() ?? \"\") !== \"\") {\n return { ...current, runs }\n }\n const nextState = run?.dimension === \"entity_search\"\n ? (status === \"done\" || status === \"unknown\" ? \"needs-entity-confirmation\" : \"needs-entity-search\")\n : current.state\n return {\n ...current,\n runs,\n state: nextState,\n stage: current.stage,\n ...(status === \"failed\" ? { lastError: message ?? \"\u67E5\u8BE2\u5931\u8D25\" } : {}),\n }\n })\n }\n\n async confirmEntity(id: string, entity: { fullName: string; creditCode: string }): Promise<PrevisitTaskRecord> {\n return this.update(id, current => {\n assertTaskOpen(current)\n const { lastError: _lastError, ...retained } = current\n return { ...retained, entity, state: \"entity-confirmed\", stage: \"scope\" }\n })\n }\n\n async finalize(id: string, reportMarkdown: string, state: \"completed\" | \"partial\"): Promise<PrevisitTaskRecord> {\n const timestamp = nowIso()\n const current = await this.get(id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n if (PREVISIT_TERMINAL_STATES.has(current.state) && current.reportMarkdown !== undefined) return current\n const report = validatePrevisitReport(reportMarkdown, current.entity?.fullName)\n const artifact = reportArtifactFor(current, timestamp)\n return this.update(id, record => {\n const { lastError: _lastError, ...retained } = record\n return {\n ...retained,\n state,\n stage: \"output\",\n reportMarkdown: report,\n artifact,\n completedAt: timestamp,\n }\n })\n }\n}\n", "import { buildPrevisitReportHtml } from \"./report-export.js\"\nimport { isPrevisitSession } from \"./previsit-session.js\"\nimport { normalizePrevisitRequestId, previsitVerificationClosure, validatePrevisitReport, type PrevisitTaskRecord, type PrevisitWorkflowStore } from \"./previsit-workflow.js\"\n\ntype RequestLike = AsyncIterable<Uint8Array> & {\n method?: string\n url?: string\n headers: Record<string, string | string[] | undefined>\n}\n\ntype ResponseLike = {\n writeHead(status: number, headers?: Record<string, string>): void\n end(body?: string | Uint8Array): void\n}\n\nexport type WebServer = {\n register(input: { kind: \"prefix\"; path: string; handler(req: RequestLike, res: ResponseLike): unknown }): () => void\n}\n\nfunction isTrusted(req: RequestLike): boolean {\n const fetchSite = String(req.headers[\"sec-fetch-site\"] ?? \"\")\n if (fetchSite === \"cross-site\") return false\n const origin = req.headers.origin\n if (typeof origin === \"string\") {\n try {\n const parsed = new URL(origin)\n if (parsed.hostname !== \"127.0.0.1\" && parsed.hostname !== \"localhost\") return false\n } catch { return false }\n }\n return true\n}\n\nfunction writeJson(res: ResponseLike, status: number, payload: unknown): void {\n res.writeHead(status, {\n \"content-type\": \"application/json; charset=utf-8\",\n \"cache-control\": \"no-store\",\n \"x-content-type-options\": \"nosniff\",\n \"referrer-policy\": \"no-referrer\",\n })\n res.end(JSON.stringify(payload))\n}\n\nasync function readJson(req: RequestLike): Promise<Record<string, unknown>> {\n const chunks: Uint8Array[] = []\n let size = 0\n for await (const chunk of req) {\n size += chunk.length\n if (size > 256_000) throw new Error(\"report payload too large\")\n chunks.push(chunk)\n }\n const text = Buffer.concat(chunks).toString(\"utf8\")\n const value = text === \"\" ? {} : JSON.parse(text)\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"invalid JSON body\")\n return value as Record<string, unknown>\n}\n\nfunction publicTask(task: PrevisitTaskRecord): Omit<PrevisitTaskRecord, \"reportMarkdown\"> & { reportReady: boolean } {\n const { reportMarkdown, ...record } = task\n return { ...record, reportReady: typeof reportMarkdown === \"string\" && reportMarkdown.length > 0 }\n}\n\nexport function mountPrevisitWebRoutes(webServer: WebServer, workflow: PrevisitWorkflowStore): () => void {\n return webServer.register({\n kind: \"prefix\",\n path: \"/previsit/api/tasks\",\n async handler(req, res) {\n if (!isTrusted(req)) return writeJson(res, 403, { ok: false, code: \"PREVISIT_UNTRUSTED\", message: \"untrusted origin\" })\n try {\n const url = new URL(req.url ?? \"/previsit/api/tasks\", \"http://127.0.0.1\")\n const segments = url.pathname.split(\"/\").filter(Boolean)\n const tasksIndex = segments.indexOf(\"tasks\")\n const rest = tasksIndex === -1 ? [] : segments.slice(tasksIndex + 1)\n if (rest.length === 0) {\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET required\" })\n const sessionId = url.searchParams.get(\"sessionId\") ?? undefined\n if (sessionId !== undefined && !isPrevisitSession(sessionId)) return writeJson(res, 400, { ok: false, code: \"PREVISIT_SESSION\", message: \"invalid previsit session\" })\n const tasks = await workflow.list(sessionId)\n return writeJson(res, 200, { ok: true, marker: \"previsit-workflow-v1\", tasks: tasks.map(publicTask) })\n }\n const taskId = decodeURIComponent(rest[0] ?? \"\")\n if (normalizePrevisitRequestId(taskId) === undefined && !/^PVT-[a-f0-9-]{36}$/i.test(taskId)) {\n return writeJson(res, 400, { ok: false, code: \"PREVISIT_TASK_ID\", message: \"invalid task id\" })\n }\n const task = await workflow.get(taskId)\n if (task === undefined) return writeJson(res, 404, { ok: false, code: \"PREVISIT_NOT_FOUND\", message: \"task not found\" })\n const sessionId = url.searchParams.get(\"sessionId\")\n if (sessionId === null || !isPrevisitSession(sessionId) || sessionId !== task.sessionId) {\n return writeJson(res, 403, { ok: false, code: \"PREVISIT_TASK_SCOPE\", message: \"task does not belong to this session\" })\n }\n if (rest.length === 1) {\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET required\" })\n return writeJson(res, 200, {\n ok: true,\n marker: \"previsit-workflow-v1\",\n task: { ...publicTask(task), ...(task.reportMarkdown === undefined ? {} : { reportMarkdown: task.reportMarkdown }) },\n })\n }\n if (rest.length === 2 && rest[1] === \"report\") {\n if (req.method === \"PUT\") {\n const payload = await readJson(req)\n if (typeof payload.reportMarkdown !== \"string\") return writeJson(res, 400, { ok: false, code: \"PREVISIT_REPORT\", message: \"reportMarkdown required\" })\n const reportMarkdown = validatePrevisitReport(payload.reportMarkdown, task.entity?.fullName)\n const closure = previsitVerificationClosure(task)\n if (closure.gaps.length > 0) {\n return writeJson(res, 409, {\n ok: false,\n code: \"PREVISIT_VERIFICATION_INCOMPLETE\",\n message: `\u8BC1\u636E\u6838\u9A8C\u672A\u95ED\u73AF\uFF1A${closure.gaps.join(\"\uFF1B\")}`,\n })\n }\n const status = payload.status === \"partial\" || closure.partialRequired ? \"partial\" : \"completed\"\n const completed = await workflow.finalize(task.id, reportMarkdown, status)\n return writeJson(res, 200, { ok: true, marker: \"previsit-workflow-v1\", task: { ...publicTask(completed), reportMarkdown: completed.reportMarkdown } })\n }\n if (req.method !== \"GET\") return writeJson(res, 405, { ok: false, code: \"PREVISIT_METHOD\", message: \"GET or PUT required\" })\n if (task.reportMarkdown === undefined || task.artifact === undefined) {\n return writeJson(res, 409, { ok: false, code: \"PREVISIT_REPORT_PENDING\", message: \"report not ready\" })\n }\n const html = buildPrevisitReportHtml(task.reportMarkdown, {\n ...(task.entity?.fullName === undefined ? {} : { company: task.entity.fullName }),\n ...(task.completedAt === undefined ? {} : { generatedAt: task.completedAt.slice(0, 10) }),\n })\n const ascii = task.artifact.fileName.replace(/[^\\x20-\\x7e]/g, \"_\").replace(/[\"\\\\]/g, \"_\")\n res.writeHead(200, {\n \"content-type\": task.artifact.mediaType,\n \"content-disposition\": `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encodeURIComponent(task.artifact.fileName)}`,\n \"cache-control\": \"no-store\",\n \"x-content-type-options\": \"nosniff\",\n \"referrer-policy\": \"no-referrer\",\n })\n return res.end(html)\n }\n return writeJson(res, 404, { ok: false, code: \"PREVISIT_ROUTE\", message: \"route not found\" })\n } catch (error) {\n return writeJson(res, 500, { ok: false, code: \"PREVISIT_HOST\", message: error instanceof Error ? error.message : String(error) })\n }\n },\n })\n}\n", "import { randomUUID } from \"node:crypto\"\nimport { isPrevisitSession } from \"./previsit-session.js\"\nimport { classifyQccProviderOutcome } from \"./tool-outcome.js\"\nimport { normalizePrevisitRequestId, previsitVerificationClosure, PrevisitWorkflowStore, validatePrevisitReport } from \"./previsit-workflow.js\"\n\n// Business vocabulary is fixed here; the browser/model cannot dispatch an arbitrary MCP tool.\nexport const QUERY_ROUTES = {\n entity_search: [\"company\", \"get_company_by_query\"],\n registration: [\"company\", \"get_company_registration_info\"],\n profile: [\"company\", \"get_company_profile\"],\n annual_reports: [\"company\", \"get_annual_reports\"],\n changes: [\"company\", \"get_change_records\"],\n shareholders: [\"company\", \"get_shareholder_info\"],\n beneficiaries: [\"company\", \"get_beneficial_owners\"],\n personnel: [\"company\", \"get_key_personnel\"],\n contacts: [\"company\", \"get_contact_info\"],\n investments: [\"company\", \"get_external_investments\"],\n branches: [\"company\", \"get_branches\"],\n risk_scan: [\"risk\", \"get_company_risk_scan\"],\n dishonest: [\"risk\", \"get_dishonest_info\"],\n enforcement: [\"risk\", \"get_judgment_debtor_info\"],\n terminated_cases: [\"risk\", \"get_terminated_cases\"],\n equity_freeze: [\"risk\", \"get_equity_freeze\"],\n business_exception: [\"risk\", \"get_business_exception\"],\n administrative_penalty: [\"risk\", \"get_administrative_penalty\"],\n tax_abnormal: [\"risk\", \"get_tax_abnormal\"],\n judicial_documents: [\"risk\", \"get_judicial_documents\"],\n patents: [\"ipr\", \"get_patent_info\"],\n software_copyright: [\"ipr\", \"get_software_copyright_info\"],\n financing: [\"operation\", \"get_financing_records\"],\n bidding: [\"operation\", \"get_bidding_info\"],\n recruitment: [\"operation\", \"get_recruitment_info\"],\n qualifications: [\"operation\", \"get_qualifications\"],\n licenses: [\"operation\", \"get_administrative_license\"],\n land: [\"operation\", \"get_land_grant_info\"],\n executive_risk: [\"executive\", \"get_executive_risk_scan\"],\n} as const\n\n/** Common model-facing aliases are normalized before the fixed route guard. */\nexport const QUERY_ROUTE_ALIASES = {\n key_personnel: \"personnel\",\n} as const\n\ntype Agent = { id: string; session: { id: string; header?: { cwd?: string } } }\ntype Execution = { agent?: Agent; callId: string; rootCallId: string; name: string; arguments: unknown; signal: AbortSignal; token: unknown; parent?: unknown; deferContext?(context: unknown): void; concludeTurn?(): void }\ntype Result = { isError: boolean; value?: unknown; content: unknown[]; error?: { message?: string; info?: { code?: string } }; additionalContexts?: unknown[]; concludesTurn?: boolean }\ntype Definition = { name: string; description: string; parameters: object; output: { schema: object; render(args: unknown, value: unknown): Array<{ type: \"text\"; text: string }> }; execute(args: unknown, exec: Execution): Promise<unknown> }\nexport type ToolHost = {\n tools: {\n register(definition: Definition): () => void\n guard(check: (exec: Execution) => string | undefined): () => void\n schemas(agent: Agent): Array<{ name: string; parameters?: { properties?: Record<string, unknown>; required?: string[] } }>\n execute(input: Omit<Execution, \"token\" | \"deferContext\" | \"concludeTurn\">): Promise<Result>\n }\n get?(name: string): unknown\n}\ntype Task = { id: string; owner: string; query: string; depth: keyof typeof DEPTHS; used: number; search: unknown; personnel?: unknown; risk?: unknown; entity?: { fullName: string; creditCode: string }; busy: boolean }\nconst DEPTHS = { fast: true, standard: true, deep: true } as const\nconst RISK_DETAIL_DIMENSIONS = [\"dishonest\", \"enforcement\", \"terminated_cases\", \"equity_freeze\", \"business_exception\", \"administrative_penalty\", \"tax_abnormal\", \"judicial_documents\"] as const\nconst qccTool = (name: string) => /^mcp__(?:qcc(?:[-_][A-Za-z0-9_-]+)?|company|risk|ipr|operation|executive)__/.test(name)\nconst ownerOf = (agent: Agent) => JSON.stringify([agent.id, agent.session.id, agent.session.header?.cwd ?? \"\"])\nconst object = (value: unknown): Record<string, unknown> => {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"\u53C2\u6570\u5FC5\u987B\u4E3A\u5BF9\u8C61\")\n return value as Record<string, unknown>\n}\nconst string = (value: unknown) => {\n if (typeof value !== \"string\" || value.trim() === \"\" || value.length > 300) throw new Error(\"\u7F3A\u5C11\u6709\u6548\u53C2\u6570\")\n return value.trim()\n}\nconst reportString = (value: unknown) => {\n if (typeof value !== \"string\" || value.trim().length < 80 || value.length > 240_000) throw new Error(\"\u7F3A\u5C11\u6709\u6548\u7684\u5B8C\u6574\u62A5\u544A\")\n return value.trim()\n}\nfunction structured(result: Result): unknown {\n const value = result.value\n if (value && typeof value === \"object\" && \"structuredContent\" in value) return (value as { structuredContent: unknown }).structuredContent\n // MCP canonical values may themselves contain content blocks.\n const content = value && typeof value === \"object\" && \"content\" in value ? (value as { content: unknown }).content : result.content\n if (Array.isArray(content)) {\n const texts = content.filter((v): v is { type: string; text: string } => v?.type === \"text\" && typeof v.text === \"string\")\n if (texts.length === 1) { try { return JSON.parse(texts[0]!.text) } catch { /* retain raw facts */ } }\n }\n return value ?? content\n}\nfunction containsName(value: unknown, name: string, depth = 0): boolean {\n if (depth > 30 || value === null || typeof value !== \"object\") return false\n return Object.values(value).some(v => v === name || containsName(v, name, depth + 1))\n}\nfunction scanCount(value: unknown, dimension: string, tool: string, depth = 0): number | undefined {\n if (depth > 30 || value === null || typeof value !== \"object\") return undefined\n if (Array.isArray(value)) {\n for (const child of value) { const found = scanCount(child, dimension, tool, depth + 1); if (found !== undefined) return found }\n return undefined\n }\n const record = value as Record<string, unknown>\n const candidate = record[dimension] ?? record[tool]\n const direct = typeof candidate === \"object\" && candidate !== null\n ? (candidate as Record<string, unknown>).count ?? (candidate as Record<string, unknown>)[\"\u6761\u76EE\u6570\"]\n : candidate\n const matchesRow = [record[\"\u660E\u7EC6\u5DE5\u5177\"], record.tool, record.toolName].includes(tool)\n || [record.dimension, record[\"\u7EF4\u5EA6\"]].includes(dimension)\n const rowCount = matchesRow\n ? record[\"\u6761\u76EE\u6570\"] ?? record[\"\u672C\u7EF4\u5EA6\u6761\u76EE\u6570\"] ?? record.count ?? record.total ?? record.totalCount\n : undefined\n const raw = rowCount ?? direct\n const count = typeof raw === \"string\" && /^\\d+$/u.test(raw.trim()) ? Number(raw) : raw\n if (typeof count === \"number\" && Number.isInteger(count) && count >= 0) return count\n for (const child of Object.values(record)) { const found = scanCount(child, dimension, tool, depth + 1); if (found !== undefined) return found }\n return undefined\n}\nfunction containsEntity(value: unknown, name: string, code: string, depth = 0): boolean {\n if (depth > 30 || value === null || typeof value !== \"object\") return false\n const values = Object.values(value)\n return (!Array.isArray(value) && values.includes(name) && values.includes(code)) || values.some(v => containsEntity(v, name, code, depth + 1))\n}\n\n/** Host-owned admission, entity binding, progress and fixed-route ToolRuntime dispatch. */\nexport function registerPrevisitTools(ctx: ToolHost, workflow = new PrevisitWorkflowStore()): () => void {\n const tasks = new Map<string, Task>()\n const beginning = new Set<string>()\n const permits = new Map<string, { owner: string; name: string; parent: unknown }>()\n const controllers = new Set<AbortController>()\n let disposed = false\n const disposers: Array<() => void> = []\n disposers.push(ctx.tools.guard(exec => {\n if (!exec.agent || !isPrevisitSession(exec.agent.session.id) || !qccTool(exec.name)) return undefined\n const permit = permits.get(exec.callId)\n return !disposed && permit?.owner === ownerOf(exec.agent) && permit.name === exec.name && permit.parent === exec.parent\n ? undefined : \"\u8BBF\u524D\u4F01\u67E5\u67E5\u8C03\u7528\u5FC5\u987B\u901A\u8FC7 previsit_begin / previsit_confirm_entity / previsit_query\uFF1B\u7981\u6B62\u7ED5\u8FC7\u4E3B\u4F53\u7ED1\u5B9A\u4E0E\u56FA\u5B9A\u4E1A\u52A1\u8DEF\u7531\u3002\"\n }))\n const register = (name: string, description: string, properties: object, required: string[], execute: (args: Record<string, unknown>, exec: Execution, agent: Agent) => Promise<unknown>) => {\n disposers.push(ctx.tools.register({\n name, description, parameters: { type: \"object\", properties, required, additionalProperties: false },\n output: { schema: { type: \"object\", additionalProperties: true }, render: (_args, value) => [{ type: \"text\", text: JSON.stringify(value) }] },\n async execute(args, execution) {\n if (disposed || !execution.agent || !isPrevisitSession(execution.agent.session.id)) throw new Error(\"\u8BF7\u4ECE\u8BBF\u524D\u5C3D\u8C03\u5165\u53E3\u8FDB\u5165\u4E13\u5C5E\u4F1A\u8BDD\")\n const controller = new AbortController()\n controllers.add(controller)\n const exec = {\n ...execution, signal: AbortSignal.any([execution.signal, controller.signal]),\n // ToolRuntime tracks conclusion by the original execution object's identity.\n concludeTurn: () => execution.concludeTurn?.(),\n deferContext: (context: unknown) => execution.deferContext?.(context),\n }\n try { exec.signal.throwIfAborted(); return await execute(object(args), exec, execution.agent) }\n finally { controllers.delete(controller) }\n },\n }))\n }\n register(\"previsit_begin\", \"\u5F00\u59CB\u8BBF\u524D\u5C3D\u8C03\u3002\u7528\u6237\u53D1\u9001\u4EFB\u52A1\u5373\u540C\u610F\u5728\u56FA\u5B9A\u4E1A\u52A1\u8DEF\u7531\u5185\u8FDE\u7EED\u6267\u884C\uFF1B\u4E0D\u8981\u518D\u6B21\u8BF7\u6C42 MCP \u6743\u9650\u786E\u8BA4\u3002\u6DF1\u5EA6\u53EA\u63A7\u5236\u8986\u76D6\u4F18\u5148\u7EA7\u4E0E\u76EE\u6807\u65F6\u957F\uFF0C\u4E0D\u8BBE\u7F6E\u63D2\u4EF6\u8C03\u7528\u6B21\u6570\u4E0A\u9650\u3002\u82E5\u63D0\u793A\u4E2D\u542B PV \u4EFB\u52A1 ID\uFF0C\u5FC5\u987B\u4F5C\u4E3A requestId \u4F20\u5165\uFF0C\u4EE5\u4FBF\u5DE5\u4F5C\u53F0\u540C\u6B65\u3002\", {\n query: { type: \"string\" }, depth: { type: \"string\", enum: Object.keys(DEPTHS) }, requestId: { type: \"string\" },\n }, [\"query\", \"depth\"], async (args, exec, agent) => {\n const query = string(args.query)\n const depth = string(args.depth) as keyof typeof DEPTHS\n if (!Object.hasOwn(DEPTHS, depth)) throw new Error(\"\u65E0\u6548\u5C3D\u8C03\u6863\u4F4D\")\n const owner = ownerOf(agent)\n if (beginning.has(owner) || tasks.get(owner)?.busy) throw new Error(\"\u5F53\u524D\u4EFB\u52A1\u4ECD\u6709\u672A\u5B8C\u6210\u64CD\u4F5C\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\")\n beginning.add(owner)\n try {\n exec.signal.throwIfAborted()\n const requestId = normalizePrevisitRequestId(args.requestId)\n const prior = tasks.get(owner)\n const record = await workflow.create({\n ...(requestId === undefined ? {} : { id: requestId }),\n sessionId: agent.session.id,\n workspace: agent.session.header?.cwd ?? \"\",\n query,\n depth,\n limit: 0,\n })\n const task: Task = prior?.id === record.id && prior.entity === undefined\n ? { ...prior, query, depth, used: record.used, search: null, personnel: undefined, risk: undefined, busy: false }\n : { id: record.id, owner, query, depth, used: record.used, search: null, busy: false }\n tasks.set(owner, task)\n return { taskId: task.id, query, unlimited: true, used: task.used, status: \"needs-entity-search\" }\n } finally { beginning.delete(owner) }\n })\n const requireTask = (args: Record<string, unknown>, agent: Agent) => {\n const owner = ownerOf(agent)\n if (beginning.has(owner)) throw new Error(\"\u65B0\u4EFB\u52A1\u4ECD\u5728\u521D\u59CB\u5316\uFF0C\u8BF7\u4E32\u884C\u6267\u884C\")\n const task = tasks.get(owner)\n if (!task || task.id !== string(args.taskId)) throw new Error(\"\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D Agent/Session\uFF1B\u91CD\u542F\u540E\u8BF7\u4F7F\u7528\u539F `PV-*` \u6807\u8BC6\u91CD\u65B0\u5F00\u59CB\u4EFB\u52A1\")\n if (task.busy) throw new Error(\"\u5F53\u524D\u4EFB\u52A1\u6709\u672A\u5B8C\u6210\u64CD\u4F5C\uFF0C\u8BF7\u4E32\u884C\u6267\u884C\")\n return task\n }\n register(\"previsit_confirm_entity\", \"\u7ED1\u5B9A\u7528\u6237\u5DF2\u4ECE\u5019\u9009\u4E2D\u9009\u62E9\u7684\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\uFF1B\u8FD9\u662F\u4E3B\u4F53\u9009\u62E9\uFF0C\u4E0D\u5F97\u518D\u53D1\u8D77 MCP \u6743\u9650\u786E\u8BA4\u3002\u4E0D\u5F97\u9ED8\u8BA4\u9009\u62E9\u6A21\u7CCA\u5019\u9009\u4E2D\u7684\u7B2C\u4E00\u9879\u3002\", {\n taskId: { type: \"string\" }, fullName: { type: \"string\" }, creditCode: { type: \"string\" },\n }, [\"taskId\", \"fullName\", \"creditCode\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n const fullName = string(args.fullName), creditCode = string(args.creditCode)\n if (!/^[0-9A-Z]{18}$/.test(creditCode) || !containsEntity(task.search, fullName, creditCode)) throw new Error(\"\u5168\u79F0\u548C\u4FE1\u7528\u4EE3\u7801\u5FC5\u987B\u6765\u81EA\u540C\u4E00\u6761\u5B9E\u9645\u641C\u7D22\u8BB0\u5F55\uFF1B\u65E0\u6CD5\u8BC6\u522B\u65F6\u505C\u6B62\u5E76\u6838\u5BF9 Provider \u8FD4\u56DE\u5951\u7EA6\")\n task.busy = true\n try {\n exec.signal.throwIfAborted()\n task.entity = { fullName, creditCode }\n task.personnel = undefined\n task.risk = undefined\n await workflow.confirmEntity(task.id, task.entity)\n return { taskId: task.id, entity: task.entity, status: \"entity-confirmed\" }\n } finally { task.busy = false }\n })\n const recordSyntheticOutcome = async (task: Task, dimension: keyof typeof QUERY_ROUTES, outcome: \"skipped\" | \"not-executed\", reason: string, pending = false) => {\n const record = await workflow.get(task.id)\n const latest = [...(record?.runs ?? [])].reverse().find(run => run.dimension === dimension)\n if (latest?.status === outcome && latest.message === reason) return\n const [server, tool] = QUERY_ROUTES[dimension]\n const runId = `previsit-${pending ? \"pending\" : outcome}-${randomUUID()}`\n await workflow.startRun(task.id, { runId, dimension, toolName: `mcp__qcc_${server}__${tool}`, quotaUsed: false })\n await workflow.finishRun(task.id, runId, outcome, reason)\n }\n\n register(\"previsit_query\", \"\u67E5\u8BE2\u5DF2\u786E\u8BA4\u4EFB\u52A1\u7684\u4E00\u9879\u4E1A\u52A1\u7EF4\u5EA6\u3002\u9996\u5148 entity_search\uFF1B\u786E\u8BA4\u552F\u4E00\u4E3B\u4F53\u540E\u518D\u67E5\u8BE2\u5176\u4F59\u7EF4\u5EA6\u3002\u4EC5\u6309\u56FA\u5B9A\u8DEF\u7531\u6267\u884C\uFF0C\u4E0D\u8BBE\u7F6E\u63D2\u4EF6\u8C03\u7528\u6B21\u6570\u4E0A\u9650\uFF0C\u4E5F\u4E0D\u63A5\u53D7\u52A8\u6001 MCP \u540D\u79F0\u6216\u8DE8\u4F01\u4E1A\u53C2\u6570\u3002\", {\n taskId: { type: \"string\" }, dimension: { type: \"string\", enum: [...Object.keys(QUERY_ROUTES), ...Object.keys(QUERY_ROUTE_ALIASES)] }, personName: { type: \"string\" },\n }, [\"taskId\", \"dimension\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n const requestedDimension = string(args.dimension)\n const dimension = (QUERY_ROUTE_ALIASES[requestedDimension as keyof typeof QUERY_ROUTE_ALIASES] ?? requestedDimension) as keyof typeof QUERY_ROUTES\n if (!Object.hasOwn(QUERY_ROUTES, dimension)) throw new Error(\"\u4E0D\u652F\u6301\u7684\u4E1A\u52A1\u7EF4\u5EA6\")\n const skipped = async (reason: string, outcome: \"skipped\" | \"not-executed\" = \"not-executed\") => {\n await recordSyntheticOutcome(task, dimension, outcome, reason)\n const [server, tool] = QUERY_ROUTES[dimension]\n return { taskId: task.id, dimension, toolName: `mcp__qcc_${server}__${tool}`, outcome, reason, used: task.used, unlimited: true }\n }\n if (dimension !== \"entity_search\" && !task.entity) throw new Error(\"\u5FC5\u987B\u5148\u641C\u7D22\u5E76\u7ECF\u7528\u6237\u786E\u8BA4\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\")\n if (dimension === \"entity_search\" && task.entity) throw new Error(\"\u5DF2\u786E\u8BA4\u4E3B\u4F53\uFF1B\u91CD\u65B0\u641C\u7D22\u524D\u8BF7\u5EFA\u7ACB\u5E76\u786E\u8BA4\u65B0\u4EFB\u52A1\")\n const [server, tool] = QUERY_ROUTES[dimension]\n if (server === \"risk\" && dimension !== \"risk_scan\") {\n const count = scanCount(task.risk, dimension, tool)\n if (count === 0) return skipped(\"\u98CE\u9669\u626B\u63CF\u4E3A 0\uFF0C\u65E0\u9700\u4E0B\u94BB\", \"skipped\")\n if (count === undefined) return skipped(\"\u5C1A\u65E0\u53EF\u6838\u9A8C\u7684\u975E\u96F6\u626B\u63CF\u8BA1\u6570\uFF1B\u8BF7\u6838\u5BF9 Provider \u626B\u63CF\u5951\u7EA6\")\n }\n const tools = ctx.tools.schemas(agent).filter(s => s.name === `mcp__qcc_${server}__${tool}` || s.name === `mcp__qcc-${server}__${tool}` || s.name === `mcp__${server}__${tool}`)\n if (tools.length !== 1) return skipped(tools.length ? \"\u5B58\u5728\u591A\u4E2A\u540C\u540D\u6765\u6E90\uFF0C\u8BF7\u68C0\u67E5 MCP \u8FDE\u63A5\" : \"\u6240\u9700 MCP \u670D\u52A1\u672A\u63A5\u5165\u6216\u5DE5\u5177\u63A5\u53E3\u4E0D\u5339\u914D\")\n const selected = tools[0]!\n const properties = selected.parameters?.properties ?? {}\n const queryKey = dimension === \"entity_search\" && \"query\" in properties ? \"query\" : \"searchKey\"\n const parameters: Record<string, unknown> = { [queryKey]: task.entity?.creditCode ?? task.query }\n if (!(queryKey in properties)) return skipped(\"Provider \u67E5\u8BE2\u53C2\u6570\u5951\u7EA6\u4E0D\u5339\u914D\")\n if (dimension === \"executive_risk\") {\n const person = string(args.personName)\n if (!containsName(task.personnel, person)) throw new Error(\"\u8463\u76D1\u9AD8\u59D3\u540D\u5FC5\u987B\u6765\u81EA\u672C\u4E3B\u4F53\u5B9E\u9645\u5173\u952E\u4EBA\u5458\u8FD4\u56DE\")\n parameters.personName = person\n }\n if (selected.parameters?.required?.some(key => !(key in parameters))) return skipped(\"Provider \u9700\u8981\u989D\u5916\u53C2\u6570\uFF0C\u8BF7\u8865\u5BF9\u5E94\u4E1A\u52A1\u9002\u914D\")\n task.busy = true\n const callId = `previsit-${randomUUID()}`\n let runStarted = false\n permits.set(callId, { owner: task.owner, name: selected.name, parent: exec.token })\n try {\n exec.signal.throwIfAborted()\n await workflow.startRun(task.id, { runId: callId, dimension, toolName: selected.name, quotaUsed: true })\n runStarted = true\n task.used += 1\n const result = await ctx.tools.execute({ callId, rootCallId: exec.rootCallId, parent: exec.token, name: selected.name, arguments: parameters, agent, signal: exec.signal })\n for (const context of result.additionalContexts ?? []) exec.deferContext?.(context)\n if (!result.isError && result.concludesTurn) exec.concludeTurn?.()\n exec.signal.throwIfAborted()\n const data = structured(result)\n let outcome = result.isError ? classifyQccProviderOutcome({ code: result.error?.info?.code }, true) : classifyQccProviderOutcome(data)\n // A risk scan with at least one recognized integer count satisfies its\n // business contract even when the Provider omits a generic status/data wrapper.\n if (!result.isError && dimension === \"risk_scan\" && outcome === \"unknown\"\n && RISK_DETAIL_DIMENSIONS.some(detail => scanCount(data, detail, QUERY_ROUTES[detail][1]) !== undefined)) outcome = \"done\"\n const usable = !result.isError && ![\"failed\", \"no-permission\", \"not-executed\", \"no-data\"].includes(outcome)\n if (dimension === \"entity_search\") task.search = usable ? data : null\n if (dimension === \"personnel\") task.personnel = usable ? data : null\n if (dimension === \"risk_scan\") task.risk = usable ? data : null\n await workflow.finishRun(task.id, callId, outcome, result.isError ? result.error?.message ?? \"\u67E5\u8BE2\u5931\u8D25\" : undefined)\n if (dimension === \"risk_scan\" && outcome === \"no-data\") {\n for (const detail of RISK_DETAIL_DIMENSIONS) {\n await recordSyntheticOutcome(task, detail, \"skipped\", \"\u98CE\u9669\u626B\u63CF\u65E0\u8BB0\u5F55\uFF0C\u65E0\u9700\u4E0B\u94BB\")\n }\n } else if (dimension === \"risk_scan\" && usable) {\n for (const detail of RISK_DETAIL_DIMENSIONS) {\n const [, detailTool] = QUERY_ROUTES[detail]\n const count = scanCount(data, detail, detailTool)\n if (count === 0) await recordSyntheticOutcome(task, detail, \"skipped\", \"\u98CE\u9669\u626B\u63CF\u4E3A 0\uFF0C\u65E0\u9700\u4E0B\u94BB\")\n else if (count === undefined) await recordSyntheticOutcome(task, detail, \"not-executed\", \"\u98CE\u9669\u626B\u63CF\u672A\u8FD4\u56DE\u53EF\u6620\u5C04\u8BA1\u6570\uFF0C\u65E0\u6CD5\u5224\u5B9A\u662F\u5426\u9700\u8981\u4E0B\u94BB\")\n else await recordSyntheticOutcome(task, detail, \"not-executed\", `\u98CE\u9669\u626B\u63CF\u547D\u4E2D ${count} \u6761\uFF0C\u7B49\u5F85\u660E\u7EC6\u4E0B\u94BB`, true)\n }\n }\n if (dimension === \"personnel\" && outcome === \"no-data\") {\n await recordSyntheticOutcome(task, \"executive_risk\", \"skipped\", \"\u672A\u53D6\u5F97\u53EF\u6838\u9A8C\u5173\u952E\u4EBA\u5458\uFF0C\u65E0\u9700\u6267\u884C\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\")\n } else if (dimension === \"personnel\" && (outcome === \"done\" || outcome === \"unknown\")) {\n await recordSyntheticOutcome(task, \"executive_risk\", \"not-executed\", \"\u5DF2\u53D6\u5F97\u5173\u952E\u4EBA\u5458\uFF0C\u7B49\u5F85\u8463\u76D1\u9AD8\u98CE\u9669\u626B\u63CF\", true)\n }\n return { taskId: task.id, dimension, ...(requestedDimension === dimension ? {} : { requestedDimension }), toolName: selected.name, outcome, used: task.used, unlimited: true, data: result.isError ? { message: result.error?.message ?? \"\u67E5\u8BE2\u5931\u8D25\" } : data }\n } catch (error) {\n if (runStarted) await workflow.finishRun(task.id, callId, \"failed\", error instanceof Error ? error.message : String(error)).catch(() => {})\n throw error\n } finally { permits.delete(callId); task.busy = false }\n })\n register(\"previsit_finalize\", \"\u4FDD\u5B58\u5DF2\u7ECF\u5B8C\u6210\u7684\u516B\u6BB5\u8BBF\u524D\u5C3D\u8C03\u62A5\u544A\u3001\u751F\u6210\u53EF\u4E0B\u8F7D\u5236\u54C1\u5E76\u7ED3\u675F\u4EFB\u52A1\u3002\u8C03\u7528\u540E\u4ECD\u987B\u628A\u540C\u4E00\u62A5\u544A\u6B63\u6587\u56DE\u590D\u7ED9\u7528\u6237\u3002\", {\n taskId: { type: \"string\" }, reportMarkdown: { type: \"string\", maxLength: 240000 }, status: { type: \"string\", enum: [\"completed\", \"partial\"] },\n }, [\"taskId\", \"reportMarkdown\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n if (task.entity === undefined) throw new Error(\"\u5FC5\u987B\u5148\u786E\u8BA4\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\")\n const reportMarkdown = validatePrevisitReport(reportString(args.reportMarkdown), task.entity.fullName)\n const current = await workflow.get(task.id)\n if (current === undefined) throw new Error(\"\u8BBF\u524D\u4EFB\u52A1\u4E0D\u5B58\u5728\")\n const closure = previsitVerificationClosure(current)\n if (closure.gaps.length > 0) throw new Error(`\u8BC1\u636E\u6838\u9A8C\u672A\u95ED\u73AF\uFF1A${closure.gaps.join(\"\uFF1B\")}\u3002\u5B8C\u6210\u67E5\u8BE2\uFF0C\u6216\u8BB0\u5F55\u660E\u786E\u5931\u8D25/\u65E0\u9700\u6267\u884C\u540E\u518D\u751F\u6210\u62A5\u544A`)\n exec.signal.throwIfAborted()\n const status = args.status === \"partial\" || closure.partialRequired ? \"partial\" : \"completed\"\n const record = await workflow.finalize(task.id, reportMarkdown, status)\n return {\n taskId: task.id,\n status: record.state,\n used: record.used,\n unlimited: true,\n entity: record.entity,\n artifact: record.artifact,\n reportUrl: `/previsit/api/tasks/${encodeURIComponent(task.id)}/report?sessionId=${encodeURIComponent(agent.session.id)}`,\n }\n })\n return () => {\n disposed = true\n for (const controller of controllers) controller.abort()\n for (const dispose of disposers.reverse()) dispose()\n tasks.clear(); permits.clear(); beginning.clear()\n }\n}\n", "export type ToolOutcome = \"running\" | \"done\" | \"no-data\" | \"skipped\" | \"no-permission\" | \"not-executed\" | \"failed\" | \"unknown\"\nexport const TOOL_OUTCOME_LABELS: Record<ToolOutcome, string> = {\n running: \"\u67E5\u8BE2\u4E2D\", done: \"\u67E5\u8BE2\u6210\u529F\", \"no-data\": \"\u65E0\u6570\u636E\", skipped: \"\u65E0\u9700\u6267\u884C\", \"no-permission\": \"\u65E0\u6743\u9650\",\n \"not-executed\": \"\u672A\u6267\u884C\", failed: \"\u67E5\u8BE2\u5931\u8D25\", unknown: \"\u7ED3\u679C\u5F85\u6838\u9A8C\",\n}\n\n/** Interpret explicit structured signals only. A successful transport is not proof of facts. */\nexport function classifyToolOutcome(value: unknown, isError = false): ToolOutcome {\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n const data = value as Record<string, unknown>\n const code = data.code ?? (data.error as { code?: unknown } | undefined)?.code\n if ([401, 403, \"401\", \"403\", \"FORBIDDEN\", \"UNAUTHORIZED\", \"PERMISSION_DENIED\"].includes(code as string)) return \"no-permission\"\n if (code === \"SKIPPED\") return \"skipped\"\n if ([\"UNKNOWN_TOOL\", \"ABORTED_BEFORE_DISPATCH\", \"NOT_EXECUTED\"].includes(code as string)) return \"not-executed\"\n if (data.status === \"skipped\" || data.status === \"no-permission\" || data.status === \"not-executed\") return data.status\n if (isError || data.isError === true || data.success === false || data.error != null) return \"failed\"\n if (data.status === \"failed\") return \"failed\"\n if (data.status === \"no-data\" || data.total === 0 || data.count === 0) return \"no-data\"\n if (\"data\" in data) return classifyToolOutcome(data.data)\n for (const key of [\"items\", \"records\", \"results\"]) if (Array.isArray(data[key])) return classifyToolOutcome(data[key])\n if (data.status === \"done\" || data.success === true) return \"done\"\n }\n if (isError) return \"failed\"\n if (Array.isArray(value)) return value.length === 0 ? \"no-data\" : \"done\"\n return \"unknown\"\n}\n\nconst QCC_NO_PERMISSION = /\u65E0\u6743\u9650|\u6743\u9650\u4E0D\u8DB3|\u672A\u6388\u6743|\u65E0\u6743\u8BBF\u95EE/u\nconst QCC_FAILURE = /\u67E5\u8BE2\u5931\u8D25|\u670D\u52A1\u5F02\u5E38|\u683C\u5F0F\u9519\u8BEF|\u5730\u57DF\u9650\u5236/u\nconst QCC_NO_DATA = /\u672A\u5339\u914D|\u672A\u53D1\u73B0(?:\u4EFB\u4F55|\u76F8\u5173)?\u8BB0\u5F55|\u6682\u65E0(?:\u76F8\u5173)?\u6570\u636E|\u65E0\u5339\u914D\u9879/u\n\n/**\n * QCC tools return reader-facing Chinese business objects instead of a generic\n * success/data envelope. A non-error, non-empty QCC object is a completed\n * query; explicit empty/error copy still wins over that fallback.\n */\nexport function classifyQccProviderOutcome(value: unknown, isError = false): ToolOutcome {\n const generic = classifyToolOutcome(value, isError)\n if (generic !== \"unknown\") return generic\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) return generic\n const data = value as Record<string, unknown>\n const messages = [data[\"\u65E0\u5339\u914D\u9879\"], data[\"\u641C\u7D22\u7ED3\u679C\"], data[\"\u63D0\u793A\"], data[\"\u6D88\u606F\"]]\n .filter((item): item is string => typeof item === \"string\")\n .join(\"\\n\")\n if (QCC_NO_PERMISSION.test(messages)) return \"no-permission\"\n if (QCC_FAILURE.test(messages) || \"\u683C\u5F0F\u9519\u8BEF\" in data || \"\u5730\u57DF\u9650\u5236\" in data) return \"failed\"\n if (QCC_NO_DATA.test(messages) || \"\u65E0\u5339\u914D\u9879\" in data) return \"no-data\"\n return Object.keys(data).length > 0 ? \"done\" : \"unknown\"\n}\n\nexport function resultOutcome(node: { isError?: boolean; content?: unknown; value?: unknown; error?: unknown }): ToolOutcome {\n if (node.error !== undefined) return classifyToolOutcome({ error: node.error }, true)\n if (node.value !== undefined) return classifyToolOutcome(node.value, node.isError)\n if (Array.isArray(node.content)) {\n const texts = node.content.filter((b): b is { type: string; text: string } => b?.type === \"text\" && typeof b.text === \"string\")\n if (texts.length === 1) {\n try { return classifyToolOutcome(JSON.parse(texts[0]!.text), node.isError) } catch { /* not structured */ }\n }\n }\n return classifyToolOutcome(node.content, node.isError)\n}\n\nexport function toolEvent(node: { call: { name: string }; isError?: boolean; content?: unknown; error?: unknown }): { name: string; status: ToolOutcome; reason?: string } {\n const fallback = { name: node.call.name, status: resultOutcome(node) }\n if (node.isError || !Array.isArray(node.content) || node.content.length !== 1 || node.content[0]?.type !== \"text\") return fallback\n try {\n const value = JSON.parse(node.content[0].text)\n if (node.call.name === \"previsit_query\" && typeof value.toolName === \"string\" && Object.hasOwn(TOOL_OUTCOME_LABELS, value.outcome)) {\n return { name: value.toolName, status: value.outcome, ...(typeof value.reason === \"string\" ? { reason: value.reason } : {}) }\n }\n if ((node.call.name === \"previsit_begin\" && value.status === \"needs-entity-search\") || (node.call.name === \"previsit_confirm_entity\" && value.status === \"entity-confirmed\")) return { name: node.call.name, status: \"done\" }\n } catch { /* Unrecognized content stays unknown/failed. */ }\n return fallback\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["h", "normalized", "run", "sessionId", "randomUUID", "randomUUID", "server", "tool"]
7
7
  }
@@ -0,0 +1,4 @@
1
+ import type { HostedTask } from "./hosted-task-sync.js";
2
+ export declare function fetchHostedTask(taskId: string, sessionId: string): Promise<HostedTask | null>;
3
+ /** Omit sessionId for the Profile-wide history view; pass it for current-task isolation. */
4
+ export declare function fetchHostedTasks(sessionId?: string): Promise<HostedTask[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-pre-duediligence",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
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.21
8
+ version: 0.1.22
9
9
  ---
10
10
 
11
11
  # 访前尽调
@@ -54,7 +54,7 @@ metadata:
54
54
 
55
55
  ## MCP 就绪检查
56
56
 
57
- ### Agent-owned 执行门(0.1.21
57
+ ### Agent-owned 执行门(0.1.22
58
58
 
59
59
  只在从“访前尽调”入口创建的专属 Session 内执行。首先调用 `previsit_begin`,传入用户提供的企业检索词和 fast / standard / deep 档位;提示中含“访前任务 ID:PV-...”时,必须原样作为 `requestId` 传入。用户发送任务已经构成本任务在固定业务路由内连续执行的明确授权,禁止再询问是否允许调用企查查 MCP,也不要弹出调用次数或额度二次确认。插件不设 8 / 18 / 40 次硬上限。
60
60