dsh-pre-duediligence 0.1.13 → 0.1.14

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,13 @@
1
1
  # 更新日志
2
2
  ## [Unreleased]
3
3
 
4
+ ## [0.1.14] - 2026-09-11
5
+
6
+ - 业务入口改用 DSH 公开 `sessions.create({ workspaceId, sessionId })` 契约,将新建的命名空间 Session 直接归属当前、最近或首个可用 Workspace,不再落入“未分组”。
7
+ - 为 DSH 0.1.1 的 `workspaces.connectWorkspace` 和 0.1.2 的 `uiWorkspace.connectWorkspace` 增加普通新会话隔离;如宿主候选到访前尽调、数据清洗补全、AI 填表或招投标的业务空 Session,改用同 Workspace 中可复用的普通空 Session 或新建普通 Session。
8
+ - 普通会话复用排除已归档、跨目录、跨 Workspace 和其它业务命名空间;并发新会话请求按 Workspace 合并,失败不导航、不污染当前页。
9
+ - 无 Sidebar、Sidebar `0.18.1` 和 `0.17.1` 的隔离真实 Host 回归均通过;初始关闭、流程按钮单例 Tab、Tab X 与宿主折叠恢复行为保持不变。
10
+
4
11
  ## [0.1.13] - 2026-09-10
5
12
 
6
13
  - 基础安装不再默认安装 Better Sidebar;仅在 `DSH_PREVISIT_WORKBENCH=on` 时安装成套工作台版本。
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # dsh-pre-duediligence
2
2
 
