Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05b813a184 | |||
| d9e101b287 | |||
| 5d0beb5733 | |||
| 715b52cdc2 | |||
| 41f000b364 | |||
| d0d5c9ac56 | |||
| f42b8e7755 | |||
| e00ba3fc4d | |||
| 976bde37cc | |||
| 820fb3737f | |||
| 7006feff73 | |||
| 188bfc09c5 | |||
| 375e105fce | |||
| e3a27256ba | |||
| c30e9e769b | |||
| 1677386c60 | |||
| ac38979d5c | |||
| 5ae1816414 | |||
| 800932249b | |||
| 9e0db27326 |
@@ -19,8 +19,10 @@ import {
|
|||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import {
|
import {
|
||||||
WECOM_PLUGIN_API_KEY_HEADER,
|
WECOM_PLUGIN_API_KEY_HEADER,
|
||||||
|
WECOM_PLUGIN_MCP_TRANSPORT,
|
||||||
WECOM_PLUGIN_PERMISSION_GROUPS,
|
WECOM_PLUGIN_PERMISSION_GROUPS,
|
||||||
WECOM_PLUGIN_PERMISSION_LABELS,
|
WECOM_PLUGIN_PERMISSION_LABELS,
|
||||||
|
resolveWecomPluginMcpUrl,
|
||||||
resolveWecomPluginPublicUrl,
|
resolveWecomPluginPublicUrl,
|
||||||
type WecomApiPluginDto,
|
type WecomApiPluginDto,
|
||||||
type WecomApiPluginSecretDto,
|
type WecomApiPluginSecretDto,
|
||||||
@@ -53,6 +55,7 @@ export default function WecomApiPluginsPage() {
|
|||||||
const [revealed, setRevealed] = useState<WecomApiPluginSecretDto | null>(null);
|
const [revealed, setRevealed] = useState<WecomApiPluginSecretDto | null>(null);
|
||||||
|
|
||||||
const pluginUrl = resolveWecomPluginPublicUrl(window.location.hostname);
|
const pluginUrl = resolveWecomPluginPublicUrl(window.location.hostname);
|
||||||
|
const mcpUrl = resolveWecomPluginMcpUrl(window.location.hostname);
|
||||||
const permissionsWatch = Form.useWatch('permissions', form) as WecomPluginPermission[] | undefined;
|
const permissionsWatch = Form.useWatch('permissions', form) as WecomPluginPermission[] | undefined;
|
||||||
|
|
||||||
const { data, loading, page, pageSize, setPage, setPageSize, reload } =
|
const { data, loading, page, pageSize, setPage, setPageSize, reload } =
|
||||||
@@ -224,7 +227,7 @@ export default function WecomApiPluginsPage() {
|
|||||||
{settingsModal}
|
{settingsModal}
|
||||||
<AdminListHeader
|
<AdminListHeader
|
||||||
settings={settingsButton}
|
settings={settingsButton}
|
||||||
description="每个实例对应企微里一只 API 插件:共用 Base URL,用不同 X-Api-Key 区分,并按勾选权限放行工具。"
|
description="每个实例对应企微里一只插件:共用 URL,用不同 X-Api-Key 区分,并按勾选权限放行工具。优先配 MCP 插件;API 插件(OpenAPI)仍可用。"
|
||||||
actions={
|
actions={
|
||||||
<Button type="primary" onClick={openCreate}>
|
<Button type="primary" onClick={openCreate}>
|
||||||
新建插件
|
新建插件
|
||||||
@@ -236,20 +239,24 @@ export default function WecomApiPluginsPage() {
|
|||||||
type="info"
|
type="info"
|
||||||
showIcon
|
showIcon
|
||||||
style={{ marginBottom: 16 }}
|
style={{ marginBottom: 16 }}
|
||||||
message="填企微「添加 API 插件」第 1 步"
|
message="填企微「添加插件」"
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
插件 URL:
|
MCP 插件 URL(推荐,{WECOM_PLUGIN_MCP_TRANSPORT}):
|
||||||
|
<Typography.Text copyable>{mcpUrl}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
API 插件 URL(兼容 OpenAPI):
|
||||||
<Typography.Text copyable>{pluginUrl}</Typography.Text>
|
<Typography.Text copyable>{pluginUrl}</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
OpenAPI(带该实例 Key):
|
OpenAPI(API 插件第 2 步,带该实例 Key):
|
||||||
<Typography.Text copyable>{`${pluginUrl}/openapi.json`}</Typography.Text>
|
<Typography.Text copyable>{`${pluginUrl}/openapi.json`}</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<Typography.Text type="secondary">
|
<Typography.Text type="secondary">
|
||||||
授权方式 Service token / API key,Header 名 {WECOM_PLUGIN_API_KEY_HEADER}
|
授权方式 Service token / API key,Header 名 {WECOM_PLUGIN_API_KEY_HEADER}
|
||||||
;第 2 步只添加本实例已勾选的工具(GET + Query)。
|
。MCP 会按本实例权限自动拉取工具;API 插件第 2 步只添加已勾选的 GET 工具。
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
+129
-27
@@ -1,28 +1,75 @@
|
|||||||
# 企微 API 插件 · 配置手册
|
# 企微 API / MCP 插件 · 配置手册
|
||||||
|
|
||||||
> 面向运营/技术:在企微后台「智能机器人 → 添加 API 插件」逐步填表。
|
> 面向运营/技术:在企微后台「智能机器人 → 添加插件」填表。
|
||||||
> 后端前缀:`/api/v1/wecom/plugin` · 鉴权 Header `X-Api-Key` · 与 HQ 长连接 Bot **独立**。
|
> 后端前缀:`/api/v1/wecom/plugin` · MCP:`/api/v1/wecom/plugin/mcp` · 鉴权 Header `X-Api-Key` · 与 HQ 长连接 Bot **独立**。
|
||||||
> 密钥与权限在 HQ **企微机器人 → API 插件** 维护(v3.5.17+)。
|
> 密钥与权限在 HQ **企微机器人 → API 插件** 维护(v3.5.17+;MCP 端点 v3.5.18)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 推荐流程(优先 OpenAPI 导入)
|
## 1. 推荐流程(MCP 插件)
|
||||||
|
|
||||||
**不要 17 个工具全手填。** 推荐:
|
企微 5.0.6+ 支持 **MCP 插件**:自动 `tools/list` 拉工具,不必手填、也不必导入 OpenAPI。REST API 插件仍保留,已配好的机器人不用改。
|
||||||
|
|
||||||
1. HQ → **企微机器人 → API 插件** → 新建实例,勾选工具权限,复制 **Key**(只显示一次)。
|
1. HQ → **企微机器人 → API 插件** → 新建实例,勾选工具权限,复制 **Key**(只显示一次)。
|
||||||
2. 企微 **第 1 步**:填插件 URL + Header Key(见 §2)。
|
2. 企微工作台 → 智能机器人 → **插件** → **添加 MCP 插件**:
|
||||||
|
- **插件 URL**:见下表(必须是企业域名,localhost 无法注入成员身份)
|
||||||
|
- **传输协议**:Streamable HTTP
|
||||||
|
- **授权方式**:Service token / API key
|
||||||
|
- **位置**:Header
|
||||||
|
- **Parameter name**:`X-Api-Key`
|
||||||
|
- **Service token**:该实例 Key
|
||||||
|
3. 企微会自动拉取已授权工具;把插件挂到「运营查询」类智能机器人即可。
|
||||||
|
4. 白名单会话试问:「查一下今日经营指标」「订单 DK…」。HQ **日志 → 智能机器人日志** 应出现 `botKey=plugin:{id}`。
|
||||||
|
|
||||||
|
| 企微表单字段 | 测试环境 | 生产环境 |
|
||||||
|
|-------------|---------|---------|
|
||||||
|
| **MCP 插件 URL** | `https://api-test.dukanghaoke.com/api/v1/wecom/plugin/mcp` | `https://api.dukanghaoke.com/api/v1/wecom/plugin/mcp` |
|
||||||
|
| **传输协议** | Streamable HTTP | 同左 |
|
||||||
|
| **Header** | `X-Api-Key` | 同左 |
|
||||||
|
|
||||||
|
**自检(JSON-RPC,无 `{code,message,data}` 信封):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 无 Key → 401
|
||||||
|
curl -sS -o /dev/null -w '%{http_code}\n' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
|
||||||
|
"https://api-test.dukanghaoke.com/api/v1/wecom/plugin/mcp"
|
||||||
|
|
||||||
|
# 带 Key:initialize
|
||||||
|
curl -sS -H "X-Api-Key: <你的Key>" -H 'Content-Type: application/json' \
|
||||||
|
-H 'Accept: application/json, text/event-stream' \
|
||||||
|
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
|
||||||
|
"https://api-test.dukanghaoke.com/api/v1/wecom/plugin/mcp"
|
||||||
|
|
||||||
|
# 带 Key:tools/list(仅含本实例已勾选权限)
|
||||||
|
curl -sS -H "X-Api-Key: <你的Key>" -H 'Content-Type: application/json' \
|
||||||
|
-H 'Accept: application/json, text/event-stream' \
|
||||||
|
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
||||||
|
"https://api-test.dukanghaoke.com/api/v1/wecom/plugin/mcp"
|
||||||
|
```
|
||||||
|
|
||||||
|
若「添加插件」拉不到工具,再试传输协议 **SSE**(需运维给 `/api/v1/wecom/plugin/mcp` 关 `proxy_buffering`);第一期默认 Streamable HTTP 无状态即可。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 兼容流程(API 插件 + OpenAPI 导入)
|
||||||
|
|
||||||
|
**不要 17 个工具全手填。** API 插件推荐:
|
||||||
|
|
||||||
|
1. HQ 建实例并复制 Key(同上)。
|
||||||
|
2. 企微 **第 1 步**:填 API 插件 URL + Header Key(见 §3)。
|
||||||
3. 企微 **第 2 步**:**OpenAPI 导入**
|
3. 企微 **第 2 步**:**OpenAPI 导入**
|
||||||
- URL:`{Base URL}/openapi.json`
|
- URL:`{Base URL}/openapi.json`
|
||||||
- **同样带 Header `X-Api-Key`**(与第 1 步相同 Key)
|
- **同样带 Header `X-Api-Key`**(与第 1 步相同 Key)
|
||||||
- 服务端按该 Key 的权限**只返回已授权路径**,与 HQ 勾选一致。
|
- 服务端按该 Key 的权限**只返回已授权路径**,与 HQ 勾选一致。
|
||||||
4. 导入后逐条核对 §3「全局规则」;类型不对时按 §5 手工修正。
|
4. 导入后逐条核对 §4「全局规则」;类型不对时按 §6 手工修正。
|
||||||
|
|
||||||
手填仅适用于:导入失败,或只加 2~3 个高频工具。
|
手填仅适用于:导入失败,或只加 2~3 个高频工具。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 第 1 步:添加 API 插件
|
## 3. 第 1 步:添加 API 插件
|
||||||
|
|
||||||
| 企微表单字段 | 测试环境 | 生产环境 |
|
| 企微表单字段 | 测试环境 | 生产环境 |
|
||||||
|-------------|---------|---------|
|
|-------------|---------|---------|
|
||||||
@@ -43,7 +90,7 @@ HQ 页也会展示 Base URL 与 Header 名(不含密钥)。
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 全局规则(调试不过先看这里)
|
## 4. 全局规则(调试不过先看这里)
|
||||||
|
|
||||||
| 项 | 正确 | 错误 |
|
| 项 | 正确 | 错误 |
|
||||||
|----|------|------|
|
|----|------|------|
|
||||||
@@ -69,20 +116,21 @@ HQ 页也会展示 Base URL 与 Header 名(不含密钥)。
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. HQ 权限与工具对照
|
## 5. HQ 权限与工具对照
|
||||||
|
|
||||||
只添加 HQ **已勾选**权限对应的工具;未授权返回 **403**。
|
只添加 HQ **已勾选**权限对应的工具;未授权 REST 返回 **403**,MCP 则不出现在 `tools/list`。
|
||||||
|
|
||||||
| HQ 权限 | 路径 | 建议工具名 |
|
| HQ 权限 | REST 路径 | MCP 工具名 |
|
||||||
|---------|------|-----------|
|
|---------|-----------|-----------|
|
||||||
| `metrics.read` | `/metrics` | 查询经营指标 |
|
| `metrics.read` | `/metrics` | `query_metrics` |
|
||||||
| `order.read` | `/orders` | 查询订单 |
|
| `order.read` | `/orders` | `query_orders` |
|
||||||
| `user.read` | `/users` | 查询用户 |
|
| `user.read` | `/users` | `query_users` |
|
||||||
| `store.read` | `/stores` | 查询门店 |
|
| `store.read` | `/stores` | `query_stores` |
|
||||||
| `redeem.read` | `/redeems` | 查询核销 |
|
| `redeem.read` | `/redeems` | `query_redeems` |
|
||||||
| `promo.read` | `/promo-codes`、`/promo-codes/{code}/stats` | 查询推广码、推广码统计 |
|
| `promo.read` | `/promo-codes`、`/promo-codes/{code}/stats` | `query_promo_codes`、`query_promo_code_stats` |
|
||||||
| `store.audit.read` | `/store-audits`、`/store-info-audits`、`/store-info-audits/{id}`、`/store-package-audits`、`/store-package-audits/{id}` | 门店/信息/套餐审核 |
|
| `store.audit.read` | `/store-audits`、`/store-info-audits`、`/store-info-audits/{id}`、`/store-package-audits`、`/store-package-audits/{id}` | `query_store_audits` 等 5 个 |
|
||||||
| `partner.read` | `/partners`、`/partners/{partnerId}/users|stores|orders` | 合伙人及关联数据 |
|
| `partner.read` | `/partners`、`/partners/{partnerId}/users|stores|orders` | `query_partners` 等 4 个 |
|
||||||
|
| `dev_plan.read` | `/versions`、`/tasks` | `query_versions`、`query_tasks` |
|
||||||
|
|
||||||
**实例建议:**
|
**实例建议:**
|
||||||
|
|
||||||
@@ -91,10 +139,11 @@ HQ 页也会展示 Base URL 与 Header 名(不含密钥)。
|
|||||||
| 日常运营 | metrics + order + user + store + promo + redeem |
|
| 日常运营 | metrics + order + user + store + promo + redeem |
|
||||||
| 审核值班 | 上表 + store.audit.read |
|
| 审核值班 | 上表 + store.audit.read |
|
||||||
| 合伙人分析 | 上表 + partner.read |
|
| 合伙人分析 | 上表 + partner.read |
|
||||||
|
| 开发计划 | `dev_plan.read`(已有实例需在 HQ 补勾才会出现这两个工具) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. 第 2 步:工具参数明细
|
## 6. 第 2 步:工具参数明细
|
||||||
|
|
||||||
以下均为 **GET**。Base URL 与 §2 相同,路径为相对路径。
|
以下均为 **GET**。Base URL 与 §2 相同,路径为相对路径。
|
||||||
|
|
||||||
@@ -332,7 +381,50 @@ HQ 页也会展示 Base URL 与 Header 名(不含密钥)。
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. OpenAPI 导入
|
### 5.16 查询开发版本
|
||||||
|
|
||||||
|
| 项 | 值 |
|
||||||
|
|----|-----|
|
||||||
|
| 权限 | `dev_plan.read` |
|
||||||
|
| 路径 | `/versions` |
|
||||||
|
| MCP | `query_versions` |
|
||||||
|
|
||||||
|
**输入(均可选):**
|
||||||
|
|
||||||
|
| 参数 | 位置 | 类型 | 模型可见 | 说明 |
|
||||||
|
|------|------|------|----------|------|
|
||||||
|
| q | Query | String | 是 | 版本号或说明,如 `v3.5.18` |
|
||||||
|
| status | Query | String | 是 | `PENDING` 待启动 / `IN_PROGRESS` 开发中 / `TESTING` 测试 / `RELEASED` 已上线 / `STOPPED` 已停止 |
|
||||||
|
| page / pageSize | Query | Integer | 否 | 分页 |
|
||||||
|
|
||||||
|
**`data.items[]`:** versionNo、content、status、statusLabel、createdAt、releasedAt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.17 查询开发任务
|
||||||
|
|
||||||
|
| 项 | 值 |
|
||||||
|
|----|-----|
|
||||||
|
| 权限 | `dev_plan.read` |
|
||||||
|
| 路径 | `/tasks` |
|
||||||
|
| MCP | `query_tasks` |
|
||||||
|
|
||||||
|
按**任务状态**筛选。可同时用 `q`、`versionNo`。
|
||||||
|
|
||||||
|
**输入(均可选):**
|
||||||
|
|
||||||
|
| 参数 | 位置 | 类型 | 模型可见 | 说明 |
|
||||||
|
|------|------|------|----------|------|
|
||||||
|
| status | Query | String | **是** | `TODO` 待开发 / `IN_PROGRESS` 开发中 / `DEVELOPED` 已开发 / `RELEASED` 已上线 / `STOPPED` 已停止 |
|
||||||
|
| q | Query | String | 是 | 任务编号或内容 |
|
||||||
|
| versionNo | Query | String | 是 | 精确版本号,如 `v3.5.18`;传入则只返回该版本下任务 |
|
||||||
|
| page / pageSize | Query | Integer | 否 | 分页 |
|
||||||
|
|
||||||
|
**`data.items[]`:** taskNo、content、type、typeLabel、status、statusLabel、creatorName、supportTicketNo、versions[]、createdAt、completedAt。不含附件 URL。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. OpenAPI 导入
|
||||||
|
|
||||||
| 项 | 值 |
|
| 项 | 值 |
|
||||||
|----|-----|
|
|----|-----|
|
||||||
@@ -350,7 +442,16 @@ curl -H "X-Api-Key: <Key>" \
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. 调试检查清单
|
## 8. 调试检查清单
|
||||||
|
|
||||||
|
### MCP 插件
|
||||||
|
|
||||||
|
- [ ] 无 Key / 错 Key → 401
|
||||||
|
- [ ] `initialize` + `tools/list` 仅含 HQ 已勾选权限对应工具
|
||||||
|
- [ ] 企微后台能自动拉到工具(Streamable HTTP)
|
||||||
|
- [ ] 白名单会话能问到真实数据;HQ 日志 `botKey=plugin:{id}`
|
||||||
|
|
||||||
|
### API 插件(兼容)
|
||||||
|
|
||||||
- [ ] 第 1 步 Key 与 HQ 实例一致,实例 **已启用**
|
- [ ] 第 1 步 Key 与 HQ 实例一致,实例 **已启用**
|
||||||
- [ ] curl 带 Key 返回 `code: 0`
|
- [ ] curl 带 Key 返回 `code: 0`
|
||||||
@@ -363,7 +464,7 @@ curl -H "X-Api-Key: <Key>" \
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. 多实例与分工
|
## 9. 多实例与分工
|
||||||
|
|
||||||
| 企微插件 | HQ 实例 | 说明 |
|
| 企微插件 | HQ 实例 | 说明 |
|
||||||
|----------|---------|------|
|
|----------|---------|------|
|
||||||
@@ -373,8 +474,9 @@ curl -H "X-Api-Key: <Key>" \
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. 相关文档
|
## 10. 相关文档
|
||||||
|
|
||||||
|
- [杜康好客-v3.5.18-开发文档](./杜康好客-v3.5.18-开发文档.md) — MCP Streamable HTTP 端点
|
||||||
- [杜康好客-v3.5.17-开发文档](./杜康好客-v3.5.17-开发文档.md) — 表结构、Admin API、权限目录
|
- [杜康好客-v3.5.17-开发文档](./杜康好客-v3.5.17-开发文档.md) — 表结构、Admin API、权限目录
|
||||||
- [杜康好客-v3.5.16-开发文档](./杜康好客-v3.5.16-开发文档.md) — 插件初版与 curl 示例
|
- [杜康好客-v3.5.16-开发文档](./杜康好客-v3.5.16-开发文档.md) — 插件初版与 curl 示例
|
||||||
- [杜康好客-v3编码手册](./杜康好客-v3编码手册.md) — 编码验收一句
|
- [杜康好客-v3编码手册](./杜康好客-v3编码手册.md) — 编码验收一句
|
||||||
|
|||||||
@@ -70,9 +70,12 @@
|
|||||||
| 3.5.14 | [`提交订单/支付成功日志端回填 USER_MINI`](./杜康好客-v3.5.14-开发文档.md) | 🔶 开发完成 |
|
| 3.5.14 | [`提交订单/支付成功日志端回填 USER_MINI`](./杜康好客-v3.5.14-开发文档.md) | 🔶 开发完成 |
|
||||||
| 3.5.16 | [`企微智能机器人 API 插件`](./杜康好客-v3.5.16-开发文档.md) | 🔶 开发完成 |
|
| 3.5.16 | [`企微智能机器人 API 插件`](./杜康好客-v3.5.16-开发文档.md) | 🔶 开发完成 |
|
||||||
| 3.5.17 | [`企微 API 插件迁入 HQ 后台`](./杜康好客-v3.5.17-开发文档.md) | 🔶 开发完成 |
|
| 3.5.17 | [`企微 API 插件迁入 HQ 后台`](./杜康好客-v3.5.17-开发文档.md) | 🔶 开发完成 |
|
||||||
|
| 3.5.18 | [`企微 API 插件 MCP 端点`](./杜康好客-v3.5.18-开发文档.md) | 🔶 开发完成 |
|
||||||
|
|
||||||
| 日期 | 说明 |
|
| 日期 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
|
| 2026-09-08 | 企微 MCP/REST 插件增加 `dev_plan.read`:`query_versions` / `query_tasks`(按任务状态筛选) |
|
||||||
|
| 2026-09-08 | v3.5.18:企微插件增加 Streamable HTTP MCP 端点 `/api/v1/wecom/plugin/mcp`,与 REST/OpenAPI 并存 |
|
||||||
| 2026-09-08 | HQ「门店账户」主账号详情可 CRUD 店员子账号(`/admin/store-accounts/:id/staff`) |
|
| 2026-09-08 | HQ「门店账户」主账号详情可 CRUD 店员子账号(`/admin/store-accounts/:id/staff`) |
|
||||||
| 2026-09-06 | v3.5.17:企微 API 插件迁入 HQ「企微机器人 → API 插件」;多实例 Key + 工具权限;不再依赖 `WECOM_PLUGIN_*` env |
|
| 2026-09-06 | v3.5.17:企微 API 插件迁入 HQ「企微机器人 → API 插件」;多实例 Key + 工具权限;不再依赖 `WECOM_PLUGIN_*` env |
|
||||||
| 2026-09-03 | v3.5.16:企微 API 插件只读数据面 `GET /api/v1/wecom/plugin/*`(X-Api-Key);与长连接 Bot 独立 |
|
| 2026-09-03 | v3.5.16:企微 API 插件只读数据面 `GET /api/v1/wecom/plugin/*`(X-Api-Key);与长连接 Bot 独立 |
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
| `store.read` | `/stores`(名称搜索 + 评分/核销/累计核销/合伙人) |
|
| `store.read` | `/stores`(名称搜索 + 评分/核销/累计核销/合伙人) |
|
||||||
| `store.audit.read` | `/store-audits`、`/store-info-audits`、`/store-package-audits` 及 `/{id}` 对比详情 |
|
| `store.audit.read` | `/store-audits`、`/store-info-audits`、`/store-package-audits` 及 `/{id}` 对比详情 |
|
||||||
| `partner.read` | `/partners`、`/partners/{id}/users|stores|orders`(可选 `from`/`to` 时间筛选) |
|
| `partner.read` | `/partners`、`/partners/{id}/users|stores|orders`(可选 `from`/`to` 时间筛选) |
|
||||||
|
| `dev_plan.read` | `/versions`、`/tasks`(MCP:`query_versions`、`query_tasks`,任务可按 status 筛选) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@
|
|||||||
|
|
||||||
## 6. 企微侧怎么配
|
## 6. 企微侧怎么配
|
||||||
|
|
||||||
详见 **[企微 API 插件 · 配置手册](./企微API插件-配置手册.md)**(第 1 步插件、第 2 步工具/OpenAPI 导入、全接口参数表)。
|
详见 **[企微 API 插件 · 配置手册](./企微API插件-配置手册.md)**。v3.5.18 起优先配 MCP 插件;本节 API + OpenAPI 仍可用。
|
||||||
|
|
||||||
1. 插件 URL = HQ 页展示的 Base URL(全实例相同)
|
1. 插件 URL = HQ 页展示的 Base URL(全实例相同)
|
||||||
2. Header `X-Api-Key` = 该实例密钥
|
2. Header `X-Api-Key` = 该实例密钥
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# 杜康好客 · v3.5.18 开发文档
|
||||||
|
|
||||||
|
> **2026-09-08** · integrations/wecom · admin-web · shared-types
|
||||||
|
> **主题**:企微智能机器人 **MCP 插件**(Streamable HTTP),与 REST API 插件并存
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 版本目标
|
||||||
|
|
||||||
|
企微后台「添加 MCP 插件」可对接本系统:托管大模型按 MCP `tools/list` / `tools/call` 拉数。不改长连接 Bot、不删 REST/OpenAPI。
|
||||||
|
|
||||||
|
**不做**:写操作、明文手机/地址、把 `/admin/*` JWT 暴露给企微、第一期 SSE 传输。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 端点
|
||||||
|
|
||||||
|
| 项 | 值 |
|
||||||
|
|----|-----|
|
||||||
|
| MCP URL | `/api/v1/wecom/plugin/mcp` |
|
||||||
|
| 传输 | Streamable HTTP,无状态(每请求新建 `McpServer`) |
|
||||||
|
| 鉴权 | Header `X-Api-Key`(与 REST 同一套 HQ 实例) |
|
||||||
|
| 响应 | JSON-RPC;**无** `{code,message,data}` 信封 |
|
||||||
|
| 工具结果 | `JSON.stringify(查询结果)` |
|
||||||
|
| 权限 | `tools/list` 只注册已勾选权限;QueryService 仍 `requirePerm` |
|
||||||
|
| 开发计划 | `dev_plan.read` → `query_versions`、`query_tasks`(任务按 TODO/IN_PROGRESS/DEVELOPED/RELEASED/STOPPED 筛选) |
|
||||||
|
|
||||||
|
生产:`https://api.dukanghaoke.com/api/v1/wecom/plugin/mcp`
|
||||||
|
测试:`https://api-test.dukanghaoke.com/api/v1/wecom/plugin/mcp`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 变更面
|
||||||
|
|
||||||
|
| 层 | 路径 |
|
||||||
|
|----|------|
|
||||||
|
| shared-types | `WECOM_PLUGIN_MCP_*`、`allowedWecomPluginMcpTools` |
|
||||||
|
| API | `wecom-plugin-mcp.*`;`WecomPluginMcpFactory`;Interceptor skip MCP |
|
||||||
|
| HQ | `WecomApiPluginsPage` 展示 MCP URL |
|
||||||
|
| 文档 | [`企微API插件-配置手册.md`](./企微API插件-配置手册.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 验收
|
||||||
|
|
||||||
|
- [ ] 无 Key / 错 Key → MCP 入口 401
|
||||||
|
- [ ] 带 Key `tools/list` 仅含该实例已勾选权限
|
||||||
|
- [ ] `tools/call` 查询口径与 REST 一致,结果无 REST 信封
|
||||||
|
- [ ] HQ 日志 `botKey=plugin:{id}`
|
||||||
|
- [ ] 现有 API 插件机器人行为不变
|
||||||
|
- [ ] 长连接 Bot 不变
|
||||||
+1
-1
@@ -48,7 +48,7 @@ C 端购酒核销 · 门店扫码核销+打款 · 合伙人拓店履约 · WebAd
|
|||||||
|
|
||||||
**HQ 企微报告(v3.5.15)**:企微机器人下「报告」与「消息推送」分开。日报/周报/月报各配 Webhook 与发送时刻;走群机器人 markdown。账期截在发送日北京 0 点(前一天 24 点),不含发送当天:日报=昨日存量+当日新增;周报/月报=上一自然周/月期末存量+本期新增。用户=有效未合并;合伙人=主账号;订单笔数与金额均排除待支付 / 已取消 / 退款中;订单金额=已付 `payAmount`(`paidAt`);核销=`RedeemRecord`。
|
**HQ 企微报告(v3.5.15)**:企微机器人下「报告」与「消息推送」分开。日报/周报/月报各配 Webhook 与发送时刻;走群机器人 markdown。账期截在发送日北京 0 点(前一天 24 点),不含发送当天:日报=昨日存量+当日新增;周报/月报=上一自然周/月期末存量+本期新增。用户=有效未合并;合伙人=主账号;订单笔数与金额均排除待支付 / 已取消 / 退款中;订单金额=已付 `payAmount`(`paidAt`);核销=`RedeemRecord`。
|
||||||
|
|
||||||
**企微 API 插件(v3.5.17)**:实例在 HQ「企微机器人 → API 插件」维护;共用 `GET /api/v1/wecom/plugin/*`,Header `X-Api-Key` 区分实例并按权限放行。经营指标含新增门店(`newStores`);门店搜索含经营数据;支持门店/信息/套餐审核对照只读查询;支持按合伙人查关联用户/门店/订单(`from`/`to` 时间筛选)。
|
**企微 API 插件(v3.5.17 / MCP v3.5.18)**:实例在 HQ「企微机器人 → API 插件」维护;共用 Header `X-Api-Key`。REST `GET /api/v1/wecom/plugin/*` 与 MCP `POST /api/v1/wecom/plugin/mcp`(Streamable HTTP 无状态)并存;MCP 按权限自动 `tools/list`。经营指标含新增门店(`newStores`);门店搜索含经营数据;支持门店/信息/套餐审核对照只读查询;支持按合伙人查关联用户/门店/订单(`from`/`to` 时间筛选);`dev_plan.read` 可查开发版本(`query_versions`)与按任务状态筛选的任务列表(`query_tasks`)。
|
||||||
|
|
||||||
**HQ 列表(v3.5.9)**:主表不省略号、可横滑;最左序号;列设置(显隐/顺序)与列宽(拖表头)存 `hq_account.list_column_prefs`。主展示列下划线,点击进编辑或详情。门店列表「累计核销好客权益」= 该店 `RedeemRecord.amount` 合计。用户列表昵称只读(点击进详情);双击「备注」离开即保存(`hq_remark`);列表手机号不脱敏。
|
**HQ 列表(v3.5.9)**:主表不省略号、可横滑;最左序号;列设置(显隐/顺序)与列宽(拖表头)存 `hq_account.list_column_prefs`。主展示列下划线,点击进编辑或详情。门店列表「累计核销好客权益」= 该店 `RedeemRecord.amount` 合计。用户列表昵称只读(点击进详情);双击「备注」离开即保存(`hq_remark`);列表手机号不脱敏。
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ HQ 账号/角色(`hq-permissions`,生效=(角色∪追加)−撤销;可绑
|
|||||||
|------|------|
|
|------|------|
|
||||||
| 智能机器人 | `/wecom/bots` 长连接指令 |
|
| 智能机器人 | `/wecom/bots` 长连接指令 |
|
||||||
| API 插件 | HQ `/wecom/plugins` 多实例;`/api/v1/wecom/plugin` Header `X-Api-Key` 只读查询(与长连接独立) |
|
| API 插件 | HQ `/wecom/plugins` 多实例;`/api/v1/wecom/plugin` Header `X-Api-Key` 只读查询(与长连接独立) |
|
||||||
|
| MCP 插件 | 同上 Key;`POST /api/v1/wecom/plugin/mcp` Streamable HTTP;`tools/list` 按权限过滤;`dev_plan.read` → `query_versions` / `query_tasks` |
|
||||||
| 消息推送 | `/wecom/pushes` Webhook+eventKey |
|
| 消息推送 | `/wecom/pushes` Webhook+eventKey |
|
||||||
| 日志 | `/logs/wecom-bots` |
|
| 日志 | `/logs/wecom-bots` |
|
||||||
| C 端微信客服 | 系统设置 `CUSTOMER_SERVICE_WECOM_URL` + `WECOM_CORP_ID`;小程序须已关联该企业微信客服 |
|
| C 端微信客服 | 系统设置 `CUSTOMER_SERVICE_WECOM_URL` + `WECOM_CORP_ID`;小程序须已关联该企业微信客服 |
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import { describe, expect, it } from 'vitest';
|
|||||||
import {
|
import {
|
||||||
WECOM_PLUGIN_API_KEY_HEADER,
|
WECOM_PLUGIN_API_KEY_HEADER,
|
||||||
WECOM_PLUGIN_BASE_PATH,
|
WECOM_PLUGIN_BASE_PATH,
|
||||||
|
WECOM_PLUGIN_MCP_PATH,
|
||||||
WECOM_PLUGIN_PATH_PERMISSION,
|
WECOM_PLUGIN_PATH_PERMISSION,
|
||||||
|
allowedWecomPluginMcpTools,
|
||||||
allowedWecomPluginOpenApiPaths,
|
allowedWecomPluginOpenApiPaths,
|
||||||
maskWecomPluginApiKey,
|
maskWecomPluginApiKey,
|
||||||
parseWecomPluginPermissions,
|
parseWecomPluginPermissions,
|
||||||
|
resolveWecomPluginMcpUrl,
|
||||||
resolveWecomPluginPublicUrl,
|
resolveWecomPluginPublicUrl,
|
||||||
wecomPluginHasPermission,
|
wecomPluginHasPermission,
|
||||||
} from './wecom-plugin';
|
} from './wecom-plugin';
|
||||||
@@ -35,6 +38,15 @@ describe('resolveWecomPluginPublicUrl', () => {
|
|||||||
it('uses X-Api-Key as the plugin auth header', () => {
|
it('uses X-Api-Key as the plugin auth header', () => {
|
||||||
expect(WECOM_PLUGIN_API_KEY_HEADER).toBe('X-Api-Key');
|
expect(WECOM_PLUGIN_API_KEY_HEADER).toBe('X-Api-Key');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('maps hosts to the Streamable HTTP MCP endpoint', () => {
|
||||||
|
expect(resolveWecomPluginMcpUrl('admin.dukanghaoke.com')).toBe(
|
||||||
|
`https://api.dukanghaoke.com${WECOM_PLUGIN_BASE_PATH}${WECOM_PLUGIN_MCP_PATH}`,
|
||||||
|
);
|
||||||
|
expect(resolveWecomPluginMcpUrl('admin-test.dukanghaoke.com')).toBe(
|
||||||
|
`https://api-test.dukanghaoke.com${WECOM_PLUGIN_BASE_PATH}${WECOM_PLUGIN_MCP_PATH}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parseWecomPluginPermissions', () => {
|
describe('parseWecomPluginPermissions', () => {
|
||||||
@@ -76,4 +88,22 @@ describe('maskWecomPluginApiKey / path permission map', () => {
|
|||||||
expect(wecomPluginHasPermission(['order.read'], 'order.read')).toBe(true);
|
expect(wecomPluginHasPermission(['order.read'], 'order.read')).toBe(true);
|
||||||
expect(wecomPluginHasPermission(['order.read'], 'metrics.read')).toBe(false);
|
expect(wecomPluginHasPermission(['order.read'], 'metrics.read')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('filters MCP tools by instance permissions', () => {
|
||||||
|
expect(allowedWecomPluginMcpTools(['order.read', 'user.read'])).toEqual([
|
||||||
|
'query_orders',
|
||||||
|
'query_users',
|
||||||
|
]);
|
||||||
|
expect(allowedWecomPluginMcpTools(['store.audit.read'])).toEqual([
|
||||||
|
'query_store_audits',
|
||||||
|
'query_store_info_audits',
|
||||||
|
'query_store_info_audit_detail',
|
||||||
|
'query_store_package_audits',
|
||||||
|
'query_store_package_audit_detail',
|
||||||
|
]);
|
||||||
|
expect(allowedWecomPluginMcpTools(['partner.read'])).toContain('query_partners');
|
||||||
|
expect(allowedWecomPluginMcpTools(['metrics.read'])).not.toContain('query_orders');
|
||||||
|
expect(allowedWecomPluginOpenApiPaths(['dev_plan.read'])).toEqual(['/versions', '/tasks']);
|
||||||
|
expect(allowedWecomPluginMcpTools(['dev_plan.read'])).toEqual(['query_versions', 'query_tasks']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ export const WECOM_PLUGIN_API_KEY_HEADER = 'X-Api-Key';
|
|||||||
|
|
||||||
export const WECOM_PLUGIN_BASE_PATH = '/api/v1/wecom/plugin';
|
export const WECOM_PLUGIN_BASE_PATH = '/api/v1/wecom/plugin';
|
||||||
|
|
||||||
|
/** 企微 MCP 插件相对 Base 的路径(Streamable HTTP 无状态) */
|
||||||
|
export const WECOM_PLUGIN_MCP_PATH = '/mcp';
|
||||||
|
|
||||||
|
export const WECOM_PLUGIN_MCP_TRANSPORT = 'Streamable HTTP';
|
||||||
|
|
||||||
export const WECOM_PLUGIN_PAGE_SIZE_DEFAULT = 5;
|
export const WECOM_PLUGIN_PAGE_SIZE_DEFAULT = 5;
|
||||||
|
|
||||||
export const WECOM_PLUGIN_PAGE_SIZE_MAX = 10;
|
export const WECOM_PLUGIN_PAGE_SIZE_MAX = 10;
|
||||||
@@ -21,6 +26,7 @@ export const WECOM_PLUGIN_PERMISSIONS = [
|
|||||||
'metrics.read',
|
'metrics.read',
|
||||||
'store.audit.read',
|
'store.audit.read',
|
||||||
'partner.read',
|
'partner.read',
|
||||||
|
'dev_plan.read',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type WecomPluginPermission = (typeof WECOM_PLUGIN_PERMISSIONS)[number];
|
export type WecomPluginPermission = (typeof WECOM_PLUGIN_PERMISSIONS)[number];
|
||||||
@@ -34,6 +40,7 @@ export const WECOM_PLUGIN_PERMISSION_LABELS: Record<WecomPluginPermission, strin
|
|||||||
'metrics.read': '经营指标',
|
'metrics.read': '经营指标',
|
||||||
'store.audit.read': '门店/套餐审核',
|
'store.audit.read': '门店/套餐审核',
|
||||||
'partner.read': '合伙人关联查询',
|
'partner.read': '合伙人关联查询',
|
||||||
|
'dev_plan.read': '查询版本与任务',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WECOM_PLUGIN_PERMISSION_GROUPS: Array<{
|
export const WECOM_PLUGIN_PERMISSION_GROUPS: Array<{
|
||||||
@@ -59,6 +66,11 @@ export const WECOM_PLUGIN_PERMISSION_GROUPS: Array<{
|
|||||||
label: '审核对照',
|
label: '审核对照',
|
||||||
permissions: ['store.audit.read'],
|
permissions: ['store.audit.read'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'dev_plan',
|
||||||
|
label: '开发计划',
|
||||||
|
permissions: ['dev_plan.read'],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** OpenAPI path → 工具权限 */
|
/** OpenAPI path → 工具权限 */
|
||||||
@@ -79,6 +91,95 @@ export const WECOM_PLUGIN_PATH_PERMISSION: Record<string, WecomPluginPermission>
|
|||||||
'/partners/{partnerId}/users': 'partner.read',
|
'/partners/{partnerId}/users': 'partner.read',
|
||||||
'/partners/{partnerId}/stores': 'partner.read',
|
'/partners/{partnerId}/stores': 'partner.read',
|
||||||
'/partners/{partnerId}/orders': 'partner.read',
|
'/partners/{partnerId}/orders': 'partner.read',
|
||||||
|
'/versions': 'dev_plan.read',
|
||||||
|
'/tasks': 'dev_plan.read',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WECOM_PLUGIN_MCP_TOOL_NAMES = [
|
||||||
|
'query_orders',
|
||||||
|
'query_users',
|
||||||
|
'query_stores',
|
||||||
|
'query_redeems',
|
||||||
|
'query_promo_codes',
|
||||||
|
'query_promo_code_stats',
|
||||||
|
'query_metrics',
|
||||||
|
'query_store_audits',
|
||||||
|
'query_store_info_audits',
|
||||||
|
'query_store_info_audit_detail',
|
||||||
|
'query_store_package_audits',
|
||||||
|
'query_store_package_audit_detail',
|
||||||
|
'query_partners',
|
||||||
|
'query_partner_users',
|
||||||
|
'query_partner_stores',
|
||||||
|
'query_partner_orders',
|
||||||
|
'query_versions',
|
||||||
|
'query_tasks',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type WecomPluginMcpToolName = (typeof WECOM_PLUGIN_MCP_TOOL_NAMES)[number];
|
||||||
|
|
||||||
|
export const WECOM_PLUGIN_MCP_TOOL_META: Record<
|
||||||
|
WecomPluginMcpToolName,
|
||||||
|
{ permission: WecomPluginPermission; description: string }
|
||||||
|
> = {
|
||||||
|
query_orders: { permission: 'order.read', description: '按订单号模糊查询' },
|
||||||
|
query_users: { permission: 'user.read', description: '按用户号或 11 位手机号查询;手机号脱敏' },
|
||||||
|
query_stores: {
|
||||||
|
permission: 'store.read',
|
||||||
|
description: '按门店名称模糊查询,返回经营数据:评分、核销笔数、累计核销好客权益、合伙人、审核状态',
|
||||||
|
},
|
||||||
|
query_redeems: { permission: 'redeem.read', description: '按核销单号或门店名查询' },
|
||||||
|
query_promo_codes: { permission: 'promo.read', description: '按推广码 code 或名称查询' },
|
||||||
|
query_promo_code_stats: { permission: 'promo.read', description: '按推广码 code 查询扫码/成交统计' },
|
||||||
|
query_metrics: {
|
||||||
|
permission: 'metrics.read',
|
||||||
|
description: '经营指标。kind=today 为今日截至当前;daily/weekly/monthly 与企微经营报告同一口径',
|
||||||
|
},
|
||||||
|
query_store_audits: { permission: 'store.audit.read', description: '门店入驻审核列表,默认 status=PENDING' },
|
||||||
|
query_store_info_audits: { permission: 'store.audit.read', description: '门店信息变更审核列表' },
|
||||||
|
query_store_info_audit_detail: {
|
||||||
|
permission: 'store.audit.read',
|
||||||
|
description: '门店信息变更审核对比,返回 live vs proposed',
|
||||||
|
},
|
||||||
|
query_store_package_audits: { permission: 'store.audit.read', description: '门店套餐审核列表' },
|
||||||
|
query_store_package_audit_detail: {
|
||||||
|
permission: 'store.audit.read',
|
||||||
|
description: '门店套餐审核对比,返回 proposedPackages 与 livePackages',
|
||||||
|
},
|
||||||
|
query_partners: { permission: 'partner.read', description: '按姓名、公司名、手机号或 ID 搜索合伙人主账号' },
|
||||||
|
query_partner_users: { permission: 'partner.read', description: '查询合伙人关联用户,可选 from/to' },
|
||||||
|
query_partner_stores: { permission: 'partner.read', description: '查询合伙人名下门店及经营数据,可选 from/to' },
|
||||||
|
query_partner_orders: {
|
||||||
|
permission: 'partner.read',
|
||||||
|
description: '查询佣金归属该合伙人的订单,可选 from/to',
|
||||||
|
},
|
||||||
|
query_versions: {
|
||||||
|
permission: 'dev_plan.read',
|
||||||
|
description: '按版本号关键词查询开发计划版本;可选 status 筛选版本状态 PENDING/IN_PROGRESS/TESTING/RELEASED/STOPPED',
|
||||||
|
},
|
||||||
|
query_tasks: {
|
||||||
|
permission: 'dev_plan.read',
|
||||||
|
description:
|
||||||
|
'查询开发任务列表。status 按任务状态筛选:TODO 待开发、IN_PROGRESS 开发中、DEVELOPED 已开发、RELEASED 已上线、STOPPED 已停止;可选 q 搜任务编号/内容,versionNo 限定某版本',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WECOM_PLUGIN_MCP_TOOLS: Record<WecomPluginPermission, WecomPluginMcpToolName[]> = {
|
||||||
|
'order.read': ['query_orders'],
|
||||||
|
'user.read': ['query_users'],
|
||||||
|
'store.read': ['query_stores'],
|
||||||
|
'redeem.read': ['query_redeems'],
|
||||||
|
'promo.read': ['query_promo_codes', 'query_promo_code_stats'],
|
||||||
|
'metrics.read': ['query_metrics'],
|
||||||
|
'store.audit.read': [
|
||||||
|
'query_store_audits',
|
||||||
|
'query_store_info_audits',
|
||||||
|
'query_store_info_audit_detail',
|
||||||
|
'query_store_package_audits',
|
||||||
|
'query_store_package_audit_detail',
|
||||||
|
],
|
||||||
|
'partner.read': ['query_partners', 'query_partner_users', 'query_partner_stores', 'query_partner_orders'],
|
||||||
|
'dev_plan.read': ['query_versions', 'query_tasks'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WECOM_PLUGIN_TOOL_PATHS: Record<WecomPluginPermission, string[]> = {
|
export const WECOM_PLUGIN_TOOL_PATHS: Record<WecomPluginPermission, string[]> = {
|
||||||
@@ -101,6 +202,7 @@ export const WECOM_PLUGIN_TOOL_PATHS: Record<WecomPluginPermission, string[]> =
|
|||||||
'/partners/{partnerId}/stores',
|
'/partners/{partnerId}/stores',
|
||||||
'/partners/{partnerId}/orders',
|
'/partners/{partnerId}/orders',
|
||||||
],
|
],
|
||||||
|
'dev_plan.read': ['/versions', '/tasks'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function parseWecomPluginPermissions(
|
export function parseWecomPluginPermissions(
|
||||||
@@ -145,6 +247,14 @@ export function allowedWecomPluginOpenApiPaths(
|
|||||||
.map(([path]) => path);
|
.map(([path]) => path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 当前实例可暴露给企微 MCP 插件的 tools/list */
|
||||||
|
export function allowedWecomPluginMcpTools(
|
||||||
|
permissions: WecomPluginPermission[],
|
||||||
|
): WecomPluginMcpToolName[] {
|
||||||
|
const allowed = new Set(permissions);
|
||||||
|
return WECOM_PLUGIN_MCP_TOOL_NAMES.filter((name) => allowed.has(WECOM_PLUGIN_MCP_TOOL_META[name].permission));
|
||||||
|
}
|
||||||
|
|
||||||
export function maskWecomPluginApiKey(apiKey?: string | null): string {
|
export function maskWecomPluginApiKey(apiKey?: string | null): string {
|
||||||
const v = String(apiKey ?? '').trim();
|
const v = String(apiKey ?? '').trim();
|
||||||
if (!v) return '未配置';
|
if (!v) return '未配置';
|
||||||
@@ -205,3 +315,7 @@ export function resolveWecomPluginPublicUrl(hostname: string): string {
|
|||||||
}
|
}
|
||||||
return `http://localhost:3010${WECOM_PLUGIN_BASE_PATH}`;
|
return `http://localhost:3010${WECOM_PLUGIN_BASE_PATH}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveWecomPluginMcpUrl(hostname: string): string {
|
||||||
|
return `${resolveWecomPluginPublicUrl(hostname)}${WECOM_PLUGIN_MCP_PATH}`;
|
||||||
|
}
|
||||||
|
|||||||
Generated
+428
@@ -322,6 +322,9 @@ importers:
|
|||||||
'@dukang/shared-types':
|
'@dukang/shared-types':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/shared-types
|
version: link:../../packages/shared-types
|
||||||
|
'@modelcontextprotocol/sdk':
|
||||||
|
specifier: ^1.30.0
|
||||||
|
version: 1.30.0(zod@4.5.4)
|
||||||
'@nestjs/bullmq':
|
'@nestjs/bullmq':
|
||||||
specifier: ^10.2.0
|
specifier: ^10.2.0
|
||||||
version: 10.2.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(bullmq@5.79.2)
|
version: 10.2.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(bullmq@5.79.2)
|
||||||
@@ -400,6 +403,9 @@ importers:
|
|||||||
sharp:
|
sharp:
|
||||||
specifier: ^0.34.5
|
specifier: ^0.34.5
|
||||||
version: 0.34.5
|
version: 0.34.5
|
||||||
|
zod:
|
||||||
|
specifier: ^4.5.4
|
||||||
|
version: 4.5.4
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@nestjs/cli':
|
'@nestjs/cli':
|
||||||
specifier: ^10.4.0
|
specifier: ^10.4.0
|
||||||
@@ -437,6 +443,9 @@ importers:
|
|||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.4.5
|
specifier: ^5.4.5
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
vitest:
|
||||||
|
specifier: ^1.6.1
|
||||||
|
version: 1.6.1(@types/node@20.19.43)(sass@1.101.0)(terser@5.48.0)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -1413,6 +1422,12 @@ packages:
|
|||||||
'@hapi/topo@5.1.0':
|
'@hapi/topo@5.1.0':
|
||||||
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
|
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
|
||||||
|
|
||||||
|
'@hono/node-server@2.1.1':
|
||||||
|
resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
peerDependencies:
|
||||||
|
hono: ^4
|
||||||
|
|
||||||
'@humanfs/core@0.19.2':
|
'@humanfs/core@0.19.2':
|
||||||
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
|
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
@@ -1648,6 +1663,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
|
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
'@modelcontextprotocol/sdk@1.30.0':
|
||||||
|
resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
'@cfworker/json-schema': ^4.1.1
|
||||||
|
zod: ^3.25 || ^4.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@cfworker/json-schema':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
|
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
|
||||||
resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==}
|
resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
@@ -3026,6 +3051,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
accepts@2.0.0:
|
||||||
|
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
acorn-jsx@5.3.2:
|
acorn-jsx@5.3.2:
|
||||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3064,6 +3093,14 @@ packages:
|
|||||||
ajv:
|
ajv:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
ajv-formats@3.0.1:
|
||||||
|
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
||||||
|
peerDependencies:
|
||||||
|
ajv: ^8.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
ajv:
|
||||||
|
optional: true
|
||||||
|
|
||||||
ajv-keywords@3.5.2:
|
ajv-keywords@3.5.2:
|
||||||
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
|
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3365,6 +3402,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
|
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
|
||||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||||
|
|
||||||
|
body-parser@2.3.0:
|
||||||
|
resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
bowser@1.9.4:
|
bowser@1.9.4:
|
||||||
resolution: {integrity: sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==}
|
resolution: {integrity: sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==}
|
||||||
|
|
||||||
@@ -3682,16 +3723,28 @@ packages:
|
|||||||
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
content-disposition@1.1.0:
|
||||||
|
resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
content-type@1.0.5:
|
content-type@1.0.5:
|
||||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
content-type@2.1.0:
|
||||||
|
resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
cookie-signature@1.0.7:
|
cookie-signature@1.0.7:
|
||||||
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
|
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
|
||||||
|
|
||||||
|
cookie-signature@1.2.2:
|
||||||
|
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
|
||||||
|
engines: {node: '>=6.6.0'}
|
||||||
|
|
||||||
cookie@0.7.2:
|
cookie@0.7.2:
|
||||||
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -4165,6 +4218,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||||
engines: {node: '>=0.8.x'}
|
engines: {node: '>=0.8.x'}
|
||||||
|
|
||||||
|
eventsource-parser@3.1.1:
|
||||||
|
resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
eventsource@3.0.7:
|
||||||
|
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
exceljs@4.4.0:
|
exceljs@4.4.0:
|
||||||
resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==}
|
resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==}
|
||||||
engines: {node: '>=8.3.0'}
|
engines: {node: '>=8.3.0'}
|
||||||
@@ -4173,10 +4234,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||||
engines: {node: '>=16.17'}
|
engines: {node: '>=16.17'}
|
||||||
|
|
||||||
|
express-rate-limit@8.7.0:
|
||||||
|
resolution: {integrity: sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==}
|
||||||
|
engines: {node: '>= 16'}
|
||||||
|
peerDependencies:
|
||||||
|
express: '>= 4.11'
|
||||||
|
|
||||||
express@4.22.1:
|
express@4.22.1:
|
||||||
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
|
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
|
||||||
engines: {node: '>= 0.10.0'}
|
engines: {node: '>= 0.10.0'}
|
||||||
|
|
||||||
|
express@5.2.1:
|
||||||
|
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
ext-list@2.2.2:
|
ext-list@2.2.2:
|
||||||
resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==}
|
resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -4306,6 +4377,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
|
resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
finalhandler@2.1.1:
|
||||||
|
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
|
||||||
|
engines: {node: '>= 18.0.0'}
|
||||||
|
|
||||||
find-up@4.1.0:
|
find-up@4.1.0:
|
||||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4380,6 +4455,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
fresh@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
from2@2.3.0:
|
from2@2.3.0:
|
||||||
resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==}
|
resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==}
|
||||||
|
|
||||||
@@ -4602,6 +4681,10 @@ packages:
|
|||||||
hls.js@1.6.16:
|
hls.js@1.6.16:
|
||||||
resolution: {integrity: sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==}
|
resolution: {integrity: sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==}
|
||||||
|
|
||||||
|
hono@4.13.7:
|
||||||
|
resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==}
|
||||||
|
engines: {node: '>=16.9.0'}
|
||||||
|
|
||||||
hookified@1.15.1:
|
hookified@1.15.1:
|
||||||
resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==}
|
resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==}
|
||||||
|
|
||||||
@@ -4656,6 +4739,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
iconv-lite@0.7.3:
|
||||||
|
resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
ics@3.12.0:
|
ics@3.12.0:
|
||||||
resolution: {integrity: sha512-BBPiZ/TbUyZrxui7SHKGOa/n2HUxaW9ucn7cw47NW9OSKe5tItYxIdN7Uczti5nKURmfwVvYi2aLigCJGH1+cA==}
|
resolution: {integrity: sha512-BBPiZ/TbUyZrxui7SHKGOa/n2HUxaW9ucn7cw47NW9OSKe5tItYxIdN7Uczti5nKURmfwVvYi2aLigCJGH1+cA==}
|
||||||
|
|
||||||
@@ -4731,6 +4818,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==}
|
resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==}
|
||||||
engines: {node: '>=12.22.0'}
|
engines: {node: '>=12.22.0'}
|
||||||
|
|
||||||
|
ip-address@10.7.0:
|
||||||
|
resolution: {integrity: sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==}
|
||||||
|
engines: {node: '>= 12'}
|
||||||
|
|
||||||
ip2region@2.3.0:
|
ip2region@2.3.0:
|
||||||
resolution: {integrity: sha512-zV5Xsadzrx9Ej6heoyhbXMsfGWWQ3C6bAIYStrHhw9kzLpGpVNlnAyRBxxPgxA1GNqr1Ti7oUxcWsMWNN3jZBg==}
|
resolution: {integrity: sha512-zV5Xsadzrx9Ej6heoyhbXMsfGWWQ3C6bAIYStrHhw9kzLpGpVNlnAyRBxxPgxA1GNqr1Ti7oUxcWsMWNN3jZBg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4811,6 +4902,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
is-promise@4.0.0:
|
||||||
|
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||||
|
|
||||||
is-retry-allowed@1.2.0:
|
is-retry-allowed@1.2.0:
|
||||||
resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==}
|
resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -4876,6 +4970,9 @@ packages:
|
|||||||
joi@17.13.4:
|
joi@17.13.4:
|
||||||
resolution: {integrity: sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==}
|
resolution: {integrity: sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==}
|
||||||
|
|
||||||
|
jose@6.2.12:
|
||||||
|
resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==}
|
||||||
|
|
||||||
js-base64@2.6.4:
|
js-base64@2.6.4:
|
||||||
resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
|
resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
|
||||||
|
|
||||||
@@ -4912,6 +5009,9 @@ packages:
|
|||||||
json-schema-traverse@1.0.0:
|
json-schema-traverse@1.0.0:
|
||||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||||
|
|
||||||
|
json-schema-typed@8.0.2:
|
||||||
|
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
|
||||||
|
|
||||||
json-stable-stringify-without-jsonify@1.0.1:
|
json-stable-stringify-without-jsonify@1.0.1:
|
||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||||
|
|
||||||
@@ -5186,6 +5286,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
media-typer@1.1.1:
|
||||||
|
resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
memfs@3.5.3:
|
memfs@3.5.3:
|
||||||
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
|
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
@@ -5197,6 +5301,10 @@ packages:
|
|||||||
merge-descriptors@1.0.3:
|
merge-descriptors@1.0.3:
|
||||||
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
|
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
|
||||||
|
|
||||||
|
merge-descriptors@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
merge-stream@2.0.0:
|
merge-stream@2.0.0:
|
||||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||||
|
|
||||||
@@ -5220,10 +5328,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-db@1.54.0:
|
||||||
|
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
mime-types@2.1.35:
|
mime-types@2.1.35:
|
||||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@3.0.2:
|
||||||
|
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
mime@1.6.0:
|
mime@1.6.0:
|
||||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -5342,6 +5458,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
negotiator@1.1.0:
|
||||||
|
resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
neo-async@2.6.2:
|
neo-async@2.6.2:
|
||||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||||
|
|
||||||
@@ -5583,6 +5703,9 @@ packages:
|
|||||||
path-to-regexp@6.3.0:
|
path-to-regexp@6.3.0:
|
||||||
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
|
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
|
||||||
|
|
||||||
|
path-to-regexp@8.4.2:
|
||||||
|
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
|
||||||
|
|
||||||
path-type@4.0.0:
|
path-type@4.0.0:
|
||||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -5643,6 +5766,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
pkce-challenge@5.0.1:
|
||||||
|
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
|
||||||
pkg-types@1.3.1:
|
pkg-types@1.3.1:
|
||||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||||
|
|
||||||
@@ -5822,6 +5949,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
|
resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
qs@6.16.0:
|
||||||
|
resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==}
|
||||||
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
query-string@5.1.1:
|
query-string@5.1.1:
|
||||||
resolution: {integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==}
|
resolution: {integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -5847,6 +5978,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
|
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
raw-body@3.0.2:
|
||||||
|
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
rc-cascader@3.34.0:
|
rc-cascader@3.34.0:
|
||||||
resolution: {integrity: sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==}
|
resolution: {integrity: sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -6262,6 +6397,10 @@ packages:
|
|||||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
router@2.2.0:
|
||||||
|
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
run-async@2.4.1:
|
run-async@2.4.1:
|
||||||
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
|
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
|
||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
@@ -6352,6 +6491,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==}
|
resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
send@1.2.1:
|
||||||
|
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
sentence-case@3.0.4:
|
sentence-case@3.0.4:
|
||||||
resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
|
resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
|
||||||
|
|
||||||
@@ -6372,6 +6515,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==}
|
resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
serve-static@2.2.1:
|
||||||
|
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
set-blocking@2.0.0:
|
set-blocking@2.0.0:
|
||||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||||
|
|
||||||
@@ -6854,6 +7001,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
type-is@2.1.0:
|
||||||
|
resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
typed-array-buffer@1.0.3:
|
typed-array-buffer@1.0.3:
|
||||||
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -7270,6 +7421,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
|
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
zod-to-json-schema@3.25.2:
|
||||||
|
resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.28 || ^4
|
||||||
|
|
||||||
|
zod@4.5.4:
|
||||||
|
resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==}
|
||||||
|
|
||||||
zrender@6.1.0:
|
zrender@6.1.0:
|
||||||
resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==}
|
resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==}
|
||||||
|
|
||||||
@@ -8528,6 +8687,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@hapi/hoek': 9.3.0
|
'@hapi/hoek': 9.3.0
|
||||||
|
|
||||||
|
'@hono/node-server@2.1.1(hono@4.13.7)':
|
||||||
|
dependencies:
|
||||||
|
hono: 4.13.7
|
||||||
|
|
||||||
'@humanfs/core@0.19.2':
|
'@humanfs/core@0.19.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@humanfs/types': 0.15.0
|
'@humanfs/types': 0.15.0
|
||||||
@@ -8710,6 +8873,28 @@ snapshots:
|
|||||||
|
|
||||||
'@lukeed/csprng@1.1.0': {}
|
'@lukeed/csprng@1.1.0': {}
|
||||||
|
|
||||||
|
'@modelcontextprotocol/sdk@1.30.0(zod@4.5.4)':
|
||||||
|
dependencies:
|
||||||
|
'@hono/node-server': 2.1.1(hono@4.13.7)
|
||||||
|
ajv: 8.20.0
|
||||||
|
ajv-formats: 3.0.1(ajv@8.20.0)
|
||||||
|
content-type: 1.0.5
|
||||||
|
cors: 2.8.5
|
||||||
|
cross-spawn: 7.0.6
|
||||||
|
eventsource: 3.0.7
|
||||||
|
eventsource-parser: 3.1.1
|
||||||
|
express: 5.2.1
|
||||||
|
express-rate-limit: 8.7.0(express@5.2.1)
|
||||||
|
hono: 4.13.7
|
||||||
|
jose: 6.2.12
|
||||||
|
json-schema-typed: 8.0.2
|
||||||
|
pkce-challenge: 5.0.1
|
||||||
|
raw-body: 3.0.2
|
||||||
|
zod: 4.5.4
|
||||||
|
zod-to-json-schema: 3.25.2(zod@4.5.4)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
|
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10213,6 +10398,11 @@ snapshots:
|
|||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
negotiator: 0.6.3
|
negotiator: 0.6.3
|
||||||
|
|
||||||
|
accepts@2.0.0:
|
||||||
|
dependencies:
|
||||||
|
mime-types: 3.0.2
|
||||||
|
negotiator: 1.1.0
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.17.0):
|
acorn-jsx@5.3.2(acorn@8.17.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.17.0
|
acorn: 8.17.0
|
||||||
@@ -10245,6 +10435,10 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ajv: 8.20.0
|
ajv: 8.20.0
|
||||||
|
|
||||||
|
ajv-formats@3.0.1(ajv@8.20.0):
|
||||||
|
optionalDependencies:
|
||||||
|
ajv: 8.20.0
|
||||||
|
|
||||||
ajv-keywords@3.5.2(ajv@6.15.0):
|
ajv-keywords@3.5.2(ajv@6.15.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.15.0
|
ajv: 6.15.0
|
||||||
@@ -10674,6 +10868,20 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
body-parser@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
bytes: 3.1.2
|
||||||
|
content-type: 2.1.0
|
||||||
|
debug: 4.4.3
|
||||||
|
http-errors: 2.0.1
|
||||||
|
iconv-lite: 0.7.3
|
||||||
|
on-finished: 2.4.1
|
||||||
|
qs: 6.16.0
|
||||||
|
raw-body: 3.0.2
|
||||||
|
type-is: 2.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
bowser@1.9.4: {}
|
bowser@1.9.4: {}
|
||||||
|
|
||||||
brace-expansion@1.1.15:
|
brace-expansion@1.1.15:
|
||||||
@@ -11060,12 +11268,18 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
|
|
||||||
|
content-disposition@1.1.0: {}
|
||||||
|
|
||||||
content-type@1.0.5: {}
|
content-type@1.0.5: {}
|
||||||
|
|
||||||
|
content-type@2.1.0: {}
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
cookie-signature@1.0.7: {}
|
cookie-signature@1.0.7: {}
|
||||||
|
|
||||||
|
cookie-signature@1.2.2: {}
|
||||||
|
|
||||||
cookie@0.7.2: {}
|
cookie@0.7.2: {}
|
||||||
|
|
||||||
copy-to-clipboard@3.3.3:
|
copy-to-clipboard@3.3.3:
|
||||||
@@ -11594,6 +11808,12 @@ snapshots:
|
|||||||
|
|
||||||
events@3.3.0: {}
|
events@3.3.0: {}
|
||||||
|
|
||||||
|
eventsource-parser@3.1.1: {}
|
||||||
|
|
||||||
|
eventsource@3.0.7:
|
||||||
|
dependencies:
|
||||||
|
eventsource-parser: 3.1.1
|
||||||
|
|
||||||
exceljs@4.4.0:
|
exceljs@4.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
archiver: 5.3.2
|
archiver: 5.3.2
|
||||||
@@ -11618,6 +11838,14 @@ snapshots:
|
|||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
strip-final-newline: 3.0.0
|
strip-final-newline: 3.0.0
|
||||||
|
|
||||||
|
express-rate-limit@8.7.0(express@5.2.1):
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
express: 5.2.1
|
||||||
|
ip-address: 10.7.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
express@4.22.1:
|
express@4.22.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
accepts: 1.3.8
|
accepts: 1.3.8
|
||||||
@@ -11654,6 +11882,39 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
express@5.2.1:
|
||||||
|
dependencies:
|
||||||
|
accepts: 2.0.0
|
||||||
|
body-parser: 2.3.0
|
||||||
|
content-disposition: 1.1.0
|
||||||
|
content-type: 1.0.5
|
||||||
|
cookie: 0.7.2
|
||||||
|
cookie-signature: 1.2.2
|
||||||
|
debug: 4.4.3
|
||||||
|
depd: 2.0.0
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
etag: 1.8.1
|
||||||
|
finalhandler: 2.1.1
|
||||||
|
fresh: 2.0.0
|
||||||
|
http-errors: 2.0.1
|
||||||
|
merge-descriptors: 2.0.0
|
||||||
|
mime-types: 3.0.2
|
||||||
|
on-finished: 2.4.1
|
||||||
|
once: 1.4.0
|
||||||
|
parseurl: 1.3.3
|
||||||
|
proxy-addr: 2.0.7
|
||||||
|
qs: 6.14.2
|
||||||
|
range-parser: 1.2.1
|
||||||
|
router: 2.2.0
|
||||||
|
send: 1.2.1
|
||||||
|
serve-static: 2.2.1
|
||||||
|
statuses: 2.0.2
|
||||||
|
type-is: 2.1.0
|
||||||
|
vary: 1.1.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
ext-list@2.2.2:
|
ext-list@2.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-db: 1.52.0
|
mime-db: 1.52.0
|
||||||
@@ -11783,6 +12044,17 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
finalhandler@2.1.1:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
on-finished: 2.4.1
|
||||||
|
parseurl: 1.3.3
|
||||||
|
statuses: 2.0.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
find-up@4.1.0:
|
find-up@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
locate-path: 5.0.0
|
locate-path: 5.0.0
|
||||||
@@ -11879,6 +12151,8 @@ snapshots:
|
|||||||
|
|
||||||
fresh@0.5.2: {}
|
fresh@0.5.2: {}
|
||||||
|
|
||||||
|
fresh@2.0.0: {}
|
||||||
|
|
||||||
from2@2.3.0:
|
from2@2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
inherits: 2.0.4
|
inherits: 2.0.4
|
||||||
@@ -12138,6 +12412,8 @@ snapshots:
|
|||||||
|
|
||||||
hls.js@1.6.16: {}
|
hls.js@1.6.16: {}
|
||||||
|
|
||||||
|
hono@4.13.7: {}
|
||||||
|
|
||||||
hookified@1.15.1: {}
|
hookified@1.15.1: {}
|
||||||
|
|
||||||
hookified@2.2.0: {}
|
hookified@2.2.0: {}
|
||||||
@@ -12202,6 +12478,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
|
iconv-lite@0.7.3:
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
ics@3.12.0:
|
ics@3.12.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.15
|
nanoid: 3.3.15
|
||||||
@@ -12317,6 +12597,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
ip-address@10.7.0: {}
|
||||||
|
|
||||||
ip2region@2.3.0(@types/node@20.19.43):
|
ip2region@2.3.0(@types/node@20.19.43):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.43
|
'@types/node': 20.19.43
|
||||||
@@ -12369,6 +12651,8 @@ snapshots:
|
|||||||
|
|
||||||
is-plain-object@5.0.0: {}
|
is-plain-object@5.0.0: {}
|
||||||
|
|
||||||
|
is-promise@4.0.0: {}
|
||||||
|
|
||||||
is-retry-allowed@1.2.0: {}
|
is-retry-allowed@1.2.0: {}
|
||||||
|
|
||||||
is-stream@1.1.0: {}
|
is-stream@1.1.0: {}
|
||||||
@@ -12432,6 +12716,8 @@ snapshots:
|
|||||||
'@sideway/formula': 3.0.1
|
'@sideway/formula': 3.0.1
|
||||||
'@sideway/pinpoint': 2.0.0
|
'@sideway/pinpoint': 2.0.0
|
||||||
|
|
||||||
|
jose@6.2.12: {}
|
||||||
|
|
||||||
js-base64@2.6.4: {}
|
js-base64@2.6.4: {}
|
||||||
|
|
||||||
js-md5@0.8.3: {}
|
js-md5@0.8.3: {}
|
||||||
@@ -12456,6 +12742,8 @@ snapshots:
|
|||||||
|
|
||||||
json-schema-traverse@1.0.0: {}
|
json-schema-traverse@1.0.0: {}
|
||||||
|
|
||||||
|
json-schema-typed@8.0.2: {}
|
||||||
|
|
||||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||||
|
|
||||||
json2mq@0.2.0:
|
json2mq@0.2.0:
|
||||||
@@ -12710,6 +12998,8 @@ snapshots:
|
|||||||
|
|
||||||
media-typer@0.3.0: {}
|
media-typer@0.3.0: {}
|
||||||
|
|
||||||
|
media-typer@1.1.1: {}
|
||||||
|
|
||||||
memfs@3.5.3:
|
memfs@3.5.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
fs-monkey: 1.1.0
|
fs-monkey: 1.1.0
|
||||||
@@ -12718,6 +13008,8 @@ snapshots:
|
|||||||
|
|
||||||
merge-descriptors@1.0.3: {}
|
merge-descriptors@1.0.3: {}
|
||||||
|
|
||||||
|
merge-descriptors@2.0.0: {}
|
||||||
|
|
||||||
merge-stream@2.0.0: {}
|
merge-stream@2.0.0: {}
|
||||||
|
|
||||||
merge2@1.4.1: {}
|
merge2@1.4.1: {}
|
||||||
@@ -12733,10 +13025,16 @@ snapshots:
|
|||||||
|
|
||||||
mime-db@1.52.0: {}
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
|
mime-db@1.54.0: {}
|
||||||
|
|
||||||
mime-types@2.1.35:
|
mime-types@2.1.35:
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-db: 1.52.0
|
mime-db: 1.52.0
|
||||||
|
|
||||||
|
mime-types@3.0.2:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.54.0
|
||||||
|
|
||||||
mime@1.6.0: {}
|
mime@1.6.0: {}
|
||||||
|
|
||||||
mime@2.6.0: {}
|
mime@2.6.0: {}
|
||||||
@@ -12842,6 +13140,10 @@ snapshots:
|
|||||||
|
|
||||||
negotiator@0.6.3: {}
|
negotiator@0.6.3: {}
|
||||||
|
|
||||||
|
negotiator@1.1.0:
|
||||||
|
dependencies:
|
||||||
|
content-type: 2.1.0
|
||||||
|
|
||||||
neo-async@2.6.2: {}
|
neo-async@2.6.2: {}
|
||||||
|
|
||||||
no-case@2.3.2:
|
no-case@2.3.2:
|
||||||
@@ -13071,6 +13373,8 @@ snapshots:
|
|||||||
|
|
||||||
path-to-regexp@6.3.0: {}
|
path-to-regexp@6.3.0: {}
|
||||||
|
|
||||||
|
path-to-regexp@8.4.2: {}
|
||||||
|
|
||||||
path-type@4.0.0: {}
|
path-type@4.0.0: {}
|
||||||
|
|
||||||
pathe@1.1.2: {}
|
pathe@1.1.2: {}
|
||||||
@@ -13117,6 +13421,8 @@ snapshots:
|
|||||||
|
|
||||||
pirates@4.0.7: {}
|
pirates@4.0.7: {}
|
||||||
|
|
||||||
|
pkce-challenge@5.0.1: {}
|
||||||
|
|
||||||
pkg-types@1.3.1:
|
pkg-types@1.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
confbox: 0.1.8
|
confbox: 0.1.8
|
||||||
@@ -13276,6 +13582,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.1
|
side-channel: 1.1.1
|
||||||
|
|
||||||
|
qs@6.16.0:
|
||||||
|
dependencies:
|
||||||
|
es-define-property: 1.0.1
|
||||||
|
side-channel: 1.1.1
|
||||||
|
|
||||||
query-string@5.1.1:
|
query-string@5.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
decode-uri-component: 0.2.2
|
decode-uri-component: 0.2.2
|
||||||
@@ -13308,6 +13619,13 @@ snapshots:
|
|||||||
iconv-lite: 0.4.24
|
iconv-lite: 0.4.24
|
||||||
unpipe: 1.0.0
|
unpipe: 1.0.0
|
||||||
|
|
||||||
|
raw-body@3.0.2:
|
||||||
|
dependencies:
|
||||||
|
bytes: 3.1.2
|
||||||
|
http-errors: 2.0.1
|
||||||
|
iconv-lite: 0.7.3
|
||||||
|
unpipe: 1.0.0
|
||||||
|
|
||||||
rc-cascader@3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
rc-cascader@3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.7
|
'@babel/runtime': 7.29.7
|
||||||
@@ -13838,6 +14156,16 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc': 4.62.2
|
'@rollup/rollup-win32-x64-msvc': 4.62.2
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
router@2.2.0:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
depd: 2.0.0
|
||||||
|
is-promise: 4.0.0
|
||||||
|
parseurl: 1.3.3
|
||||||
|
path-to-regexp: 8.4.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
run-async@2.4.1: {}
|
run-async@2.4.1: {}
|
||||||
|
|
||||||
run-async@3.0.0: {}
|
run-async@3.0.0: {}
|
||||||
@@ -13954,6 +14282,22 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
send@1.2.1:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
etag: 1.8.1
|
||||||
|
fresh: 2.0.0
|
||||||
|
http-errors: 2.0.1
|
||||||
|
mime-types: 3.0.2
|
||||||
|
ms: 2.1.3
|
||||||
|
on-finished: 2.4.1
|
||||||
|
range-parser: 1.2.1
|
||||||
|
statuses: 2.0.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
sentence-case@3.0.4:
|
sentence-case@3.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
no-case: 3.0.4
|
no-case: 3.0.4
|
||||||
@@ -13979,6 +14323,15 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
serve-static@2.2.1:
|
||||||
|
dependencies:
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
parseurl: 1.3.3
|
||||||
|
send: 1.2.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
set-blocking@2.0.0: {}
|
set-blocking@2.0.0: {}
|
||||||
|
|
||||||
set-function-length@1.2.2:
|
set-function-length@1.2.2:
|
||||||
@@ -14507,6 +14860,12 @@ snapshots:
|
|||||||
media-typer: 0.3.0
|
media-typer: 0.3.0
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
|
|
||||||
|
type-is@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
content-type: 2.1.0
|
||||||
|
media-typer: 1.1.1
|
||||||
|
mime-types: 3.0.2
|
||||||
|
|
||||||
typed-array-buffer@1.0.3:
|
typed-array-buffer@1.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
@@ -14667,6 +15026,24 @@ snapshots:
|
|||||||
|
|
||||||
vary@1.1.2: {}
|
vary@1.1.2: {}
|
||||||
|
|
||||||
|
vite-node@1.6.1(@types/node@20.19.43)(sass@1.101.0)(terser@5.48.0):
|
||||||
|
dependencies:
|
||||||
|
cac: 6.7.14
|
||||||
|
debug: 4.4.3
|
||||||
|
pathe: 1.1.2
|
||||||
|
picocolors: 1.1.1
|
||||||
|
vite: 5.4.21(@types/node@20.19.43)(sass@1.101.0)(terser@5.48.0)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- sass-embedded
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
vite-node@1.6.1(@types/node@25.9.5)(sass@1.101.0)(terser@5.48.0):
|
vite-node@1.6.1(@types/node@25.9.5)(sass@1.101.0)(terser@5.48.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
@@ -14693,6 +15070,17 @@ snapshots:
|
|||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
vite: 5.4.21(@types/node@25.9.5)(sass@1.101.0)(terser@5.48.0)
|
vite: 5.4.21(@types/node@25.9.5)(sass@1.101.0)(terser@5.48.0)
|
||||||
|
|
||||||
|
vite@5.4.21(@types/node@20.19.43)(sass@1.101.0)(terser@5.48.0):
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.21.5
|
||||||
|
postcss: 8.5.15
|
||||||
|
rollup: 4.62.2
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 20.19.43
|
||||||
|
fsevents: 2.3.3
|
||||||
|
sass: 1.101.0
|
||||||
|
terser: 5.48.0
|
||||||
|
|
||||||
vite@5.4.21(@types/node@25.9.5)(sass@1.101.0)(terser@5.48.0):
|
vite@5.4.21(@types/node@25.9.5)(sass@1.101.0)(terser@5.48.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.21.5
|
esbuild: 0.21.5
|
||||||
@@ -14704,6 +15092,40 @@ snapshots:
|
|||||||
sass: 1.101.0
|
sass: 1.101.0
|
||||||
terser: 5.48.0
|
terser: 5.48.0
|
||||||
|
|
||||||
|
vitest@1.6.1(@types/node@20.19.43)(sass@1.101.0)(terser@5.48.0):
|
||||||
|
dependencies:
|
||||||
|
'@vitest/expect': 1.6.1
|
||||||
|
'@vitest/runner': 1.6.1
|
||||||
|
'@vitest/snapshot': 1.6.1
|
||||||
|
'@vitest/spy': 1.6.1
|
||||||
|
'@vitest/utils': 1.6.1
|
||||||
|
acorn-walk: 8.3.5
|
||||||
|
chai: 4.5.0
|
||||||
|
debug: 4.4.3
|
||||||
|
execa: 8.0.1
|
||||||
|
local-pkg: 0.5.1
|
||||||
|
magic-string: 0.30.21
|
||||||
|
pathe: 1.1.2
|
||||||
|
picocolors: 1.1.1
|
||||||
|
std-env: 3.10.0
|
||||||
|
strip-literal: 2.1.1
|
||||||
|
tinybench: 2.9.0
|
||||||
|
tinypool: 0.8.4
|
||||||
|
vite: 5.4.21(@types/node@20.19.43)(sass@1.101.0)(terser@5.48.0)
|
||||||
|
vite-node: 1.6.1(@types/node@20.19.43)(sass@1.101.0)(terser@5.48.0)
|
||||||
|
why-is-node-running: 2.3.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 20.19.43
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- sass-embedded
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
vitest@1.6.1(@types/node@25.9.5)(sass@1.101.0)(terser@5.48.0):
|
vitest@1.6.1(@types/node@25.9.5)(sass@1.101.0)(terser@5.48.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 1.6.1
|
'@vitest/expect': 1.6.1
|
||||||
@@ -14992,6 +15414,12 @@ snapshots:
|
|||||||
compress-commons: 6.0.2
|
compress-commons: 6.0.2
|
||||||
readable-stream: 4.7.0
|
readable-stream: 4.7.0
|
||||||
|
|
||||||
|
zod-to-json-schema@3.25.2(zod@4.5.4):
|
||||||
|
dependencies:
|
||||||
|
zod: 4.5.4
|
||||||
|
|
||||||
|
zod@4.5.4: {}
|
||||||
|
|
||||||
zrender@6.1.0:
|
zrender@6.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.3.0
|
tslib: 2.3.0
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"dev": "nest start --watch",
|
"dev": "nest start --watch",
|
||||||
"start": "node dist/main",
|
"start": "node dist/main",
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
|
"test": "vitest run src",
|
||||||
"prisma:generate": "prisma generate",
|
"prisma:generate": "prisma generate",
|
||||||
"prisma:push": "prisma db push",
|
"prisma:push": "prisma db push",
|
||||||
"prisma:migrate": "prisma migrate dev",
|
"prisma:migrate": "prisma migrate dev",
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
"@alicloud/openapi-client": "^0.4.15",
|
"@alicloud/openapi-client": "^0.4.15",
|
||||||
"@dukang/domain": "workspace:*",
|
"@dukang/domain": "workspace:*",
|
||||||
"@dukang/shared-types": "workspace:*",
|
"@dukang/shared-types": "workspace:*",
|
||||||
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
||||||
"@nestjs/bullmq": "^10.2.0",
|
"@nestjs/bullmq": "^10.2.0",
|
||||||
"@nestjs/common": "^10.4.0",
|
"@nestjs/common": "^10.4.0",
|
||||||
"@nestjs/config": "^3.2.0",
|
"@nestjs/config": "^3.2.0",
|
||||||
@@ -58,7 +60,8 @@
|
|||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"sharp": "^0.34.5"
|
"sharp": "^0.34.5",
|
||||||
|
"zod": "^4.5.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.4.0",
|
"@nestjs/cli": "^10.4.0",
|
||||||
@@ -72,6 +75,7 @@
|
|||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"prisma": "^5.18.0",
|
"prisma": "^5.18.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5",
|
||||||
|
"vitest": "^1.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { skipWecomPluginResponseWrap } from './response.interceptor';
|
||||||
|
|
||||||
|
describe('skipWecomPluginResponseWrap', () => {
|
||||||
|
it('skips OpenAPI and MCP JSON-RPC paths', () => {
|
||||||
|
expect(skipWecomPluginResponseWrap('/api/v1/wecom/plugin/openapi.json')).toBe(true);
|
||||||
|
expect(skipWecomPluginResponseWrap('/api/v1/wecom/plugin/mcp')).toBe(true);
|
||||||
|
expect(skipWecomPluginResponseWrap('/api/v1/wecom/plugin/mcp?x=1')).toBe(true);
|
||||||
|
expect(skipWecomPluginResponseWrap('/api/v1/wecom/plugin/metrics')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,9 +6,9 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Observable, map } from 'rxjs';
|
import { Observable, map } from 'rxjs';
|
||||||
|
|
||||||
function skipResponseWrap(url?: string): boolean {
|
export function skipWecomPluginResponseWrap(url?: string): boolean {
|
||||||
const path = (url || '').split('?')[0];
|
const path = (url || '').split('?')[0];
|
||||||
return path.endsWith('/wecom/plugin/openapi.json');
|
return path.endsWith('/wecom/plugin/openapi.json') || path.includes('/wecom/plugin/mcp');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -16,7 +16,7 @@ export class ResponseInterceptor implements NestInterceptor {
|
|||||||
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
||||||
const req = context.switchToHttp().getRequest<{ originalUrl?: string; url?: string }>();
|
const req = context.switchToHttp().getRequest<{ originalUrl?: string; url?: string }>();
|
||||||
const res = context.switchToHttp().getResponse<{ headersSent?: boolean }>();
|
const res = context.switchToHttp().getResponse<{ headersSent?: boolean }>();
|
||||||
if (skipResponseWrap(req.originalUrl || req.url)) {
|
if (skipWecomPluginResponseWrap(req.originalUrl || req.url)) {
|
||||||
return next.handle();
|
return next.handle();
|
||||||
}
|
}
|
||||||
return next.handle().pipe(
|
return next.handle().pipe(
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Controller, Delete, Get, Post, Req, Res, UseGuards } from '@nestjs/common';
|
||||||
|
import type { Request, Response } from 'express';
|
||||||
|
import { CurrentWecomPlugin } from './wecom-plugin.decorators';
|
||||||
|
import { WecomPluginGuard } from './wecom-plugin.guard';
|
||||||
|
import { WecomPluginMcpFactory } from './wecom-plugin-mcp.factory';
|
||||||
|
import type { WecomPluginRuntime } from './wecom-plugin.types';
|
||||||
|
|
||||||
|
@Controller('wecom/plugin/mcp')
|
||||||
|
@UseGuards(WecomPluginGuard)
|
||||||
|
export class WecomPluginMcpController {
|
||||||
|
constructor(private readonly mcp: WecomPluginMcpFactory) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
post(@CurrentWecomPlugin() plugin: WecomPluginRuntime, @Req() req: Request, @Res() res: Response) {
|
||||||
|
return this.mcp.handle(req, res, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
get(@CurrentWecomPlugin() plugin: WecomPluginRuntime, @Req() req: Request, @Res() res: Response) {
|
||||||
|
return this.mcp.handle(req, res, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete()
|
||||||
|
delete(@CurrentWecomPlugin() plugin: WecomPluginRuntime, @Req() req: Request, @Res() res: Response) {
|
||||||
|
return this.mcp.handle(req, res, plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||||
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||||
|
import http from 'node:http';
|
||||||
|
import type { AddressInfo } from 'node:net';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { createWecomPluginMcpServer, WecomPluginMcpFactory } from './wecom-plugin-mcp.factory';
|
||||||
|
import type { WecomPluginQueryService } from './wecom-plugin-query.service';
|
||||||
|
import type { WecomPluginRuntime } from './wecom-plugin.types';
|
||||||
|
|
||||||
|
const plugin: WecomPluginRuntime = {
|
||||||
|
id: '9',
|
||||||
|
name: 'ops-readonly',
|
||||||
|
permissions: ['order.read', 'metrics.read'],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function listen(server: http.Server): Promise<string> {
|
||||||
|
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||||
|
const { port } = server.address() as AddressInfo;
|
||||||
|
return `http://127.0.0.1:${port}/mcp`;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('WecomPluginMcpFactory Streamable HTTP', () => {
|
||||||
|
it('lists only permitted tools and calls query_orders', async () => {
|
||||||
|
const query = {
|
||||||
|
queryOrders: vi.fn().mockResolvedValue({ total: 1, items: [{ orderNo: 'DK1' }] }),
|
||||||
|
} as unknown as WecomPluginQueryService;
|
||||||
|
const factory = new WecomPluginMcpFactory(query);
|
||||||
|
const httpServer = http.createServer((req, res) => {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
req.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
||||||
|
req.on('end', () => {
|
||||||
|
const raw = Buffer.concat(chunks).toString('utf8');
|
||||||
|
(req as http.IncomingMessage & { body?: unknown }).body = raw ? JSON.parse(raw) : undefined;
|
||||||
|
void factory.handle(req, res, plugin);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = await listen(httpServer);
|
||||||
|
const client = new Client({ name: 'wecom-plugin-test', version: '1.0.0' });
|
||||||
|
const transport = new StreamableHTTPClientTransport(new URL(url));
|
||||||
|
try {
|
||||||
|
await client.connect(transport);
|
||||||
|
const listed = await client.listTools();
|
||||||
|
expect(listed.tools.map((t) => t.name).sort()).toEqual(['query_metrics', 'query_orders']);
|
||||||
|
const called = await client.callTool({ name: 'query_orders', arguments: { q: 'DK' } });
|
||||||
|
expect(called.isError).toBeFalsy();
|
||||||
|
const text = called.content.find((c) => c.type === 'text');
|
||||||
|
expect(text && 'text' in text ? JSON.parse(text.text) : null).toEqual({
|
||||||
|
total: 1,
|
||||||
|
items: [{ orderNo: 'DK1' }],
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await client.close().catch(() => undefined);
|
||||||
|
await new Promise<void>((resolve, reject) => httpServer.close((err) => (err ? reject(err) : resolve())));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('createWecomPluginMcpServer does not register unauthorized tools', () => {
|
||||||
|
const query = {} as WecomPluginQueryService;
|
||||||
|
const server = createWecomPluginMcpServer(query, plugin, 'alice');
|
||||||
|
expect(server).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { allowedWecomPluginMcpTools } from '@dukang/shared-types';
|
||||||
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||||
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||||
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||||
|
import { WECOM_PLUGIN_MCP_TOOL_DEFS } from './wecom-plugin-mcp.tools';
|
||||||
|
import { WecomPluginQueryService } from './wecom-plugin-query.service';
|
||||||
|
import type { WecomPluginRuntime } from './wecom-plugin.types';
|
||||||
|
import { wecomPluginCaller } from './wecom-plugin.util';
|
||||||
|
|
||||||
|
export function createWecomPluginMcpServer(
|
||||||
|
query: WecomPluginQueryService,
|
||||||
|
plugin: WecomPluginRuntime,
|
||||||
|
wecomUserId: string,
|
||||||
|
): McpServer {
|
||||||
|
const server = new McpServer({
|
||||||
|
name: plugin.name || 'dukang-wecom-plugin',
|
||||||
|
version: '1.0.0',
|
||||||
|
});
|
||||||
|
const allowed = new Set(allowedWecomPluginMcpTools(plugin.permissions));
|
||||||
|
const ctx = { query, plugin, wecomUserId };
|
||||||
|
for (const def of WECOM_PLUGIN_MCP_TOOL_DEFS) {
|
||||||
|
if (!allowed.has(def.name)) continue;
|
||||||
|
server.registerTool(
|
||||||
|
def.name,
|
||||||
|
{ title: def.name, description: def.description, inputSchema: def.inputSchema },
|
||||||
|
async (args) => def.invoke(ctx, (args ?? {}) as Record<string, unknown>),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WecomPluginMcpFactory {
|
||||||
|
private readonly logger = new Logger(WecomPluginMcpFactory.name);
|
||||||
|
|
||||||
|
constructor(@Inject(WecomPluginQueryService) private readonly query: WecomPluginQueryService) {}
|
||||||
|
|
||||||
|
async handle(
|
||||||
|
req: IncomingMessage & { body?: unknown; headers: IncomingMessage['headers'] },
|
||||||
|
res: ServerResponse,
|
||||||
|
plugin: WecomPluginRuntime,
|
||||||
|
): Promise<void> {
|
||||||
|
const server = createWecomPluginMcpServer(this.query, plugin, wecomPluginCaller(req));
|
||||||
|
const transport = new StreamableHTTPServerTransport({
|
||||||
|
sessionIdGenerator: undefined,
|
||||||
|
});
|
||||||
|
res.on('close', () => {
|
||||||
|
void transport.close();
|
||||||
|
void server.close();
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await server.connect(transport);
|
||||||
|
await transport.handleRequest(req, res, req.body);
|
||||||
|
} catch (e) {
|
||||||
|
const message = e instanceof Error ? e.message : String(e);
|
||||||
|
this.logger.error(`wecom plugin MCP failed: ${message}`);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.setHeader('Content-Type', 'application/json');
|
||||||
|
res.end(
|
||||||
|
JSON.stringify({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
error: { code: -32603, message },
|
||||||
|
id: null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
import { WECOM_PLUGIN_MCP_TOOL_NAMES } from '@dukang/shared-types';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { WECOM_PLUGIN_MCP_TOOL_DEFS } from './wecom-plugin-mcp.tools';
|
||||||
|
import type { WecomPluginQueryService } from './wecom-plugin-query.service';
|
||||||
|
import type { WecomPluginRuntime } from './wecom-plugin.types';
|
||||||
|
|
||||||
|
const plugin: WecomPluginRuntime = {
|
||||||
|
id: '1',
|
||||||
|
name: 'ops',
|
||||||
|
permissions: ['order.read', 'metrics.read'],
|
||||||
|
};
|
||||||
|
|
||||||
|
function textOf(result: { content: Array<{ type: string; text?: string }> }): unknown {
|
||||||
|
const part = result.content[0];
|
||||||
|
expect(part?.type).toBe('text');
|
||||||
|
return JSON.parse(String(part?.text));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('WECOM_PLUGIN_MCP_TOOL_DEFS', () => {
|
||||||
|
it('covers the shared-types catalog 1:1', () => {
|
||||||
|
expect(WECOM_PLUGIN_MCP_TOOL_DEFS.map((d) => d.name).sort()).toEqual([...WECOM_PLUGIN_MCP_TOOL_NAMES].sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns raw JSON without REST envelope', async () => {
|
||||||
|
const query = {
|
||||||
|
queryOrders: vi.fn().mockResolvedValue({ total: 1, items: [{ orderNo: 'DK1' }] }),
|
||||||
|
} as unknown as WecomPluginQueryService;
|
||||||
|
const def = WECOM_PLUGIN_MCP_TOOL_DEFS.find((d) => d.name === 'query_orders');
|
||||||
|
expect(def).toBeTruthy();
|
||||||
|
const result = await def!.invoke({ query, plugin, wecomUserId: 'alice' }, { q: 'DK' });
|
||||||
|
expect(result.isError).toBeFalsy();
|
||||||
|
expect(textOf(result)).toEqual({ total: 1, items: [{ orderNo: 'DK1' }] });
|
||||||
|
expect(query.queryOrders).toHaveBeenCalledWith(plugin, 'alice', 'DK', undefined, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps HttpException to isError text', async () => {
|
||||||
|
const query = {
|
||||||
|
queryOrders: vi.fn().mockRejectedValue(new BadRequestException('请提供查询关键词 q')),
|
||||||
|
} as unknown as WecomPluginQueryService;
|
||||||
|
const def = WECOM_PLUGIN_MCP_TOOL_DEFS.find((d) => d.name === 'query_orders');
|
||||||
|
const result = await def!.invoke({ query, plugin, wecomUserId: 'alice' }, {});
|
||||||
|
expect(result.isError).toBe(true);
|
||||||
|
expect(result.content[0]).toMatchObject({ type: 'text', text: '请提供查询关键词 q' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invokes query_versions and query_tasks', async () => {
|
||||||
|
const query = {
|
||||||
|
queryVersions: vi.fn().mockResolvedValue({ total: 1, items: [{ versionNo: 'v3.5.18' }] }),
|
||||||
|
queryTasks: vi.fn().mockResolvedValue({ total: 1, items: [{ taskNo: 'T1', status: 'TODO' }] }),
|
||||||
|
} as unknown as WecomPluginQueryService;
|
||||||
|
const versions = WECOM_PLUGIN_MCP_TOOL_DEFS.find((d) => d.name === 'query_versions');
|
||||||
|
const tasks = WECOM_PLUGIN_MCP_TOOL_DEFS.find((d) => d.name === 'query_tasks');
|
||||||
|
expect(versions).toBeTruthy();
|
||||||
|
expect(tasks).toBeTruthy();
|
||||||
|
await versions!.invoke(
|
||||||
|
{ query, plugin, wecomUserId: 'alice' },
|
||||||
|
{ q: 'v3.5', status: 'IN_PROGRESS', page: 1, pageSize: 5 },
|
||||||
|
);
|
||||||
|
expect(query.queryVersions).toHaveBeenCalledWith(plugin, 'alice', 'v3.5', 'IN_PROGRESS', '1', '5');
|
||||||
|
await tasks!.invoke(
|
||||||
|
{ query, plugin, wecomUserId: 'alice' },
|
||||||
|
{ status: 'TODO', q: '核销', versionNo: 'v3.5.18', page: 1, pageSize: 5 },
|
||||||
|
);
|
||||||
|
expect(query.queryTasks).toHaveBeenCalledWith(
|
||||||
|
plugin,
|
||||||
|
'alice',
|
||||||
|
'核销',
|
||||||
|
'TODO',
|
||||||
|
'v3.5.18',
|
||||||
|
'1',
|
||||||
|
'5',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
import { WECOM_PLUGIN_MCP_TOOL_META, type WecomPluginMcpToolName } from '@dukang/shared-types';
|
||||||
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { wecomPluginMcpErrorText, wecomPluginQueryArg } from './wecom-plugin.util';
|
||||||
|
import type { WecomPluginQueryService } from './wecom-plugin-query.service';
|
||||||
|
import type { WecomPluginRuntime } from './wecom-plugin.types';
|
||||||
|
|
||||||
|
const q = (description: string) => z.string().describe(description);
|
||||||
|
const page = z.number().int().min(1).optional().describe('页码,默认 1');
|
||||||
|
const pageSize = z.number().int().min(1).max(10).optional().describe('每页条数,默认 5,最大 10');
|
||||||
|
const from = z.string().optional().describe('起始日期 YYYY-MM-DD 或 ISO');
|
||||||
|
const to = z.string().optional().describe('结束日期 YYYY-MM-DD 或 ISO(含当天)');
|
||||||
|
const auditStatus = z
|
||||||
|
.enum(['PENDING', 'APPROVED', 'REJECTED'])
|
||||||
|
.optional()
|
||||||
|
.describe('审核状态,默认 PENDING');
|
||||||
|
|
||||||
|
export type WecomPluginMcpToolContext = {
|
||||||
|
query: WecomPluginQueryService;
|
||||||
|
plugin: WecomPluginRuntime;
|
||||||
|
wecomUserId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function jsonResult(run: () => Promise<unknown>): Promise<CallToolResult> {
|
||||||
|
try {
|
||||||
|
const data = await run();
|
||||||
|
return { content: [{ type: 'text', text: JSON.stringify(data) }] };
|
||||||
|
} catch (e) {
|
||||||
|
return { content: [{ type: 'text', text: wecomPluginMcpErrorText(e) }], isError: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolDef = {
|
||||||
|
name: WecomPluginMcpToolName;
|
||||||
|
description: string;
|
||||||
|
inputSchema: Record<string, z.ZodTypeAny>;
|
||||||
|
invoke: (ctx: WecomPluginMcpToolContext, args: Record<string, unknown>) => Promise<CallToolResult>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function str(args: Record<string, unknown>, key: string): string | undefined {
|
||||||
|
return wecomPluginQueryArg(args[key] as string | number | undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WECOM_PLUGIN_MCP_TOOL_DEFS: ToolDef[] = [
|
||||||
|
{
|
||||||
|
name: 'query_orders',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_orders.description,
|
||||||
|
inputSchema: { q: q('订单号,如 DK20260903xxxx'), page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() => ctx.query.queryOrders(ctx.plugin, ctx.wecomUserId, str(args, 'q'), str(args, 'page'), str(args, 'pageSize'))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_users',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_users.description,
|
||||||
|
inputSchema: { q: q('用户号或 11 位手机号'), page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() => ctx.query.queryUsers(ctx.plugin, ctx.wecomUserId, str(args, 'q'), str(args, 'page'), str(args, 'pageSize'))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_stores',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_stores.description,
|
||||||
|
inputSchema: { q: q('门店名称关键词'), page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() => ctx.query.queryStores(ctx.plugin, ctx.wecomUserId, str(args, 'q'), str(args, 'page'), str(args, 'pageSize'))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_redeems',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_redeems.description,
|
||||||
|
inputSchema: { q: q('核销单号或门店名'), page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() => ctx.query.queryRedeems(ctx.plugin, ctx.wecomUserId, str(args, 'q'), str(args, 'page'), str(args, 'pageSize'))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_promo_codes',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_promo_codes.description,
|
||||||
|
inputSchema: { q: q('推广码或名称'), page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() =>
|
||||||
|
ctx.query.queryPromoCodes(ctx.plugin, ctx.wecomUserId, str(args, 'q'), str(args, 'page'), str(args, 'pageSize')),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_promo_code_stats',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_promo_code_stats.description,
|
||||||
|
inputSchema: { code: q('推广码 code') },
|
||||||
|
invoke: (ctx, args) => jsonResult(() => ctx.query.queryPromoCodeStats(ctx.plugin, ctx.wecomUserId, str(args, 'code'))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_metrics',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_metrics.description,
|
||||||
|
inputSchema: {
|
||||||
|
kind: z.enum(['today', 'daily', 'weekly', 'monthly']).optional().describe('默认 today'),
|
||||||
|
},
|
||||||
|
invoke: (ctx, args) => jsonResult(() => ctx.query.queryMetrics(ctx.plugin, ctx.wecomUserId, str(args, 'kind'))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_store_audits',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_store_audits.description,
|
||||||
|
inputSchema: { q: z.string().optional().describe('门店名称'), status: auditStatus, page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() =>
|
||||||
|
ctx.query.queryStoreAudits(
|
||||||
|
ctx.plugin,
|
||||||
|
ctx.wecomUserId,
|
||||||
|
str(args, 'q'),
|
||||||
|
str(args, 'status'),
|
||||||
|
str(args, 'page'),
|
||||||
|
str(args, 'pageSize'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_store_info_audits',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_store_info_audits.description,
|
||||||
|
inputSchema: { status: auditStatus, page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() =>
|
||||||
|
ctx.query.queryStoreInfoAudits(ctx.plugin, ctx.wecomUserId, str(args, 'status'), str(args, 'page'), str(args, 'pageSize')),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_store_info_audit_detail',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_store_info_audit_detail.description,
|
||||||
|
inputSchema: { id: q('信息变更审核 ID') },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() => ctx.query.queryStoreInfoAuditDetail(ctx.plugin, ctx.wecomUserId, str(args, 'id') ?? '')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_store_package_audits',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_store_package_audits.description,
|
||||||
|
inputSchema: { status: auditStatus, page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() =>
|
||||||
|
ctx.query.queryStorePackageAudits(ctx.plugin, ctx.wecomUserId, str(args, 'status'), str(args, 'page'), str(args, 'pageSize')),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_store_package_audit_detail',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_store_package_audit_detail.description,
|
||||||
|
inputSchema: { id: q('套餐审核 ID') },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() => ctx.query.queryStorePackageAuditDetail(ctx.plugin, ctx.wecomUserId, str(args, 'id') ?? '')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_partners',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_partners.description,
|
||||||
|
inputSchema: { q: q('合伙人关键词'), page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() =>
|
||||||
|
ctx.query.queryPartners(ctx.plugin, ctx.wecomUserId, str(args, 'q'), str(args, 'page'), str(args, 'pageSize')),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_partner_users',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_partner_users.description,
|
||||||
|
inputSchema: { partnerId: q('合伙人 ID'), from, to, page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() =>
|
||||||
|
ctx.query.queryPartnerUsers(
|
||||||
|
ctx.plugin,
|
||||||
|
ctx.wecomUserId,
|
||||||
|
str(args, 'partnerId') ?? '',
|
||||||
|
str(args, 'from'),
|
||||||
|
str(args, 'to'),
|
||||||
|
str(args, 'page'),
|
||||||
|
str(args, 'pageSize'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_partner_stores',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_partner_stores.description,
|
||||||
|
inputSchema: { partnerId: q('合伙人 ID'), from, to, page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() =>
|
||||||
|
ctx.query.queryPartnerStores(
|
||||||
|
ctx.plugin,
|
||||||
|
ctx.wecomUserId,
|
||||||
|
str(args, 'partnerId') ?? '',
|
||||||
|
str(args, 'from'),
|
||||||
|
str(args, 'to'),
|
||||||
|
str(args, 'page'),
|
||||||
|
str(args, 'pageSize'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_partner_orders',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_partner_orders.description,
|
||||||
|
inputSchema: { partnerId: q('合伙人 ID'), from, to, page, pageSize },
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() =>
|
||||||
|
ctx.query.queryPartnerOrders(
|
||||||
|
ctx.plugin,
|
||||||
|
ctx.wecomUserId,
|
||||||
|
str(args, 'partnerId') ?? '',
|
||||||
|
str(args, 'from'),
|
||||||
|
str(args, 'to'),
|
||||||
|
str(args, 'page'),
|
||||||
|
str(args, 'pageSize'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_versions',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_versions.description,
|
||||||
|
inputSchema: {
|
||||||
|
q: z.string().optional().describe('版本号关键词,如 v3.5.18'),
|
||||||
|
status: z
|
||||||
|
.enum(['PENDING', 'IN_PROGRESS', 'TESTING', 'RELEASED', 'STOPPED'])
|
||||||
|
.optional()
|
||||||
|
.describe('版本状态:待启动/开发中/测试/已上线/已停止'),
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
},
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() =>
|
||||||
|
ctx.query.queryVersions(ctx.plugin, ctx.wecomUserId, str(args, 'q'), str(args, 'status'), str(args, 'page'), str(args, 'pageSize')),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'query_tasks',
|
||||||
|
description: WECOM_PLUGIN_MCP_TOOL_META.query_tasks.description,
|
||||||
|
inputSchema: {
|
||||||
|
status: z
|
||||||
|
.enum(['TODO', 'IN_PROGRESS', 'DEVELOPED', 'RELEASED', 'STOPPED'])
|
||||||
|
.optional()
|
||||||
|
.describe('任务状态:TODO 待开发、IN_PROGRESS 开发中、DEVELOPED 已开发、RELEASED 已上线、STOPPED 已停止'),
|
||||||
|
q: z.string().optional().describe('任务编号或内容关键词'),
|
||||||
|
versionNo: z.string().optional().describe('限定某版本号,如 v3.5.18'),
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
},
|
||||||
|
invoke: (ctx, args) =>
|
||||||
|
jsonResult(() =>
|
||||||
|
ctx.query.queryTasks(
|
||||||
|
ctx.plugin,
|
||||||
|
ctx.wecomUserId,
|
||||||
|
str(args, 'q'),
|
||||||
|
str(args, 'status'),
|
||||||
|
str(args, 'versionNo'),
|
||||||
|
str(args, 'page'),
|
||||||
|
str(args, 'pageSize'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
ForbiddenException,
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
clampWecomPluginPage,
|
clampWecomPluginPage,
|
||||||
clampWecomPluginPageSize,
|
clampWecomPluginPageSize,
|
||||||
@@ -13,14 +19,27 @@ import {
|
|||||||
type WecomReportStats,
|
type WecomReportStats,
|
||||||
} from '@dukang/domain';
|
} from '@dukang/domain';
|
||||||
import {
|
import {
|
||||||
|
WECOM_PLUGIN_MCP_PATH,
|
||||||
|
WECOM_PLUGIN_MCP_TRANSPORT,
|
||||||
WECOM_PLUGIN_TOOL_PATHS,
|
WECOM_PLUGIN_TOOL_PATHS,
|
||||||
|
allowedWecomPluginMcpTools,
|
||||||
wecomPluginHasPermission,
|
wecomPluginHasPermission,
|
||||||
STORE_INFO_CHANGEABLE_FIELD_LABELS,
|
STORE_INFO_CHANGEABLE_FIELD_LABELS,
|
||||||
|
DEV_PLAN_TASK_STATUSES,
|
||||||
|
DEV_PLAN_TASK_STATUS_LABELS,
|
||||||
|
DEV_PLAN_TASK_TYPE_LABELS,
|
||||||
|
DEV_PLAN_VERSION_STATUSES,
|
||||||
|
DEV_PLAN_VERSION_STATUS_LABELS,
|
||||||
type StoreInfoChangeableField,
|
type StoreInfoChangeableField,
|
||||||
type WecomPluginPermission,
|
type WecomPluginPermission,
|
||||||
|
type DevPlanTaskDto,
|
||||||
|
type DevPlanVersionDto,
|
||||||
|
type DevPlanTaskStatusDto,
|
||||||
|
type DevPlanVersionStatusDto,
|
||||||
} from '@dukang/shared-types';
|
} from '@dukang/shared-types';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||||
|
import { DevPlanService } from '../../modules/dev-plan/dev-plan.service';
|
||||||
import { PromoCodeService } from '../../modules/promo/promo-code.service';
|
import { PromoCodeService } from '../../modules/promo/promo-code.service';
|
||||||
import { WecomBotAuditService } from './wecom-bot-audit.service';
|
import { WecomBotAuditService } from './wecom-bot-audit.service';
|
||||||
import { wecomPluginAuditBot, type WecomPluginRuntime } from './wecom-plugin.types';
|
import { wecomPluginAuditBot, type WecomPluginRuntime } from './wecom-plugin.types';
|
||||||
@@ -38,21 +57,76 @@ function requireQuery(q?: string): string {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseDevPlanTaskStatus(raw?: string | null): DevPlanTaskStatusDto | null {
|
||||||
|
const v = String(raw ?? '').trim().toUpperCase();
|
||||||
|
if (!v) return null;
|
||||||
|
if (!(DEV_PLAN_TASK_STATUSES as readonly string[]).includes(v)) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`任务状态须为 ${DEV_PLAN_TASK_STATUSES.join(' | ')}(待开发/开发中/已开发/已上线/已停止)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return v as DevPlanTaskStatusDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDevPlanVersionStatus(raw?: string | null): DevPlanVersionStatusDto | null {
|
||||||
|
const v = String(raw ?? '').trim().toUpperCase();
|
||||||
|
if (!v) return null;
|
||||||
|
if (!(DEV_PLAN_VERSION_STATUSES as readonly string[]).includes(v)) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`版本状态须为 ${DEV_PLAN_VERSION_STATUSES.join(' | ')}(待启动/开发中/测试/已上线/已停止)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return v as DevPlanVersionStatusDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toWecomPluginTaskView(t: DevPlanTaskDto) {
|
||||||
|
return {
|
||||||
|
taskNo: t.taskNo,
|
||||||
|
content: t.content,
|
||||||
|
type: t.type,
|
||||||
|
typeLabel: DEV_PLAN_TASK_TYPE_LABELS[t.type] ?? t.type,
|
||||||
|
status: t.status,
|
||||||
|
statusLabel: DEV_PLAN_TASK_STATUS_LABELS[t.status] ?? t.status,
|
||||||
|
creatorName: t.creatorName?.trim() || '—',
|
||||||
|
supportTicketNo: t.supportTicketNo ?? null,
|
||||||
|
versions: (t.versions ?? []).map((v) => v.versionNo),
|
||||||
|
createdAt: t.createdAt,
|
||||||
|
completedAt: t.completedAt ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toWecomPluginVersionView(v: DevPlanVersionDto) {
|
||||||
|
return {
|
||||||
|
versionNo: v.versionNo,
|
||||||
|
content: v.content ?? null,
|
||||||
|
status: v.status,
|
||||||
|
statusLabel: DEV_PLAN_VERSION_STATUS_LABELS[v.status] ?? v.status,
|
||||||
|
createdAt: v.createdAt,
|
||||||
|
releasedAt: v.releasedAt ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WecomPluginQueryService {
|
export class WecomPluginQueryService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly promo: PromoCodeService,
|
private readonly promo: PromoCodeService,
|
||||||
private readonly audit: WecomBotAuditService,
|
private readonly audit: WecomBotAuditService,
|
||||||
|
@Inject(DevPlanService) private readonly devPlan: DevPlanService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
info(plugin: WecomPluginRuntime) {
|
info(plugin: WecomPluginRuntime) {
|
||||||
return {
|
return {
|
||||||
name: plugin.name,
|
name: plugin.name,
|
||||||
description: '企微智能机器人只读 API 插件。查询订单、用户、门店经营、核销、推广码、经营指标、审核对照与合伙人关联数据。',
|
description: '企微智能机器人只读插件。查询订单、用户、门店经营、核销、推广码、经营指标、审核对照、合伙人关联数据、开发计划版本与任务。',
|
||||||
auth: { header: 'X-Api-Key' },
|
auth: { header: 'X-Api-Key' },
|
||||||
permissions: plugin.permissions,
|
permissions: plugin.permissions,
|
||||||
tools: plugin.permissions.flatMap((p) => WECOM_PLUGIN_TOOL_PATHS[p]),
|
tools: plugin.permissions.flatMap((p) => WECOM_PLUGIN_TOOL_PATHS[p]),
|
||||||
|
mcp: {
|
||||||
|
path: WECOM_PLUGIN_MCP_PATH,
|
||||||
|
transport: WECOM_PLUGIN_MCP_TRANSPORT,
|
||||||
|
tools: allowedWecomPluginMcpTools(plugin.permissions),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -999,6 +1073,106 @@ export class WecomPluginQueryService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queryVersions(
|
||||||
|
plugin: WecomPluginRuntime,
|
||||||
|
wecomUserId: string,
|
||||||
|
q?: string,
|
||||||
|
status?: string,
|
||||||
|
page?: string,
|
||||||
|
pageSize?: string,
|
||||||
|
) {
|
||||||
|
this.requirePerm(plugin, 'dev_plan.read');
|
||||||
|
const versionStatus = parseDevPlanVersionStatus(status);
|
||||||
|
const take = clampWecomPluginPageSize(pageSize);
|
||||||
|
const pg = clampWecomPluginPage(page);
|
||||||
|
const keyword = String(q ?? '').trim();
|
||||||
|
return this.audit.run(
|
||||||
|
{
|
||||||
|
bot: wecomPluginAuditBot(plugin),
|
||||||
|
wecomUserId,
|
||||||
|
action: 'plugin.dev_plan.versions',
|
||||||
|
permission: 'dev_plan.read',
|
||||||
|
inputSummary: `${keyword || '*'} ${versionStatus || ''}`.trim(),
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
const data = (await this.devPlan.listVersions({
|
||||||
|
keyword: keyword || undefined,
|
||||||
|
status: versionStatus ?? undefined,
|
||||||
|
page: pg,
|
||||||
|
pageSize: take,
|
||||||
|
})) as { items: DevPlanVersionDto[]; total: number };
|
||||||
|
return {
|
||||||
|
total: data.total,
|
||||||
|
items: data.items.map(toWecomPluginVersionView),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryTasks(
|
||||||
|
plugin: WecomPluginRuntime,
|
||||||
|
wecomUserId: string,
|
||||||
|
q?: string,
|
||||||
|
status?: string,
|
||||||
|
versionNo?: string,
|
||||||
|
page?: string,
|
||||||
|
pageSize?: string,
|
||||||
|
) {
|
||||||
|
this.requirePerm(plugin, 'dev_plan.read');
|
||||||
|
const taskStatus = parseDevPlanTaskStatus(status);
|
||||||
|
const take = clampWecomPluginPageSize(pageSize);
|
||||||
|
const pg = clampWecomPluginPage(page);
|
||||||
|
const keyword = String(q ?? '').trim();
|
||||||
|
const ver = String(versionNo ?? '').trim();
|
||||||
|
return this.audit.run(
|
||||||
|
{
|
||||||
|
bot: wecomPluginAuditBot(plugin),
|
||||||
|
wecomUserId,
|
||||||
|
action: 'plugin.dev_plan.tasks',
|
||||||
|
permission: 'dev_plan.read',
|
||||||
|
inputSummary: `${ver || keyword || '*'} ${taskStatus || ''}`.trim(),
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
if (ver) {
|
||||||
|
const listed = (await this.devPlan.listVersions({
|
||||||
|
keyword: ver,
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
})) as { items: DevPlanVersionDto[] };
|
||||||
|
const match = listed.items.find((v) => v.versionNo.toLowerCase() === ver.toLowerCase());
|
||||||
|
if (!match) throw new NotFoundException(`未找到版本:${ver}`);
|
||||||
|
const detail = await this.devPlan.getVersion(BigInt(match.id));
|
||||||
|
let tasks = (detail.tasks ?? []).map(toWecomPluginTaskView);
|
||||||
|
if (taskStatus) tasks = tasks.filter((t) => t.status === taskStatus);
|
||||||
|
if (keyword) {
|
||||||
|
const kw = keyword.toLowerCase();
|
||||||
|
tasks = tasks.filter(
|
||||||
|
(t) => t.taskNo.toLowerCase().includes(kw) || t.content.toLowerCase().includes(kw),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const total = tasks.length;
|
||||||
|
const start = (pg - 1) * take;
|
||||||
|
return {
|
||||||
|
versionNo: match.versionNo,
|
||||||
|
versionStatus: match.status,
|
||||||
|
total,
|
||||||
|
items: tasks.slice(start, start + take),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const data = (await this.devPlan.listTasks({
|
||||||
|
status: taskStatus ?? undefined,
|
||||||
|
keyword: keyword || undefined,
|
||||||
|
page: pg,
|
||||||
|
pageSize: take,
|
||||||
|
})) as { items: DevPlanTaskDto[]; total: number };
|
||||||
|
return {
|
||||||
|
total: data.total,
|
||||||
|
items: data.items.map(toWecomPluginTaskView),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private mapStoreInfoAuditRow(
|
private mapStoreInfoAuditRow(
|
||||||
row: {
|
row: {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { WecomPluginGuard } from './wecom-plugin.guard';
|
|||||||
import { WecomPluginQueryService } from './wecom-plugin-query.service';
|
import { WecomPluginQueryService } from './wecom-plugin-query.service';
|
||||||
import { CurrentWecomPlugin } from './wecom-plugin.decorators';
|
import { CurrentWecomPlugin } from './wecom-plugin.decorators';
|
||||||
import type { WecomPluginRuntime } from './wecom-plugin.types';
|
import type { WecomPluginRuntime } from './wecom-plugin.types';
|
||||||
|
import { wecomPluginCaller } from './wecom-plugin.util';
|
||||||
|
|
||||||
@Controller('wecom/plugin')
|
@Controller('wecom/plugin')
|
||||||
@UseGuards(WecomPluginGuard)
|
@UseGuards(WecomPluginGuard)
|
||||||
@@ -28,7 +29,7 @@ export class WecomPluginController {
|
|||||||
@Query('page') page?: string,
|
@Query('page') page?: string,
|
||||||
@Query('pageSize') pageSize?: string,
|
@Query('pageSize') pageSize?: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryOrders(plugin, pluginCaller(req), q, page, pageSize);
|
return this.query.queryOrders(plugin, wecomPluginCaller(req), q, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('users')
|
@Get('users')
|
||||||
@@ -39,7 +40,7 @@ export class WecomPluginController {
|
|||||||
@Query('page') page?: string,
|
@Query('page') page?: string,
|
||||||
@Query('pageSize') pageSize?: string,
|
@Query('pageSize') pageSize?: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryUsers(plugin, pluginCaller(req), q, page, pageSize);
|
return this.query.queryUsers(plugin, wecomPluginCaller(req), q, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('stores')
|
@Get('stores')
|
||||||
@@ -50,7 +51,7 @@ export class WecomPluginController {
|
|||||||
@Query('page') page?: string,
|
@Query('page') page?: string,
|
||||||
@Query('pageSize') pageSize?: string,
|
@Query('pageSize') pageSize?: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryStores(plugin, pluginCaller(req), q, page, pageSize);
|
return this.query.queryStores(plugin, wecomPluginCaller(req), q, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('store-audits')
|
@Get('store-audits')
|
||||||
@@ -62,7 +63,7 @@ export class WecomPluginController {
|
|||||||
@Query('page') page?: string,
|
@Query('page') page?: string,
|
||||||
@Query('pageSize') pageSize?: string,
|
@Query('pageSize') pageSize?: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryStoreAudits(plugin, pluginCaller(req), q, status, page, pageSize);
|
return this.query.queryStoreAudits(plugin, wecomPluginCaller(req), q, status, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('store-info-audits')
|
@Get('store-info-audits')
|
||||||
@@ -73,7 +74,7 @@ export class WecomPluginController {
|
|||||||
@Query('page') page?: string,
|
@Query('page') page?: string,
|
||||||
@Query('pageSize') pageSize?: string,
|
@Query('pageSize') pageSize?: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryStoreInfoAudits(plugin, pluginCaller(req), status, page, pageSize);
|
return this.query.queryStoreInfoAudits(plugin, wecomPluginCaller(req), status, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('store-info-audits/:id')
|
@Get('store-info-audits/:id')
|
||||||
@@ -82,7 +83,7 @@ export class WecomPluginController {
|
|||||||
@Req() req: Request,
|
@Req() req: Request,
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryStoreInfoAuditDetail(plugin, pluginCaller(req), id);
|
return this.query.queryStoreInfoAuditDetail(plugin, wecomPluginCaller(req), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('store-package-audits')
|
@Get('store-package-audits')
|
||||||
@@ -93,7 +94,7 @@ export class WecomPluginController {
|
|||||||
@Query('page') page?: string,
|
@Query('page') page?: string,
|
||||||
@Query('pageSize') pageSize?: string,
|
@Query('pageSize') pageSize?: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryStorePackageAudits(plugin, pluginCaller(req), status, page, pageSize);
|
return this.query.queryStorePackageAudits(plugin, wecomPluginCaller(req), status, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('store-package-audits/:id')
|
@Get('store-package-audits/:id')
|
||||||
@@ -102,7 +103,7 @@ export class WecomPluginController {
|
|||||||
@Req() req: Request,
|
@Req() req: Request,
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryStorePackageAuditDetail(plugin, pluginCaller(req), id);
|
return this.query.queryStorePackageAuditDetail(plugin, wecomPluginCaller(req), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('redeems')
|
@Get('redeems')
|
||||||
@@ -113,7 +114,7 @@ export class WecomPluginController {
|
|||||||
@Query('page') page?: string,
|
@Query('page') page?: string,
|
||||||
@Query('pageSize') pageSize?: string,
|
@Query('pageSize') pageSize?: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryRedeems(plugin, pluginCaller(req), q, page, pageSize);
|
return this.query.queryRedeems(plugin, wecomPluginCaller(req), q, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('promo-codes')
|
@Get('promo-codes')
|
||||||
@@ -124,7 +125,7 @@ export class WecomPluginController {
|
|||||||
@Query('page') page?: string,
|
@Query('page') page?: string,
|
||||||
@Query('pageSize') pageSize?: string,
|
@Query('pageSize') pageSize?: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryPromoCodes(plugin, pluginCaller(req), q, page, pageSize);
|
return this.query.queryPromoCodes(plugin, wecomPluginCaller(req), q, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('promo-codes/:code/stats')
|
@Get('promo-codes/:code/stats')
|
||||||
@@ -133,7 +134,7 @@ export class WecomPluginController {
|
|||||||
@Req() req: Request,
|
@Req() req: Request,
|
||||||
@Param('code') code: string,
|
@Param('code') code: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryPromoCodeStats(plugin, pluginCaller(req), code);
|
return this.query.queryPromoCodeStats(plugin, wecomPluginCaller(req), code);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('partners')
|
@Get('partners')
|
||||||
@@ -144,7 +145,7 @@ export class WecomPluginController {
|
|||||||
@Query('page') page?: string,
|
@Query('page') page?: string,
|
||||||
@Query('pageSize') pageSize?: string,
|
@Query('pageSize') pageSize?: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryPartners(plugin, pluginCaller(req), q, page, pageSize);
|
return this.query.queryPartners(plugin, wecomPluginCaller(req), q, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('partners/:partnerId/users')
|
@Get('partners/:partnerId/users')
|
||||||
@@ -159,7 +160,7 @@ export class WecomPluginController {
|
|||||||
) {
|
) {
|
||||||
return this.query.queryPartnerUsers(
|
return this.query.queryPartnerUsers(
|
||||||
plugin,
|
plugin,
|
||||||
pluginCaller(req),
|
wecomPluginCaller(req),
|
||||||
partnerId,
|
partnerId,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
@@ -180,7 +181,7 @@ export class WecomPluginController {
|
|||||||
) {
|
) {
|
||||||
return this.query.queryPartnerStores(
|
return this.query.queryPartnerStores(
|
||||||
plugin,
|
plugin,
|
||||||
pluginCaller(req),
|
wecomPluginCaller(req),
|
||||||
partnerId,
|
partnerId,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
@@ -201,7 +202,7 @@ export class WecomPluginController {
|
|||||||
) {
|
) {
|
||||||
return this.query.queryPartnerOrders(
|
return this.query.queryPartnerOrders(
|
||||||
plugin,
|
plugin,
|
||||||
pluginCaller(req),
|
wecomPluginCaller(req),
|
||||||
partnerId,
|
partnerId,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
@@ -216,12 +217,31 @@ export class WecomPluginController {
|
|||||||
@Req() req: Request,
|
@Req() req: Request,
|
||||||
@Query('kind') kind?: string,
|
@Query('kind') kind?: string,
|
||||||
) {
|
) {
|
||||||
return this.query.queryMetrics(plugin, pluginCaller(req), kind);
|
return this.query.queryMetrics(plugin, wecomPluginCaller(req), kind);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pluginCaller(req: Request): string {
|
@Get('versions')
|
||||||
const raw = req.headers['x-wecom-userid'] ?? req.headers['userid'];
|
versions(
|
||||||
const v = Array.isArray(raw) ? raw[0] : raw;
|
@CurrentWecomPlugin() plugin: WecomPluginRuntime,
|
||||||
return String(v || 'plugin').trim() || 'plugin';
|
@Req() req: Request,
|
||||||
|
@Query('q') q?: string,
|
||||||
|
@Query('status') status?: string,
|
||||||
|
@Query('page') page?: string,
|
||||||
|
@Query('pageSize') pageSize?: string,
|
||||||
|
) {
|
||||||
|
return this.query.queryVersions(plugin, wecomPluginCaller(req), q, status, page, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('tasks')
|
||||||
|
tasks(
|
||||||
|
@CurrentWecomPlugin() plugin: WecomPluginRuntime,
|
||||||
|
@Req() req: Request,
|
||||||
|
@Query('q') q?: string,
|
||||||
|
@Query('status') status?: string,
|
||||||
|
@Query('versionNo') versionNo?: string,
|
||||||
|
@Query('page') page?: string,
|
||||||
|
@Query('pageSize') pageSize?: string,
|
||||||
|
) {
|
||||||
|
return this.query.queryTasks(plugin, wecomPluginCaller(req), q, status, versionNo, page, pageSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,5 +328,52 @@ export const WECOM_PLUGIN_OPENAPI = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'/versions': {
|
||||||
|
get: {
|
||||||
|
summary: '查询开发版本',
|
||||||
|
description: '按版本号或说明模糊查询;status 为版本状态',
|
||||||
|
operationId: '查询开发版本',
|
||||||
|
parameters: [
|
||||||
|
{ name: 'q', in: 'query', required: false, schema: { type: 'string' }, description: '版本号关键词,如 v3.5.18' },
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
in: 'query',
|
||||||
|
required: false,
|
||||||
|
schema: { type: 'string', enum: ['PENDING', 'IN_PROGRESS', 'TESTING', 'RELEASED', 'STOPPED'] },
|
||||||
|
},
|
||||||
|
...pageParams,
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
200: { description: '版本列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
|
||||||
|
401: unauthorized,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/tasks': {
|
||||||
|
get: {
|
||||||
|
summary: '查询开发任务',
|
||||||
|
description:
|
||||||
|
'按任务状态筛选。status:TODO 待开发 / IN_PROGRESS 开发中 / DEVELOPED 已开发 / RELEASED 已上线 / STOPPED 已停止。可选 q、versionNo。',
|
||||||
|
operationId: '查询开发任务',
|
||||||
|
parameters: [
|
||||||
|
{ name: 'q', in: 'query', required: false, schema: { type: 'string' }, description: '任务编号或内容' },
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
in: 'query',
|
||||||
|
required: false,
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['TODO', 'IN_PROGRESS', 'DEVELOPED', 'RELEASED', 'STOPPED'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ name: 'versionNo', in: 'query', required: false, schema: { type: 'string' }, description: '限定某版本号' },
|
||||||
|
...pageParams,
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
200: { description: '任务列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
|
||||||
|
401: unauthorized,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { wecomPluginCaller, wecomPluginMcpErrorText, wecomPluginQueryArg } from './wecom-plugin.util';
|
||||||
|
|
||||||
|
describe('wecomPluginCaller', () => {
|
||||||
|
it('prefers X-WeCom-User-Id', () => {
|
||||||
|
expect(wecomPluginCaller({ headers: { 'x-wecom-userid': 'alice', userid: 'bob' } })).toBe('alice');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to plugin', () => {
|
||||||
|
expect(wecomPluginCaller({ headers: {} })).toBe('plugin');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('wecomPluginMcpErrorText', () => {
|
||||||
|
it('unwraps Nest HttpException message', () => {
|
||||||
|
expect(wecomPluginMcpErrorText(new BadRequestException('请提供查询关键词 q'))).toBe('请提供查询关键词 q');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('wecomPluginQueryArg', () => {
|
||||||
|
it('stringifies numbers and drops empty', () => {
|
||||||
|
expect(wecomPluginQueryArg(2)).toBe('2');
|
||||||
|
expect(wecomPluginQueryArg(' ')).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { HttpException } from '@nestjs/common';
|
||||||
|
|
||||||
|
export function wecomPluginCaller(req: { headers: Record<string, unknown> }): string {
|
||||||
|
const raw = req.headers['x-wecom-userid'] ?? req.headers['userid'];
|
||||||
|
const v = Array.isArray(raw) ? raw[0] : raw;
|
||||||
|
return String(v || 'plugin').trim() || 'plugin';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wecomPluginMcpErrorText(e: unknown): string {
|
||||||
|
if (e instanceof HttpException) {
|
||||||
|
const res = e.getResponse();
|
||||||
|
if (typeof res === 'string') return res;
|
||||||
|
if (res && typeof res === 'object' && 'message' in res) {
|
||||||
|
const message = (res as { message?: string | string[] }).message;
|
||||||
|
return Array.isArray(message) ? message.join(', ') : String(message ?? e.message);
|
||||||
|
}
|
||||||
|
return e.message;
|
||||||
|
}
|
||||||
|
return e instanceof Error ? e.message : String(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wecomPluginQueryArg(v?: string | number): string | undefined {
|
||||||
|
if (v == null) return undefined;
|
||||||
|
const s = String(v).trim();
|
||||||
|
return s ? s : undefined;
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ import { WecomBotSessionService } from './wecom-bot-session.service';
|
|||||||
import { WecomPluginAuthService } from './wecom-plugin-auth.service';
|
import { WecomPluginAuthService } from './wecom-plugin-auth.service';
|
||||||
import { WecomPluginController } from './wecom-plugin.controller';
|
import { WecomPluginController } from './wecom-plugin.controller';
|
||||||
import { WecomPluginGuard } from './wecom-plugin.guard';
|
import { WecomPluginGuard } from './wecom-plugin.guard';
|
||||||
|
import { WecomPluginMcpController } from './wecom-plugin-mcp.controller';
|
||||||
|
import { WecomPluginMcpFactory } from './wecom-plugin-mcp.factory';
|
||||||
import { WecomPluginQueryService } from './wecom-plugin-query.service';
|
import { WecomPluginQueryService } from './wecom-plugin-query.service';
|
||||||
|
|
||||||
/** 企微多机器人:依赖 Common(工单)+ Settlement/DevPlan + Promo + Integrations(短信)+ Llm */
|
/** 企微多机器人:依赖 Common(工单)+ Settlement/DevPlan + Promo + Integrations(短信)+ Llm */
|
||||||
@@ -26,7 +28,7 @@ import { WecomPluginQueryService } from './wecom-plugin-query.service';
|
|||||||
PromoModule,
|
PromoModule,
|
||||||
LlmModule,
|
LlmModule,
|
||||||
],
|
],
|
||||||
controllers: [WecomPluginController],
|
controllers: [WecomPluginController, WecomPluginMcpController],
|
||||||
providers: [
|
providers: [
|
||||||
WecomBotSessionService,
|
WecomBotSessionService,
|
||||||
WecomBotAuditService,
|
WecomBotAuditService,
|
||||||
@@ -37,6 +39,7 @@ import { WecomPluginQueryService } from './wecom-plugin-query.service';
|
|||||||
WecomPluginAuthService,
|
WecomPluginAuthService,
|
||||||
WecomPluginGuard,
|
WecomPluginGuard,
|
||||||
WecomPluginQueryService,
|
WecomPluginQueryService,
|
||||||
|
WecomPluginMcpFactory,
|
||||||
],
|
],
|
||||||
exports: [WecomAibotService, WecomBotAuditService],
|
exports: [WecomAibotService, WecomBotAuditService],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user