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 {}
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