3
- 0.1.13 Better Sidebar 改为显式选装的可视化工作台;默认基础安装仅需 DSH MCP Connector,并保留无侧栏的原生会话、提示词和 Host 工具链。发布内容与验证边界见 [发布说明](docs/RELEASE-0.1.13.md)、[采用记录](docs/DSH-UX-001-ADOPTION.md)和[兼容矩阵](docs/COMPATIBILITY.md)。
3
+ 0.1.14 将访前业务 Session 归属到当前 Workspace,并保护 DSH 原生“新建会话”不复用访前尽调、数据清洗补全、AI 填表或招投标的业务空会话。无 Sidebar 基础路径和可选 Better Sidebar 工作台均保持原有能力。发布内容与验证边界见 [发布说明](docs/RELEASE-0.1.14.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.13
10
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.14
11
11
  ```
12
12
 
13
13
  请先满足下文的 DSH 与连接器要求;Better Sidebar 仅在需要可视化工作台时选装。安装后完整停止并重启对应 Profile。
@@ -23,7 +23,7 @@ dsh plugin --profile web add dsh-pre-duediligence@0.1.13
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.13**。本版采用 DSH-UX-001 v1.5.1:保留 Better Sidebar 作为可选工作台增强,基础安装与运行不再以它为前置;不抢占 DSH `details`、不用 `shell.overlay` / 自建 fixed 侧栏降级,也不把尚未进入验证基线的原生右栏、dockkit file-upload 引入本轮依赖。企查查 Provider 和四款业务插件共装仍待验收。npm 发布与组合验收是独立状态。详见 [更新日志](CHANGELOG.md)、[采用记录](docs/DSH-UX-001-ADOPTION.md)、[兼容与验收矩阵](docs/COMPATIBILITY.md)和[市场投稿登记](docs/MARKETPLACE.md)。
26
+ 版本:**0.1.14**。本版采用 DSH-UX-001 v1.5.2:业务会话使用公开 `workspaceId` 创建契约归属当前 Workspace;原生新建会话在候选为四款业务空 Session 时会改用同 Workspace 的普通 Session。Better Sidebar 仍是可选增强,无 Sidebar 降级、初始关闭、单例 Tab 和任务状态边界不变。本轮未配置模型或企查查 OAuth,不声明真实 Provider 业务验收通过。详见 [更新日志](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
 
@@ -35,6 +35,7 @@ dsh plugin --profile web add dsh-pre-duediligence@0.1.13
35
35
 
36
36
  - Better Sidebar 为可选工作台依赖,不是基础安装前置;缺失时保留原生会话、提示词生成、原生发送、Skill/工具链和会话报告阅读,点击工作台流程按钮时明确提示且不清空草稿或任务状态
37
37
  - 只在 DSH 左侧菜单增加“访前尽调”入口;对应工作台标签在 Better Sidebar 的右侧“+”菜单中隐藏
38
+ - 访前专属 Session 直接归属当前 Workspace,不进入“未分组”;DSH 原生“新建会话”不会复用四款业务智能体的空 Session
38
39
  - 菜单、首页与工作台统一使用企查查蓝 Mockup 的线性建筑 LOGO;初始页面为“访前尽调一页纸智能体”
39
40
  - 点击左侧入口只创建并进入访前 Session,初始页不打开右侧工作台;点击输入框下方五项业务按钮后才打开对应视图
40
41
  - 首页只保留简洁说明、DSH 原生输入框和输入框下方五项快捷菜单:对象与目标、范围确认、资料采集、证据核验、任务历史;菜单采用数据清洗补全式单行描边卡片,上图标下短标签,窄屏在容器内横向滚动
@@ -90,7 +91,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-pre-duediligenc
90
91
  安装脚本默认向 DSH Web profile 安装两个基础 Bundle:
91
92
 
92
93
  1. `dsh-mcp-connector@0.2.32`:提供通用 MCP 连接器与市场,通过“企查查·企业工商”完成 OAuth,并动态挂载企业、风险、知产、经营、历史和董监高 MCP。
93
- 2. `dsh-pre-duediligence@0.1.13`:提供左侧智能体入口、原生会话首页、提示词生成器、Host 执行门和 Skill。
94
+ 2. `dsh-pre-duediligence@0.1.14`:提供左侧智能体入口、原生会话首页、提示词生成器、Host 执行门和 Skill。
94
95
 
95
96
  默认不会安装 Better Sidebar。需要 Session 级可视化工作台时显式开启:
96
97
 
@@ -118,7 +119,7 @@ dsh web
118
119
 
119
120
  ~~~bash
120
121
  dsh plugin --profile web add dsh-mcp-connector@0.2.32
121
- dsh plugin --profile web add dsh-pre-duediligence@0.1.13 --allow-build=node-pty,dsh-pre-duediligence
122
+ dsh plugin --profile web add dsh-pre-duediligence@0.1.14 --allow-build=node-pty,dsh-pre-duediligence
122
123
  dsh web
123
124
  ~~~
124
125
 
@@ -134,7 +135,7 @@ dsh web
134
135
  dsh plugin --profile web list --depth 0
135
136
  ~~~
136
137
 
137
- 应始终看到所选基线的 Connector 和 `dsh-pre-duediligence@0.1.13`;仅在显式启用或原本已安装时看到 Sidebar。重启 DSH 并连接“企查查·企业工商”后,在左侧菜单点击“访前尽调”进入初始原生会话。无 Sidebar 时可继续生成提示词、回填/发送草稿并在会话中阅读报告;五项流程按钮会提示安装可选工作台。安装 Sidebar 时,右侧工作台仍默认关闭,点击业务按钮后才打开对应视图;其右侧“+”菜单不会列出该入口,普通会话保持 DSH 原样。
138
+ 应始终看到所选基线的 Connector 和 `dsh-pre-duediligence@0.1.14`;仅在显式启用或原本已安装时看到 Sidebar。重启 DSH 并连接“企查查·企业工商”后,在左侧菜单点击“访前尽调”进入初始原生会话。无 Sidebar 时可继续生成提示词、回填/发送草稿并在会话中阅读报告;五项流程按钮会提示安装可选工作台。安装 Sidebar 时,右侧工作台仍默认关闭,点击业务按钮后才打开对应视图;其右侧“+”菜单不会列出该入口,普通会话保持 DSH 原样。
138
139
 
139
140
  ### 无 Sidebar 功能边界
140
141
 
@@ -1,5 +1,17 @@
1
1
  # 访前尽调兼容与验收记录
2
2
 
3
+ ## 0.1.14:Workspace 归组与普通会话隔离(2026-09-11)
4
+
5
+ | 路径 | 组合 | Workspace / Session 结论 | 容器结论 |
6
+ | --- | --- | --- | --- |
7
+ | 无 Sidebar | DSH `0.1.2-rc.1` / Connector `0.2.37` / 候选包 `0.1.14` | 业务 Session 写入所选 Workspace;只有业务空 Session 时,原生新会话创建并归组普通 `session-*` | 入口初始只显示原生会话与五项流程按钮;点流程显示可选 Sidebar 提示,无伪工作台 |
8
+ | candidate Sidebar | DSH `0.1.2-rc.1` / Sidebar `0.18.1` / Connector `0.2.37` / 候选包 `0.1.14` | 使用 `uiWorkspace.connectWorkspace` 返回过滤;业务与普通 Session 均归属选中 Workspace | 初始关闭;流程按钮单例 Tab;Tab X 重开、宿主折叠恢复均通过 |
9
+ | stable Sidebar | DSH `0.1.1-rc.2` / Sidebar `0.17.1` / Connector `0.2.32` / 候选包 `0.1.14` | 使用 `workspaces.connectWorkspace` 返回过滤;在只有业务空 Session 时新建并归组普通 `session-*` | 初始关闭;流程按钮单例 Tab;Tab X 重开、宿主折叠恢复均通过 |
10
+
11
+ 实现仅使用公开客户端能力:业务入口通过 `sessions.create({ workspaceId, sessionId })` 创建命名空间 Session;原生新会话在导航服务选到任一四款业务命名空间时,复用同 Workspace / 同 `cwd` / 未归档的普通空 Session,或通过 `sessions.create({ workspaceId })` 新建。没有 DOM 点击模拟、存储文件直接改写或跨 Workspace 复用。
12
+
13
+ 自动化:Node 24 的 `pnpm check` 通过 18 个测试文件 / 125 项断言、类型检查和构建;`pnpm test:ui` 通过 4 个隔离 Chrome 场景。真实 Host 回归使用三个临时 `DSH_HOME` 和临时 Workspace;stable Profile 的旧版 live patch watcher 在当前机器上遇到 `EMFILE`,回归时仅对该临时 Profile 启用 chokidar polling,不改产品配置与用户 Profile。本轮未配置模型/OAuth,未发送消息、调用付费 MCP 或生成真实报告,也未读写生产 `~/.dsh`。
14
+
3
15
  ## 0.1.13:可选 Sidebar 第一阶段
4
16
 
5
17
  目标是让基础智能体在没有 Better Sidebar 时独立安装和运行,同时保留兼容 Sidebar 的现有工作台;不在本阶段重写工作台容器。
@@ -1,5 +1,18 @@
1
1
  # DSH-UX-001 采纳与验收记录
2
2
 
3
+ ## 0.1.14:DSH-UX-001 v1.5.2 Workspace 归组与普通会话隔离
4
+
5
+ 日期:2026-09-11。本轮延续 v1.5.1 的可选工作台边界,只修正 Workspace / Session 所有权和原生新会话的隔离语义;不改页面信息架构、容器选择或业务状态真实性规则。
6
+
7
+ | v1.5.2 要求 | 本仓实现 | 保护边界 |
8
+ | --- | --- | --- |
9
+ | 业务 Session 归属当前 Workspace | 按当前 Session 所属 Workspace、`recentWorkspaceId`、首个可用 Workspace 的顺序解析 `workspaceId`;通过 `sessions.create({ workspaceId, sessionId })` 创建 | 保留 `session-dsh-pre-duediligence-*` 命名空间和严格返回 id 校验;无 Workspace 或创建失败时不导航 |
10
+ | 普通“新建会话”不复用业务空 Session | 同时适配 DSH 0.1.1 的 `workspaces.connectWorkspace` 与 DSH 0.1.2 的 `uiWorkspace.connectWorkspace` | 统一排除访前尽调、数据清洗补全、AI 填表、招投标四个命名空间;不改普通 Session 的宿主返回 |
11
+ | 可复用候选不跨边界 | 仅选择同 Workspace 的 `sessionIds` 成员、同 `cwd`、`blank === true`、且未归档的普通 Session | 无合法候选时以 `sessions.create({ workspaceId })` 新建;并发请求按 Workspace 合并,失败原样向上抛出 |
12
+ | 保留已有容器交互 | 入口只进入业务 Session,初始不打开 Sidebar;五项流程按钮定位同一 Session 单例 Tab | 无 Sidebar 只给可执行提示;Tab X 和宿主折叠后从原流程按钮恢复,不变更任务/历史/制品 |
13
+
14
+ 自动化证据:Node 24 下 18 个测试文件 / 125 项断言、类型检查和构建通过;浅/深色 × 1440×900 / 390×700 的 4 个隔离 Chrome UI 场景通过。真实 Host 在三个临时 `DSH_HOME` 路径验证无 Sidebar、Sidebar `0.18.1`、Sidebar `0.17.1`;两个 DSH 基线均覆盖只有业务空 Session 时的普通新会话创建与归组。未配置模型/OAuth,未调用付费 MCP,未读写生产 `~/.dsh`。
15
+
3
16
  ## 0.1.13:DSH-UX-001 v1.5.1 可选工作台第一阶段
4
17
 
5
18
  - Better Sidebar 继续作为唯一可视化工作台容器,但不再是基础安装前置;本阶段不新增自有抽屉,也不开始原生容器替换。
@@ -0,0 +1,14 @@
1
+ # dsh-pre-duediligence 0.1.14
2
+
3
+ Version: **0.1.14**
4
+ Status: **release**
5
+
6
+ 本版采用 DSH-UX-001 v1.5.2,修复业务 Session 的 Workspace 归组与普通会话隔离。左侧“访前尽调”入口新建 Session 时传入选中 Workspace 的 `workspaceId`,保留 `session-dsh-pre-duediligence-*` 命名空间,并让宿主把该 Session 记录到 Workspace 的 `sessionIds`,不再出现“未分组”。
7
+
8
+ DSH 原生“新建会话”仍保持普通 Session 语义。本插件在公开 Workspace 导航返回四款业务智能体的空 Session 时,仅筛选同 Workspace、同目录、未归档的普通空 Session;无可复用候选则通过 `sessions.create({ workspaceId })` 新建。同一 Workspace 的并发请求共用一次创建,失败原样向上返回。
9
+
10
+ 发布门禁包括 Node 24 下 18 个测试文件 / 125 项断言、类型检查、Host/Client 构建和 4 个隔离 Chrome UI 场景。候选 tarball 另在三个临时 `DSH_HOME` 下完成真实 Web Host 回归:无 Sidebar、DSH `0.1.2-rc.1` + Sidebar `0.18.1`、DSH `0.1.1-rc.2` + Sidebar `0.17.1`。两个 DSH 基线都验证了“Workspace 只有业务空 Session”时的普通新会话创建与归组;两个 Sidebar 版本都验证了初始关闭、流程按钮单例 Tab、Tab X 重开和宿主折叠恢复。
11
+
12
+ 验收没有配置模型密钥或企查查 OAuth,没有调用付费 MCP 或生成真实企业报告。所有运行均使用临时 `DSH_HOME`,未读写生产 `~/.dsh`。
13
+
14
+ 回滚可安装 `dsh-pre-duediligence@0.1.13`,并恢复升级前记录的整套 DSH / Sidebar / Connector 版本;不要移动既有 tag 或覆盖已发布 npm 版本。
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.13}"
8
+ readonly PREVISIT_SPEC="${DSH_PRE_DUEDILIGENCE_SPEC:-dsh-pre-duediligence@0.1.14}"
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
@@ -297,12 +297,15 @@ var PREVISIT_SESSION_ID_PREFIX = "session-dsh-pre-duediligence-";
297
297
  function isPrevisitSession(sessionId) {
298
298
  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);
299
299
  }
300
- async function createPrevisitSession(ctx) {
300
+ function resolvePrevisitWorkspaceId(ctx) {
301
301
  const workspace = ctx.workspaces?.list?.getSnapshot();
302
302
  const current = ctx.sessions.list?.getSnapshot().current;
303
303
  const items = workspace?.items ?? [];
304
- const cwd = items.find((item) => current !== void 0 && item.sessionIds?.includes(current))?.path ?? items.find((item) => item.workspaceId === workspace?.recentWorkspaceId)?.path ?? items[0]?.path;
305
- if (!cwd) throw new Error("\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u518D\u6253\u5F00\u8BBF\u524D\u5C3D\u8C03");
304
+ return items.find((item) => current !== void 0 && item.sessionIds?.includes(current))?.workspaceId ?? items.find((item) => item.workspaceId === workspace?.recentWorkspaceId)?.workspaceId ?? items[0]?.workspaceId;
305
+ }
306
+ async function createPrevisitSession(ctx) {
307
+ const workspaceId = resolvePrevisitWorkspaceId(ctx);
308
+ if (!workspaceId) throw new Error("\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u518D\u6253\u5F00\u8BBF\u524D\u5C3D\u8C03");
306
309
  if (typeof ctx.sessions.create !== "function" || typeof ctx.sessions.open !== "function") {
307
310
  throw new Error("\u5F53\u524D DSH \u7248\u672C\u6CA1\u6709\u53EF\u7528\u7684\u4F1A\u8BDD\u521B\u5EFA\u80FD\u529B");
308
311
  }
@@ -310,7 +313,7 @@ async function createPrevisitSession(ctx) {
310
313
  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");
311
314
  }
312
315
  const requested = PREVISIT_SESSION_ID_PREFIX + globalThis.crypto.randomUUID();
313
- const created = await ctx.sessions.create({ cwd, sessionId: requested });
316
+ const created = await ctx.sessions.create({ workspaceId, sessionId: requested });
314
317
  if (created !== requested) throw new Error("\u8BBF\u524D\u5C3D\u8C03\u4F1A\u8BDD\u6807\u8BC6\u4E0D\u5339\u914D\uFF0C\u8BF7\u91CD\u8BD5");
315
318
  return created;
316
319
  }
@@ -1344,6 +1347,53 @@ function PrevisitHome({ sessionId, useSession, openWorkbench: openWorkbench2 })
1344
1347
  ] });
1345
1348
  }
1346
1349
 
1350
+ // src/ordinary-session-guard.ts
1351
+ var BUSINESS_SESSION_PATTERN = /^session-dsh-(?:pre-duediligence|data-cleaning-agent|form-fill-agent|tender-workbench)-/u;
1352
+ function isBusinessSession(sessionId) {
1353
+ return BUSINESS_SESSION_PATTERN.test(sessionId);
1354
+ }
1355
+ function installOrdinarySessionGuard(host, navigation) {
1356
+ const original = navigation.connectWorkspace;
1357
+ if (typeof original !== "function") return () => {
1358
+ };
1359
+ const pending = /* @__PURE__ */ new Map();
1360
+ let active = true;
1361
+ const guarded = async function(workspaceId) {
1362
+ const selected = await original.call(this, workspaceId);
1363
+ if (!active || !isBusinessSession(selected)) return selected;
1364
+ const existing = pending.get(workspaceId);
1365
+ if (existing !== void 0) return existing;
1366
+ const resolveOrdinary = async () => {
1367
+ const workspace = host.workspaces?.list?.getSnapshot();
1368
+ const target = workspace?.items?.find((item) => item.workspaceId === workspaceId);
1369
+ if (target === void 0) throw new Error("\u65B0\u4F1A\u8BDD\u6240\u5C5E\u5DE5\u4F5C\u7A7A\u95F4\u4E0D\u53EF\u7528");
1370
+ const snapshot = host.sessions.list?.getSnapshot();
1371
+ const ids = snapshot?.ids ?? Object.keys(snapshot?.byId ?? {});
1372
+ const archived = new Set(workspace?.archivedSessionIds ?? []);
1373
+ const reusable = ids.find((id) => {
1374
+ const summary = snapshot?.byId?.[id];
1375
+ return typeof target.path === "string" && !isBusinessSession(id) && summary?.blank === true && summary.cwd === target.path && target.sessionIds?.includes(id) === true && !archived.has(id);
1376
+ });
1377
+ if (reusable !== void 0) return reusable;
1378
+ const create = host.sessions.create;
1379
+ if (typeof create !== "function") throw new Error("\u5F53\u524D DSH \u7248\u672C\u6CA1\u6709\u53EF\u7528\u7684\u666E\u901A\u4F1A\u8BDD\u521B\u5EFA\u80FD\u529B");
1380
+ const created = await create.call(host.sessions, { workspaceId });
1381
+ if (isBusinessSession(created)) throw new Error("\u65B0\u4F1A\u8BDD\u9519\u8BEF\u8FD4\u56DE\u4E86\u4E1A\u52A1\u4F1A\u8BDD");
1382
+ return created;
1383
+ };
1384
+ const attempt = resolveOrdinary().finally(() => {
1385
+ pending.delete(workspaceId);
1386
+ });
1387
+ pending.set(workspaceId, attempt);
1388
+ return attempt;
1389
+ };
1390
+ navigation.connectWorkspace = guarded;
1391
+ return () => {
1392
+ active = false;
1393
+ if (navigation.connectWorkspace === guarded) navigation.connectWorkspace = original;
1394
+ };
1395
+ }
1396
+
1347
1397
  // src/workbench-state.ts
1348
1398
  var PREVISIT_PHASES = ["target", "scope", "collect", "verify", "output"];
1349
1399
  function hasTool(toolNames, fragments) {
@@ -2292,6 +2342,19 @@ function apply(ctx) {
2292
2342
  if (view !== void 0) locatePrevisitView(shared, sessionId, view);
2293
2343
  };
2294
2344
  ctx.effect(() => installStyles(), "dsh-pre-duediligence: QCC blue UI styles");
2345
+ ctx.effect(
2346
+ () => installOrdinarySessionGuard(ctx, ctx.workspaces ?? {}),
2347
+ "dsh-pre-duediligence: ordinary Session reuse (DSH 0.1.1)"
2348
+ );
2349
+ ctx.inject(["uiWorkspace"], (workspaceCtx) => {
2350
+ const navigation = workspaceCtx.uiWorkspace ?? workspaceCtx.get?.("uiWorkspace");
2351
+ if (navigation !== void 0) {
2352
+ workspaceCtx.effect(
2353
+ () => installOrdinarySessionGuard(workspaceCtx, navigation),
2354
+ "dsh-pre-duediligence: ordinary Session reuse"
2355
+ );
2356
+ }
2357
+ });
2295
2358
  ctx.inject(["betterSidebar"], (sidebarCtx) => {
2296
2359
  sidebarCtx.effect(() => {
2297
2360
  const candidate = sidebarCtx.betterSidebar;
package/lib/index.js CHANGED
@@ -296,7 +296,7 @@ function loadBundledSkill() {
296
296
  },
297
297
  metadata: {
298
298
  author: "QCC",
299
- version: "0.1.13",
299
+ version: "0.1.14",
300
300
  industry: "enterprise-services",
301
301
  mcpServers: ["qcc-company", "qcc-risk", "qcc-ipr", "qcc-operation", "qcc-executive"]
302
302
  }
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-tools.ts", "../src/previsit-session.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 { registerPrevisitTools, type ToolHost } from \"./previsit-tools.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 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.13\",\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 ctx.effect(() => registerPrevisitTools(ctx))\n ctx.skills.register(loadBundledSkill())\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", "import { randomUUID } from \"node:crypto\"\nimport { isPrevisitSession } from \"./previsit-session.js\"\nimport { classifyToolOutcome } from \"./tool-outcome.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\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; limit: number; used: number; search: unknown; personnel?: unknown; risk?: unknown; entity?: { fullName: string; creditCode: string }; busy: boolean }\nconst LIMITS = { fast: 8, standard: 18, deep: 40 } 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}\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 const record = value as Record<string, unknown>\n const candidate = record[dimension] ?? record[tool]\n const count = typeof candidate === \"number\" ? candidate : candidate && typeof candidate === \"object\" ? (candidate as { count?: unknown }).count : undefined\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, human approval, entity binding and bounded ToolRuntime dispatch. */\nexport function registerPrevisitTools(ctx: ToolHost): () => 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 const approval = async (exec: Execution, reason: string) => {\n const service = ctx.get?.(\"approval\") as { request(input: object): Promise<string> } | undefined\n if (service === undefined || await service.request({ agent: exec.agent, toolName: exec.name, callId: exec.callId, reason, signal: exec.signal }) !== \"allowed-once\") throw new Error(\"\u7528\u6237\u672A\u786E\u8BA4\uFF0C\u672C\u6B21\u672A\u6267\u884C\u4ED8\u8D39\u67E5\u8BE2\")\n exec.signal.throwIfAborted()\n }\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\u786E\u8BA4\u3001\u4E3B\u4F53\u548C\u9884\u7B97\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\uFF1A\u5411\u7528\u6237\u786E\u8BA4\u4F01\u4E1A\u68C0\u7D22\u8BCD\u3001\u4ED8\u8D39\u8303\u56F4\u548C\u6700\u591A 8/18/40 \u6B21\u8C03\u7528\u3002\u8FD4\u56DE\u4EFB\u52A1\u6807\u8BC6\uFF1B\u672A\u786E\u8BA4\u4E0D\u5EFA\u7ACB\u4EFB\u52A1\u3002\", {\n query: { type: \"string\" }, depth: { type: \"string\", enum: Object.keys(LIMITS) },\n }, [\"query\", \"depth\"], async (args, exec, agent) => {\n const query = string(args.query)\n const depth = string(args.depth) as keyof typeof LIMITS\n const limit = LIMITS[depth]\n if (!Object.hasOwn(LIMITS, 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 await approval(exec, `\u4E3A\u201C${query}\u201D\u6267\u884C\u8BBF\u524D\u5C3D\u8C03\uFF0C\u6700\u591A ${limit} \u6B21\u4F01\u67E5\u67E5\u8C03\u7528\uFF08\u542B\u5931\u8D25\u91CD\u8BD5\uFF09\uFF0C\u6D88\u8017\u60A8\u81EA\u6709\u8FDE\u63A5\u7684\u989D\u5EA6\u3002\u786E\u8BA4\u8303\u56F4\u4E0E\u989D\u5EA6\u540E\u5F00\u59CB\uFF1B\u591A\u5019\u9009\u9700\u53E6\u884C\u786E\u8BA4\u552F\u4E00\u4E3B\u4F53\u3002`)\n const task: Task = { id: randomUUID(), owner, query, limit, used: 0, search: null, busy: false }\n tasks.set(owner, task)\n return { taskId: task.id, query, limit, used: 0, 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\u7B49\u5F85\u786E\u8BA4\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\u91CD\u65B0\u786E\u8BA4\u9884\u7B97\")\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\", \"\u5C06\u641C\u7D22\u8FD4\u56DE\u7684\u4F01\u4E1A\u5168\u79F0\u53CA\u4FE1\u7528\u4EE3\u7801\u4EA4\u7528\u6237\u786E\u8BA4\uFF0C\u7ED1\u5B9A\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\u3002\u4E0D\u5F97\u9ED8\u8BA4\u9009\u62E9\u7B2C\u4E00\u5019\u9009\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 await approval(exec, `\u8BF7\u786E\u8BA4\u672C\u6B21\u552F\u4E00\u5C3D\u8C03\u4E3B\u4F53\uFF1A${fullName}\uFF08\u7EDF\u4E00\u793E\u4F1A\u4FE1\u7528\u4EE3\u7801\uFF1A${creditCode}\uFF09\u3002\u6709\u591A\u4E2A\u5019\u9009\u65F6\uFF0C\u53EA\u6709\u60A8\u786E\u8BA4\u7684\u8FD9\u5BB6\u4F01\u4E1A\u53EF\u4EE5\u7EE7\u7EED\u67E5\u8BE2\u3002`)\n task.entity = { fullName, creditCode }\n task.personnel = undefined\n task.risk = undefined\n return { taskId: task.id, entity: task.entity, status: \"entity-confirmed\" }\n } finally { task.busy = false }\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\u56FA\u5B9A\u8DEF\u7531\u3001\u9884\u7B97\u5185\u6267\u884C\uFF0C\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) }, personName: { type: \"string\" },\n }, [\"taskId\", \"dimension\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n const dimension = string(args.dimension) as keyof typeof QUERY_ROUTES\n if (!Object.hasOwn(QUERY_ROUTES, dimension)) throw new Error(\"\u4E0D\u652F\u6301\u7684\u4E1A\u52A1\u7EF4\u5EA6\")\n if (task.used >= task.limit) return { taskId: task.id, dimension, outcome: \"not-executed\", reason: \"\u8C03\u7528\u9884\u7B97\u5DF2\u7528\u5B8C\" }\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 === undefined || count === 0) return { taskId: task.id, dimension, outcome: \"not-executed\", reason: count === 0 ? \"\u626B\u63CF\u8FD4\u56DE\u96F6\u8BB0\u5F55\uFF0C\u4E0D\u4E0B\u94BB\" : \"\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 { taskId: task.id, dimension, outcome: \"not-executed\", reason: 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 { taskId: task.id, dimension, outcome: \"not-executed\", reason: \"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 { taskId: task.id, dimension, outcome: \"not-executed\", reason: \"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 permits.set(callId, { owner: task.owner, name: selected.name, parent: exec.token })\n try {\n exec.signal.throwIfAborted()\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 const outcome = result.isError ? classifyToolOutcome({ code: result.error?.info?.code }, true) : classifyToolOutcome(data)\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 return { taskId: task.id, dimension, toolName: selected.name, outcome, used: task.used, limit: task.limit, data: result.isError ? { message: result.error?.message ?? \"\u67E5\u8BE2\u5931\u8D25\" } : data }\n } finally { permits.delete(callId); task.busy = false }\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 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: { cwd: 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 async function createPrevisitSession(ctx: PrevisitSessionHost): Promise<string> {\n const workspace = ctx.workspaces?.list?.getSnapshot()\n const current = ctx.sessions.list?.getSnapshot().current\n const items = workspace?.items ?? []\n const cwd = items.find(item => current !== undefined && item.sessionIds?.includes(current))?.path\n ?? items.find(item => item.workspaceId === workspace?.recentWorkspaceId)?.path\n ?? items[0]?.path\n if (!cwd) 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({ cwd, 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", "export type ToolOutcome = \"running\" | \"done\" | \"no-data\" | \"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\", \"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 ([\"UNKNOWN_TOOL\", \"ABORTED_BEFORE_DISPATCH\", \"NOT_EXECUTED\"].includes(code as string)) return \"not-executed\"\n if (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\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 } {\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)) return { name: value.toolName, status: value.outcome }\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 { registerPrevisitTools, type ToolHost } from \"./previsit-tools.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 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.14\",\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 ctx.effect(() => registerPrevisitTools(ctx))\n ctx.skills.register(loadBundledSkill())\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", "import { randomUUID } from \"node:crypto\"\nimport { isPrevisitSession } from \"./previsit-session.js\"\nimport { classifyToolOutcome } from \"./tool-outcome.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\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; limit: number; used: number; search: unknown; personnel?: unknown; risk?: unknown; entity?: { fullName: string; creditCode: string }; busy: boolean }\nconst LIMITS = { fast: 8, standard: 18, deep: 40 } 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}\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 const record = value as Record<string, unknown>\n const candidate = record[dimension] ?? record[tool]\n const count = typeof candidate === \"number\" ? candidate : candidate && typeof candidate === \"object\" ? (candidate as { count?: unknown }).count : undefined\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, human approval, entity binding and bounded ToolRuntime dispatch. */\nexport function registerPrevisitTools(ctx: ToolHost): () => 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 const approval = async (exec: Execution, reason: string) => {\n const service = ctx.get?.(\"approval\") as { request(input: object): Promise<string> } | undefined\n if (service === undefined || await service.request({ agent: exec.agent, toolName: exec.name, callId: exec.callId, reason, signal: exec.signal }) !== \"allowed-once\") throw new Error(\"\u7528\u6237\u672A\u786E\u8BA4\uFF0C\u672C\u6B21\u672A\u6267\u884C\u4ED8\u8D39\u67E5\u8BE2\")\n exec.signal.throwIfAborted()\n }\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\u786E\u8BA4\u3001\u4E3B\u4F53\u548C\u9884\u7B97\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\uFF1A\u5411\u7528\u6237\u786E\u8BA4\u4F01\u4E1A\u68C0\u7D22\u8BCD\u3001\u4ED8\u8D39\u8303\u56F4\u548C\u6700\u591A 8/18/40 \u6B21\u8C03\u7528\u3002\u8FD4\u56DE\u4EFB\u52A1\u6807\u8BC6\uFF1B\u672A\u786E\u8BA4\u4E0D\u5EFA\u7ACB\u4EFB\u52A1\u3002\", {\n query: { type: \"string\" }, depth: { type: \"string\", enum: Object.keys(LIMITS) },\n }, [\"query\", \"depth\"], async (args, exec, agent) => {\n const query = string(args.query)\n const depth = string(args.depth) as keyof typeof LIMITS\n const limit = LIMITS[depth]\n if (!Object.hasOwn(LIMITS, 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 await approval(exec, `\u4E3A\u201C${query}\u201D\u6267\u884C\u8BBF\u524D\u5C3D\u8C03\uFF0C\u6700\u591A ${limit} \u6B21\u4F01\u67E5\u67E5\u8C03\u7528\uFF08\u542B\u5931\u8D25\u91CD\u8BD5\uFF09\uFF0C\u6D88\u8017\u60A8\u81EA\u6709\u8FDE\u63A5\u7684\u989D\u5EA6\u3002\u786E\u8BA4\u8303\u56F4\u4E0E\u989D\u5EA6\u540E\u5F00\u59CB\uFF1B\u591A\u5019\u9009\u9700\u53E6\u884C\u786E\u8BA4\u552F\u4E00\u4E3B\u4F53\u3002`)\n const task: Task = { id: randomUUID(), owner, query, limit, used: 0, search: null, busy: false }\n tasks.set(owner, task)\n return { taskId: task.id, query, limit, used: 0, 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\u7B49\u5F85\u786E\u8BA4\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\u91CD\u65B0\u786E\u8BA4\u9884\u7B97\")\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\", \"\u5C06\u641C\u7D22\u8FD4\u56DE\u7684\u4F01\u4E1A\u5168\u79F0\u53CA\u4FE1\u7528\u4EE3\u7801\u4EA4\u7528\u6237\u786E\u8BA4\uFF0C\u7ED1\u5B9A\u552F\u4E00\u6CD5\u5F8B\u5B9E\u4F53\u3002\u4E0D\u5F97\u9ED8\u8BA4\u9009\u62E9\u7B2C\u4E00\u5019\u9009\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 await approval(exec, `\u8BF7\u786E\u8BA4\u672C\u6B21\u552F\u4E00\u5C3D\u8C03\u4E3B\u4F53\uFF1A${fullName}\uFF08\u7EDF\u4E00\u793E\u4F1A\u4FE1\u7528\u4EE3\u7801\uFF1A${creditCode}\uFF09\u3002\u6709\u591A\u4E2A\u5019\u9009\u65F6\uFF0C\u53EA\u6709\u60A8\u786E\u8BA4\u7684\u8FD9\u5BB6\u4F01\u4E1A\u53EF\u4EE5\u7EE7\u7EED\u67E5\u8BE2\u3002`)\n task.entity = { fullName, creditCode }\n task.personnel = undefined\n task.risk = undefined\n return { taskId: task.id, entity: task.entity, status: \"entity-confirmed\" }\n } finally { task.busy = false }\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\u56FA\u5B9A\u8DEF\u7531\u3001\u9884\u7B97\u5185\u6267\u884C\uFF0C\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) }, personName: { type: \"string\" },\n }, [\"taskId\", \"dimension\"], async (args, exec, agent) => {\n const task = requireTask(args, agent)\n const dimension = string(args.dimension) as keyof typeof QUERY_ROUTES\n if (!Object.hasOwn(QUERY_ROUTES, dimension)) throw new Error(\"\u4E0D\u652F\u6301\u7684\u4E1A\u52A1\u7EF4\u5EA6\")\n if (task.used >= task.limit) return { taskId: task.id, dimension, outcome: \"not-executed\", reason: \"\u8C03\u7528\u9884\u7B97\u5DF2\u7528\u5B8C\" }\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 === undefined || count === 0) return { taskId: task.id, dimension, outcome: \"not-executed\", reason: count === 0 ? \"\u626B\u63CF\u8FD4\u56DE\u96F6\u8BB0\u5F55\uFF0C\u4E0D\u4E0B\u94BB\" : \"\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 { taskId: task.id, dimension, outcome: \"not-executed\", reason: 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 { taskId: task.id, dimension, outcome: \"not-executed\", reason: \"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 { taskId: task.id, dimension, outcome: \"not-executed\", reason: \"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 permits.set(callId, { owner: task.owner, name: selected.name, parent: exec.token })\n try {\n exec.signal.throwIfAborted()\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 const outcome = result.isError ? classifyToolOutcome({ code: result.error?.info?.code }, true) : classifyToolOutcome(data)\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 return { taskId: task.id, dimension, toolName: selected.name, outcome, used: task.used, limit: task.limit, data: result.isError ? { message: result.error?.message ?? \"\u67E5\u8BE2\u5931\u8D25\" } : data }\n } finally { permits.delete(callId); task.busy = false }\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 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", "export type ToolOutcome = \"running\" | \"done\" | \"no-data\" | \"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\", \"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 ([\"UNKNOWN_TOOL\", \"ABORTED_BEFORE_DISPATCH\", \"NOT_EXECUTED\"].includes(code as string)) return \"not-executed\"\n if (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\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 } {\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)) return { name: value.toolName, status: value.outcome }\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": []
7
7
  }
@@ -8,6 +8,15 @@ type WorkspaceSnapshot = {
8
8
  sessionIds?: string[];
9
9
  }>;
10
10
  recentWorkspaceId?: string;
11
+ archivedSessionIds?: string[];
12
+ };
13
+ type SessionListSnapshot = {
14
+ current?: string;
15
+ ids?: string[];
16
+ byId?: Record<string, {
17
+ blank?: boolean;
18
+ cwd?: string;
19
+ } | undefined>;
11
20
  };
12
21
  type SlotsService = {
13
22
  inject(name: string, setup: () => void | (() => void)): unknown;
@@ -21,11 +30,9 @@ type SlotsService = {
21
30
  export type LeftSidebarHost = {
22
31
  slots: SlotsService;
23
32
  sessions: {
24
- list?: SnapshotStore<{
25
- current?: string;
26
- }>;
33
+ list?: SnapshotStore<SessionListSnapshot>;
27
34
  create?(options: {
28
- cwd: string;
35
+ workspaceId: string;
29
36
  sessionId: string;
30
37
  }): Promise<string>;
31
38
  open?(sessionId: string): void;
@@ -0,0 +1,41 @@
1
+ type SessionSummary = {
2
+ blank?: boolean;
3
+ cwd?: string;
4
+ };
5
+ type GuardHost = {
6
+ sessions: {
7
+ list?: {
8
+ getSnapshot(): {
9
+ current?: string;
10
+ ids?: string[];
11
+ byId?: Record<string, SessionSummary | undefined>;
12
+ };
13
+ };
14
+ create?: unknown;
15
+ };
16
+ workspaces?: {
17
+ list?: {
18
+ getSnapshot(): {
19
+ items?: Array<{
20
+ workspaceId: string;
21
+ path?: string;
22
+ sessionIds?: string[];
23
+ }>;
24
+ archivedSessionIds?: string[];
25
+ };
26
+ };
27
+ };
28
+ };
29
+ export type WorkspaceNavigation = {
30
+ connectWorkspace?(workspaceId: string): Promise<string>;
31
+ };
32
+ export declare function isBusinessSession(sessionId: string): boolean;
33
+ /**
34
+ * DSH currently chooses a reusable blank Session before returning from
35
+ * uiWorkspace.connectWorkspace(). Business entry Sessions must stay attached
36
+ * to their Workspace for native composer ownership, but must never become the
37
+ * ordinary "New Session" result. This wrapper filters only that returned
38
+ * selection and otherwise delegates to public Host capabilities.
39
+ */
40
+ export declare function installOrdinarySessionGuard(host: GuardHost, navigation: WorkspaceNavigation): () => void;
41
+ export {};
@@ -8,7 +8,7 @@ export type PrevisitSessionHost = {
8
8
  };
9
9
  };
10
10
  create?(options: {
11
- cwd: string;
11
+ workspaceId: string;
12
12
  sessionId: string;
13
13
  }): Promise<string>;
14
14
  open?(sessionId: string): void;
@@ -26,4 +26,5 @@ export type PrevisitSessionHost = {
26
26
  };
27
27
  };
28
28
  };
29
+ export declare function resolvePrevisitWorkspaceId(ctx: PrevisitSessionHost): string | undefined;
29
30
  export declare function createPrevisitSession(ctx: PrevisitSessionHost): Promise<string>;
@@ -1,5 +1,6 @@
1
1
  import { type BetterSidebarService } from "./better-sidebar.js";
2
2
  import { type LeftSidebarHost } from "./left-sidebar.js";
3
+ import { type WorkspaceNavigation } from "./ordinary-session-guard.js";
3
4
  export declare const inject: readonly ["slots", "sessions", "workspaces", "conversation"];
4
5
  export declare const OPTIONAL_WORKBENCH_MESSAGE = "\u672A\u5B89\u88C5\u53EF\u9009 Better Sidebar\uFF1B\u5F53\u524D\u8349\u7A3F\u548C\u4E1A\u52A1\u72B6\u6001\u5DF2\u4FDD\u7559\u3002\u57FA\u7840\u4F1A\u8BDD\u3001\u63D0\u793A\u8BCD\u751F\u6210\u3001\u539F\u751F\u53D1\u9001\u53CA\u4F1A\u8BDD\u62A5\u544A\u9605\u8BFB\u4ECD\u53EF\u4F7F\u7528\u3002\u5982\u9700\u53EF\u89C6\u5316\u5DE5\u4F5C\u53F0\uFF0C\u8BF7\u505C\u6B62 DSH \u540E\u6309 README \u8BBE\u7F6E DSH_PREVISIT_WORKBENCH=on \u5E76\u91CD\u8DD1\u5B89\u88C5\u811A\u672C\u3002";
5
6
  type SnapshotStore<T> = {
@@ -51,6 +52,7 @@ type ClientContext = LeftSidebarHost & {
51
52
  get(name: string): unknown;
52
53
  } | undefined;
53
54
  };
55
+ uiWorkspace?: WorkspaceNavigation;
54
56
  betterSidebar?: BetterSidebarService;
55
57
  inject(deps: string[], setup: (ctx: ClientContext) => void): unknown;
56
58
  effect(setup: () => void | (() => void), label?: string): unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-pre-duediligence",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Pre-visit due diligence and company research in DeepSeek Harness for customer background checks and risk briefs, with Qichacha MCP tool-call budget approval and entity confirmation.",
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.13
8
+ version: 0.1.14
9
9
  ---
10
10
 
11
11
  # 访前尽调