Commit 97d06d4c by luoqi

feat(wecom): 企微智能机器人长连接接入 PAC 助手

把 PAC 助手接到企业微信「智能机器人 · API 模式 · 长连接」(@wecom/aibot-node-sdk):
- 被动应答:群里 @机器人 → 收 message.text → 映射客服 token → 复用 AssistantService.chat
  (同一个助手大脑,自主调 MCP 工具)→ replyStream 流式回推(全量替换 + finish 收尾)。
- 主动推送:sendMessage 推「紧急召回」按钮卡片到群(带召回卡深链)。
- 按钮回写:模板卡片按钮点击 → 回写 plan_executions + 驱动状态机(召回闭环在企微内完成)。

开关:仅配置 PAC_WX_AIBOT_BOT_ID/SECRET 时启动长连接(其它环境静默跳过)。
身份映射 PoC:企微 userid 统一映射到 host=jvs-dw / 默认 tenant / staff;生产再换真实对照。
注:同一 bot 仅维持一条有效长连接(新连接踢旧),一个环境一个 bot;
    卡片就地更新(updateTemplateCard)有企微 40058 待打磨,不影响回写。
demo 主动推送端点 @Public 但默认关(PAC_WX_AIBOT_DEMO_PUSH=1 才放行),生产勿开。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent f2aca14a
......@@ -165,3 +165,18 @@ ALERT_WEBHOOK_URL=
# staging: true (演示 / 客服试用必须开)
# production: false ⭐ (生产关掉,走 host SSO)
PAC_ENABLE_MOCK_LOGIN=
# ─── 企业微信智能机器人(长连接 / API 模式)──────────────────────────
# 企微后台「创建智能机器人 → API 模式 → 使用长连接」拿 Bot ID + Secret。
# 不填则不启动长连接(其它环境无副作用)。本地填了即自动连。
PAC_WX_AIBOT_BOT_ID=
PAC_WX_AIBOT_SECRET=
# 身份映射(PoC):企微用户统一映射到此 host/tenant 的客服 scope。
# 生产替换为「企微 userid ↔ 真实客服(tenant/clinic/role)」对照。
PAC_WX_AIBOT_HOST=jvs-dw
PAC_WX_AIBOT_TENANT_ID=
# 召回卡深链前缀(主动推送里的链接);线上填真实 web 域名。
PAC_WEB_BASE_URL=http://localhost:3100
# [DEV] 开启「主动推送」演示端点 POST /pac/v1/internal/wx-aibot/push-urgent(=1 放行)。
# ⚠️ 生产请关闭/移除,真实触发走定时扫描或事件驱动,不暴露裸 HTTP 端点。
PAC_WX_AIBOT_DEMO_PUSH=
......@@ -67,6 +67,7 @@
"@pac/utils": "workspace:*",
"@prisma/client": "^6.19.2",
"@scalar/nestjs-api-reference": "^1.1.14",
"@wecom/aibot-node-sdk": "^1.0.7",
"ai": "^6.0.184",
"bullmq": "^5.76.10",
"class-transformer": "^0.5.1",
......
......@@ -19,6 +19,7 @@ import { RealtimeCoachModule } from './modules/realtime-coach/realtime-coach.mod
import { AdminModule } from './modules/admin/admin.module';
import { McpModule } from './modules/mcp/mcp.module';
import { AssistantModule } from './modules/assistant/assistant.module';
import { WeixinAibotModule } from './modules/weixin-aibot/weixin-aibot.module';
import { QueuesModule } from './queues/queues.module';
import { QueuesBullBoardModule } from './queues/bull-board.module';
import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
......@@ -51,6 +52,7 @@ import { HealthController } from './health.controller';
RealtimeCoachModule,
McpModule,
AssistantModule,
WeixinAibotModule,
AdminModule,
PlanAggregateModule,
],
......
......@@ -16,5 +16,6 @@ import { DictationGateway } from './dictation.gateway';
imports: [AiModule, AuthModule],
controllers: [AssistantController],
providers: [AssistantService, McpClientService, TranscribeService, DictationGateway],
exports: [AssistantService], // 供 WeixinAibotModule 等其它入口复用同一个助手大脑
})
export class AssistantModule {}
import { Body, Controller, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Public } from '../../common/decorators/public.decorator';
import { WeixinAibotService } from './weixin-aibot.service';
/**
* WeixinAibotController — 企微机器人主动推送的「演示触发」端点。
*
* ⚠️ DEV/DEMO ONLY:@Public 免鉴权,仅 env `PAC_WX_AIBOT_DEMO_PUSH=1` 时放行。
* 生产环境**不该**用裸 HTTP 触发群推送 —— 真实触发应是「定时扫描新出现的紧急缺口」
* 或「事件驱动(新高优 plan 落库)」,而非外部可调端点。上线前移除或换成内部鉴权。
*/
@ApiTags('internal')
@Controller('internal/wx-aibot')
export class WeixinAibotController {
constructor(private readonly svc: WeixinAibotService) {}
@Public()
@Post('push-urgent')
@ApiOperation({ summary: '[DEV] 主动推一条紧急召回提醒到群(需 PAC_WX_AIBOT_DEMO_PUSH=1)' })
async pushUrgent(@Body() body: { chatid?: string } | undefined): Promise<unknown> {
if (process.env.PAC_WX_AIBOT_DEMO_PUSH !== '1') {
return { ok: false, msg: 'demo push 未启用(设 PAC_WX_AIBOT_DEMO_PUSH=1 后重启)' };
}
return this.svc.pushUrgentAlert(body?.chatid);
}
}
import { Module } from '@nestjs/common';
import { AuthModule } from '../auth/auth.module';
import { AssistantModule } from '../assistant/assistant.module';
import { PlanModule } from '../plan/plan.module';
import { WeixinAibotController } from './weixin-aibot.controller';
import { WeixinAibotService } from './weixin-aibot.service';
/**
* WeixinAibotModule — 企业微信智能机器人长连接入口。
*
* 复用 AuthService(签客服 token)+ AssistantService(助手大脑);PrismaService 全局可注入。
* 仅在配置了 Bot ID/Secret 时实际启动长连接(见 service.onModuleInit)。
*/
@Module({
imports: [AuthModule, AssistantModule, PlanModule],
controllers: [WeixinAibotController],
providers: [WeixinAibotService],
})
export class WeixinAibotModule {}
import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import AiBot, { generateReqId } from '@wecom/aibot-node-sdk';
import type { WsFrame, TemplateCard } from '@wecom/aibot-node-sdk';
import type { UserRole } from '@pac/types';
import { calcAge, maskName } from '@pac/utils';
import { AuthService } from '../auth/auth.service';
import { AssistantService } from '../assistant/assistant.service';
import { ExecutionService } from '../plan/execution.service';
import { PrismaService } from '../../prisma/prisma.service';
import type { TenantScopeContext } from '../../common/decorators/tenant-scope.decorator';
/** 卡片按钮 outcome → 中文(更新卡片展示用)。 */
const OUTCOME_ZH: Record<string, string> = {
success_appointed: '已约到诊',
scheduled_next: '已跟进 / 约下次',
no_answer: '未接通',
};
/**
* WeixinAibotService — 把 PAC 助手接到企业微信「智能机器人 · API 模式 · 长连接」。
*
* 链路:企微(客服在聊天里发问)→ WSClient 长连接收到 message.text
* → 映射 企微 userid 成 PAC 客服 token(tenant scope)
* → 复用 AssistantService.chat(同一个助手大脑,自主调 MCP 工具)
* → 把 streamText 的文本增量用 replyStream 回推(全量替换语义,finish=true 收尾)。
*
* 开关:仅当配置了 PAC_WX_AIBOT_BOT_ID + PAC_WX_AIBOT_SECRET 才启动(其它环境静默跳过)。
*
* ⚠️ 本版为本地 PoC:
* - 身份映射是「所有企微用户 → 同一个默认 tenant scope」(jvs-dw / 默认租户 / staff)。
* 生产需替换为「企微 userid ↔ 真实客服(tenant/clinic/role)」的对照(配置或库)。
* - 企微聊天是文字/Markdown,渲染不了 render_artifact 的 HTML 卡片;PoC 直接吐文字。
* 产品化时给企微通道一个不带 render_artifact 的精简工具集 + 文字优先提示词。
*/
@Injectable()
export class WeixinAibotService implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(WeixinAibotService.name);
private client?: InstanceType<typeof AiBot.WSClient>;
private cachedHostId?: string;
/** 最近活跃的群 chatid(主动推送缺省目标;群里 @一次即记住)。 */
private lastChatId?: string;
/** 召回卡深链前缀(本地默认 localhost:3100;线上配真实域名)。 */
private readonly webBase = process.env.PAC_WEB_BASE_URL?.trim() || 'http://localhost:3100';
// ── 配置 ──
private readonly botId = process.env.PAC_WX_AIBOT_BOT_ID?.trim();
private readonly secret = process.env.PAC_WX_AIBOT_SECRET?.trim();
private readonly hostName = process.env.PAC_WX_AIBOT_HOST?.trim() || 'jvs-dw';
// PoC 默认租户 = 瑞尔(ruier);可用 env 覆盖。生产从 企微userid→客服 映射来。
private readonly tenantId =
process.env.PAC_WX_AIBOT_TENANT_ID?.trim() || 'ba67e6cf30dc4f9c9c46adef188bbd04';
constructor(
private readonly auth: AuthService,
private readonly assistant: AssistantService,
private readonly executions: ExecutionService,
private readonly prisma: PrismaService,
) {}
onModuleInit(): void {
if (!this.botId || !this.secret) {
this.logger.log('未配置 PAC_WX_AIBOT_BOT_ID / SECRET,企微机器人长连接跳过。');
return;
}
const client = new AiBot.WSClient({ botId: this.botId, secret: this.secret });
this.client = client;
client.on('authenticated', () => this.logger.log('企微智能机器人长连接认证成功 🔐'));
client.on('error', (e: unknown) =>
this.logger.error(`企微长连接错误: ${e instanceof Error ? e.message : String(e)}`),
);
client.on('event.enter_chat', (frame: WsFrame) => {
void client.replyWelcome(frame, {
msgtype: 'text',
text: {
content:
'你好,我是 FRIDAY 助手。可以问我:"今日推荐先打谁""某患者为什么被召回""帮我圈重要价值且有种植机会的患者"。',
},
});
});
client.on('message.text', (frame: WsFrame) => {
void this.handleText(frame);
});
// 模板卡片按钮点击 → 回写 plan_executions(召回闭环在企微里完成)
client.on('event.template_card_event', (frame: WsFrame) => {
void this.handleCardEvent(frame);
});
client.connect();
this.logger.log(`企微智能机器人长连接已启动(host=${this.hostName} tenant=${this.tenantId})`);
}
onModuleDestroy(): void {
this.client?.disconnect();
}
/** 收到文本 → 跑助手 → 流式回推企微。 */
private async handleText(frame: WsFrame): Promise<void> {
const client = this.client;
if (!client) return;
const content = frame.body.text?.content?.trim();
const wxUserid = frame.body.from?.userid ?? 'unknown';
const streamId = generateReqId('stream');
if (frame.body.chatid) this.lastChatId = frame.body.chatid; // 记住群,供主动推送
if (!content) return;
const ac = new AbortController();
const timeout = setTimeout(() => ac.abort(), 90_000); // 安全兜底:90s 未完成则中断
try {
await client.replyStream(frame, streamId, '🔍 正在查…', false);
const token = await this.mintToken(wxUserid);
const result = await this.assistant.chat({
userToken: token,
messages: [{ role: 'user', content }],
abortSignal: ac.signal,
});
let full = '';
let lastPush = 0;
let lastPushed = '';
const flush = async (finish: boolean): Promise<void> => {
const text = full.trim() || (finish ? '(无内容)' : '🔍 正在查…');
if (!finish && text === lastPushed) return;
lastPushed = text;
// 企微 replyStream 为「全量替换」语义:每次发当前累计全文,finish=true 收尾。
await client.replyStream(frame, streamId, text, finish);
};
for await (const part of result.fullStream) {
const p = part as Record<string, unknown> & { type: string };
if (p.type === 'text-delta' || p.type === 'text') {
full += (p.text as string) ?? (p.delta as string) ?? '';
const now = Date.now();
if (now - lastPush > 800) {
lastPush = now;
await flush(false);
}
}
// 工具调用/结果在企微通道不单独展示(文字答案已含结论);render_artifact 的 HTML 忽略。
}
await flush(true);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
this.logger.error(`企微消息处理失败(user=${wxUserid}): ${msg}`);
try {
await client.replyStream(frame, streamId, `抱歉,出错了:${msg}`, true);
} catch {
/* 回推失败忽略 */
}
} finally {
clearTimeout(timeout);
}
}
/**
* 主动推一条「紧急召回」提醒到群(带召回卡深链)。
* PoC:取召回池当前优先级最高的一条作示例;生产由事件/定时扫描"新出现的紧急缺口"触发。
* chatid 缺省用最近活跃群(群里 @过一次即可)。
*/
async pushUrgentAlert(chatid?: string): Promise<{ sent: boolean; reason?: string }> {
const client = this.client;
if (!client) return { sent: false, reason: '长连接未启动' };
const target = chatid || this.lastChatId;
if (!target) return { sent: false, reason: '无可用 chatid(先在群里 @一次机器人)' };
const hostId = await this.resolveHostId();
const plan = await this.prisma.followupPlan.findFirst({
where: {
hostId,
tenantId: this.tenantId,
status: 'active',
assigneeUserId: null,
supersededAt: null,
},
orderBy: { priorityScore: 'desc' },
include: { reasons: { orderBy: { priorityScore: 'desc' }, take: 1 } },
});
if (!plan) return { sent: false, reason: '召回池为空' };
const patient = await this.prisma.patient.findUnique({
where: { id: plan.patientId },
select: { name: true, gender: true, birthDate: true },
});
const name = maskName(patient?.name ?? null) ?? '患者';
const age = patient?.birthDate ? calcAge(patient.birthDate) : null;
const genderZh = patient?.gender === 'male' ? '男' : patient?.gender === 'female' ? '女' : '';
const who = age != null ? `${name}(${age}${genderZh})` : name;
const reason = (plan.reasons[0]?.reason ?? '有应治未治缺口,建议尽快联系').slice(0, 110);
const link = `${this.webBase}/plans/${plan.id}`;
const score = Math.round(plan.priorityScore);
// 按钮交互卡片:点整卡 → 跳 web 召回卡;点按钮 → 回写 plan_executions(见 handleCardEvent)。
const card: TemplateCard = {
card_type: 'button_interaction',
source: { desc: 'PAC 召回 · 紧急', desc_color: 2 },
main_title: { title: `⚠️ 紧急召回 · ${who}`, desc: `优先级 ${score}` },
sub_title_text: reason,
card_action: { type: 1, url: link },
button_list: [
{ text: '已约到诊', style: 1, key: `exec:success_appointed:${plan.id}` },
{ text: '已跟进', style: 1, key: `exec:scheduled_next:${plan.id}` },
{ text: '未接通', style: 2, key: `exec:no_answer:${plan.id}` },
],
// task_id 同一机器人不可重复(企微 42014)→ 加时间戳后缀;字符限 数字/字母/_-@
task_id: `u_${plan.id.replace(/-/g, '')}_${Date.now().toString(36)}`,
};
await client.sendMessage(target, { msgtype: 'template_card', template_card: card });
this.logger.log(`已推紧急卡片到群 ${target}: ${who}(优先级 ${score})`);
return { sent: true };
}
/** 模板卡片按钮点击 → 回写 plan_executions + 更新卡片显示结果(模拟客服在企微里闭环)。 */
private async handleCardEvent(frame: WsFrame): Promise<void> {
const client = this.client;
if (!client) return;
// ⚠️ 真实报文 event_key 嵌在 event.template_card_event 下(SDK d.ts 标的 event.event_key 不准),两路兜底。
const ev = frame.body.event;
const eventKey: string | undefined = ev?.event_key ?? ev?.template_card_event?.event_key;
const clicker: string = frame.body.from?.userid ?? 'unknown';
if (!eventKey?.startsWith('exec:')) return;
const [, outcome, planId] = eventKey.split(':');
if (!outcome || !planId) return;
const link = `${this.webBase}/plans/${planId}`;
const errStr = (e: unknown): string =>
e instanceof Error ? e.message : (() => { try { return JSON.stringify(e); } catch { return String(e); } })();
// ① 回写(核心)——失败即返回
let planStatus = '';
let attempts = 0;
try {
const scope = await this.defaultScope(clicker);
const res = await this.executions.submit(scope, planId, `wx:${clicker}`, {
channel: 'wecom',
outcome,
notes: '企微卡片按钮回写(PoC)',
});
planStatus = res.planStatus;
attempts = res.contactAttempts;
this.logger.log(
`卡片回写 plan=${planId} outcome=${outcome} → status=${planStatus} by wx:${clicker}`,
);
} catch (err) {
this.logger.warn(`回写失败 plan=${planId}: ${errStr(err)}`);
return;
}
// ② 更新卡片(仅展示,失败不影响已成功的回写)。
// 企微对 button_interaction 卡片的按钮更新需保持**同类型**(text_notice 会 40058),
// 故更新为同类 button_interaction:标题改"已处理",按钮换成一个跳转查看(noop key,不再触发回写)。
const zh = OUTCOME_ZH[outcome] ?? outcome;
try {
// 注:card_action.url 为 http://localhost(非 HTTPS / 未登记域名)会被更新接口判 40058;
// 本地先不带 card_action,线上配真实 HTTPS 域名(PAC_WEB_BASE_URL)再加回跳转。
const updateCard: TemplateCard = {
card_type: 'button_interaction',
source: { desc: 'PAC 召回 · 已处理', desc_color: 3 },
main_title: { title: `✅ 已处理:${zh}` },
sub_title_text: `工单 ${planStatus} · 操作人 wx:${clicker} · 第 ${attempts} 次触达`,
button_list: [{ text: '已完成', style: 1, key: `noop:${planId}` }],
};
if (link.startsWith('https://')) updateCard.card_action = { type: 1, url: link };
await client.updateTemplateCard(frame, updateCard);
} catch (err) {
this.logger.warn(`卡片更新失败(回写已成功)plan=${planId}: ${errStr(err)}`);
}
}
/** 默认客服 scope(PoC:统一 host/tenant;clicker 当 operatorUserId)。 */
private async defaultScope(wxUserid: string): Promise<TenantScopeContext> {
const hostId = await this.resolveHostId();
return { hostId, tenantId: this.tenantId, clinicIds: [], userId: `wx:${wxUserid}` };
}
/** 企微 userid → PAC 客服 token(PoC:统一映射到默认 tenant scope)。 */
private async mintToken(wxUserid: string): Promise<string> {
const hostId = await this.resolveHostId();
const role: UserRole = 'staff';
return this.auth.signAccessToken({
sub: `wx:${wxUserid}`,
hostId,
tenantId: this.tenantId,
clinicIds: [], // 空 = 不加诊所过滤,看该 tenant 整池(PoC);生产按客服归属诊所
role,
permissions: this.auth.resolvePermissions(role),
});
}
private async resolveHostId(): Promise<string> {
if (this.cachedHostId) return this.cachedHostId;
const host = await this.prisma.host.findFirst({ where: { name: this.hostName } });
if (!host) throw new Error(`host "${this.hostName}" 未找到(先 seed)`);
return (this.cachedHostId = host.id);
}
}
......@@ -105,6 +105,9 @@ importers:
'@scalar/nestjs-api-reference':
specifier: ^1.1.14
version: 1.1.14
'@wecom/aibot-node-sdk':
specifier: ^1.0.7
version: 1.0.7
ai:
specifier: ^6.0.184
version: 6.0.184(zod@4.4.3)
......@@ -2591,6 +2594,9 @@ packages:
'@webassemblyjs/wast-printer@1.14.1':
resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
'@wecom/aibot-node-sdk@1.0.7':
resolution: {integrity: sha512-51w+sTqunry6GD3HFvmuh0gArMSJDFE418vyvR1wMJHj1N6DaFuGD3HuaY2fazZs3mb9FWeQFX3+vU5t0Qhwmw==}
'@xhmikosr/archive-type@7.1.0':
resolution: {integrity: sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==}
engines: {node: '>=18'}
......@@ -2665,6 +2671,10 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
ai@6.0.184:
resolution: {integrity: sha512-j//zHkKvj5ra27l8izHco8cj1g1Pr7vx1ZK+hrzrkHvndgIRmdfZKOb6+RAPpvbk42qGIsuYvlYbGlVAu3erNQ==}
engines: {node: '>=18'}
......@@ -2810,6 +2820,9 @@ packages:
async@3.2.6:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
......@@ -2818,6 +2831,9 @@ packages:
resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==}
engines: {node: '>=4'}
axios@1.18.0:
resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==}
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
engines: {node: '>= 0.4'}
......@@ -3147,6 +3163,10 @@ packages:
resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==}
engines: {node: '>=18'}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
......@@ -3323,6 +3343,10 @@ packages:
defu@6.1.7:
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'}
......@@ -3629,6 +3653,9 @@ packages:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
eventemitter3@5.0.4:
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
events-universal@1.0.1:
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
......@@ -3777,6 +3804,15 @@ packages:
fn.name@1.1.0:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
follow-redirects@1.16.0:
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
for-each@0.3.5:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
......@@ -3796,6 +3832,10 @@ packages:
resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==}
engines: {node: '>= 14.17'}
form-data@4.0.6:
resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
engines: {node: '>= 6'}
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
......@@ -3951,6 +3991,10 @@ packages:
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
engines: {node: '>= 0.4'}
hasown@2.0.4:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'}
hast-util-to-jsx-runtime@2.3.6:
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
......@@ -3988,6 +4032,10 @@ packages:
resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==}
engines: {node: '>=10.19.0'}
https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
......@@ -5223,6 +5271,10 @@ packages:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
proxy-from-env@2.1.0:
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
engines: {node: '>=10'}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
......@@ -8537,6 +8589,17 @@ snapshots:
'@webassemblyjs/ast': 1.14.1
'@xtuc/long': 4.2.2
'@wecom/aibot-node-sdk@1.0.7':
dependencies:
axios: 1.18.0
eventemitter3: 5.0.4
ws: 8.21.0
transitivePeerDependencies:
- bufferutil
- debug
- supports-color
- utf-8-validate
'@xhmikosr/archive-type@7.1.0':
dependencies:
file-type: 20.5.0
......@@ -8666,6 +8729,12 @@ snapshots:
acorn@8.16.0: {}
agent-base@6.0.2:
dependencies:
debug: 4.4.3
transitivePeerDependencies:
- supports-color
ai@6.0.184(zod@4.4.3):
dependencies:
'@ai-sdk/gateway': 3.0.115(zod@4.4.3)
......@@ -8834,12 +8903,24 @@ snapshots:
async@3.2.6: {}
asynckit@0.4.0: {}
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.1.0
axe-core@4.11.4: {}
axios@1.18.0:
dependencies:
follow-redirects: 1.16.0
form-data: 4.0.6
https-proxy-agent: 5.0.1
proxy-from-env: 2.1.0
transitivePeerDependencies:
- debug
- supports-color
axobject-query@4.1.0: {}
b4a@1.8.1: {}
......@@ -9184,6 +9265,10 @@ snapshots:
color-convert: 3.1.3
color-string: 2.1.4
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
comma-separated-tokens@2.0.3: {}
commander@2.20.3: {}
......@@ -9329,6 +9414,8 @@ snapshots:
defu@6.1.7: {}
delayed-stream@1.0.0: {}
denque@2.1.0: {}
depd@2.0.0: {}
......@@ -9527,7 +9614,7 @@ snapshots:
es-errors: 1.3.0
get-intrinsic: 1.3.0
has-tostringtag: 1.0.2
hasown: 2.0.3
hasown: 2.0.4
es-shim-unscopables@1.1.0:
dependencies:
......@@ -9554,8 +9641,8 @@ snapshots:
'@next/eslint-plugin-next': 16.2.4
eslint: 9.39.4(jiti@2.7.0)
eslint-import-resolver-node: 0.3.10
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0))
......@@ -9577,7 +9664,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)):
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.3
......@@ -9588,22 +9675,22 @@ snapshots:
tinyglobby: 0.2.16
unrs-resolver: 1.11.1
optionalDependencies:
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
eslint: 9.39.4(jiti@2.7.0)
eslint-import-resolver-node: 0.3.10
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
......@@ -9614,7 +9701,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.39.4(jiti@2.7.0)
eslint-import-resolver-node: 0.3.10
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
hasown: 2.0.3
is-core-module: 2.16.2
is-glob: 4.0.3
......@@ -9767,6 +9854,8 @@ snapshots:
etag@1.8.1: {}
eventemitter3@5.0.4: {}
events-universal@1.0.1:
dependencies:
bare-events: 2.8.2
......@@ -9961,6 +10050,8 @@ snapshots:
fn.name@1.1.0: {}
follow-redirects@1.16.0: {}
for-each@0.3.5:
dependencies:
is-callable: 1.2.7
......@@ -9989,6 +10080,14 @@ snapshots:
form-data-encoder@2.1.4: {}
form-data@4.0.6:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
hasown: 2.0.4
mime-types: 2.1.35
forwarded@0.2.0: {}
fresh@2.0.0: {}
......@@ -10160,6 +10259,10 @@ snapshots:
dependencies:
function-bind: 1.1.2
hasown@2.0.4:
dependencies:
function-bind: 1.1.2
hast-util-to-jsx-runtime@2.3.6:
dependencies:
'@types/estree': 1.0.8
......@@ -10213,6 +10316,13 @@ snapshots:
quick-lru: 5.1.1
resolve-alpn: 1.2.1
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
debug: 4.4.3
transitivePeerDependencies:
- supports-color
human-signals@2.1.0: {}
iconv-lite@0.7.2:
......@@ -11793,6 +11903,8 @@ snapshots:
forwarded: 0.2.0
ipaddr.js: 1.9.1
proxy-from-env@2.1.0: {}
punycode@2.3.1: {}
pure-rand@6.1.0: {}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment