# guoway-kf-doubao-plugin **Repository Path**: templi/guoway-kf-doubao-plugin ## Basic Information - **Project Name**: guoway-kf-doubao-plugin - **Description**: 果唯云客服豆包插件 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: https://kf.guoway.net - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-10-20 - **Last Updated**: 2026-10-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: 豆包, 在线客服软件, 企业客服软件, AIAgent, 豆包办公插件 ## README # 豆包办公插件(果唯云客服桥接) > 官网:[https://kf.guoway.net](https://kf.guoway.net) — 果唯云客服,一站式全渠道智能客服系统,支持网站、App、小程序、微信公众号等渠道接入。 把果唯云客服的访客消息实时交给豆包办公智能体 / 豆包大模型处理,并把处理结果回复给访客。插件本身是一个独立 Node 进程,不侵入客服服务端。 ``` 访客 ──消息──▶ 果唯云客服 ──WebSocket 推送──▶ 本插件 ──▶ 豆包办公智能体 / 豆包大模型 ▲ │ └────────── POST /api/v1/bot/messages ◀────────┘ ``` - 接收:WebSocket 订阅 `bot_message_receive`,断线自动重连 - 处理:三种 Agent 模式(回显 / Webhook 智能体 / 豆包 Ark 大模型) - 回复:HTTP 接口回复访客,`client_msg_id` 幂等,失败自动兜底 - 转人工:关键词直达人工,Agent 也可主动要求转人工 - 工具:对外提供「发消息 / 转人工 / 拉历史」接口,供豆包智能体回调 - MCP:内置 Streamable HTTP 适配层,豆包工作桌面版可直接添加为自定义连接器 ## 目录结构 ``` doubao-plugin/ ├── src/ │ ├── index.js 入口:编排消息、队列、兜底、退出 │ ├── config.js 环境变量 / .env 加载与校验 │ ├── im-client.js Bot 协议:token、WS 认证、重连、发送、转人工、历史 │ ├── agent.js Agent 适配器:echo / webhook / ark │ ├── guard.js 访客输入护栏:命令/注入拦截、只读约束、回复痕迹过滤 │ ├── session-store.js 会话上下文(内存,带 TTL 与容量上限) │ ├── queue.js 会话级串行 + 全局并发控制 │ ├── http-server.js 健康检查、REST 工具接口与 MCP 路由 │ ├── mcp-server.js MCP 适配层(Streamable HTTP + JSON-RPC 2.0) │ ├── tools.js 工具实现(REST 与 MCP 共用) │ ├── http-util.js HTTP 工具与鉴权 │ ├── logger.js JSON 结构化日志 │ └── uuid.js UUID v7(WS 首帧要求) ├── test/ │ ├── guard.test.mjs 护栏与配置单测 │ └── e2e.mjs 端到端回归(真实服务端 + 真实访客会话) ├── plugin.json 插件清单(能力、配置、工具) ├── openapi.json 工具接口 OpenAPI(供智能体平台导入) └── .env.example 配置模板 ``` ## 快速开始 ### 1. 创建 Bot 登录企业管理后台 →「AI 机器人(Bot)」→ 新建机器人 → 启用,保存 **Bot ID** 与 **Bot Secret**(密钥仅展示一次)。 ### 2. 配置插件 ```bash cd doubao-plugin cp .env.example .env npm install ``` 编辑 `.env`: ```ini IM_BASE_URL=https://kf.guoway.net BOT_ID=你的 Bot ID BOT_SECRET=你的 Bot 密钥 AGENT_MODE=echo ``` ### 3. 启动 ```bash npm start ``` 看到 `WebSocket 已认证` 即接入成功。此时访客在网页端发消息,插件会回显: ``` 【豆包办公插件】已收到:你好,我想咨询价格 ``` > 注意:同一个 Bot 只保留最新一条连接。如果同时启动多个插件实例,先启动的会被踢下线。 ### 4. 验证 ```bash curl http://127.0.0.1:18090/healthz ``` ```json { "ok": true, "agent_mode": "echo", "counters": { "received": 3, "replied": 3, "failed": 0, "transferred": 0 }, "im": { "connected": true, "bot_id": "..." } } ``` `replied` 统计所有成功发出的回复,包括 Agent 回复、兜底文案与工具接口主动发送的消息;`transferred` 包含关键词、Agent 请求与工具接口三种转人工来源。 ### 5. 端到端回归(可选) ```bash # 需要客服测试实例已启动,且本机可用 mysql/redis ADMIN_ACCOUNT=admin ADMIN_PASSWORD=你的密码 \ IM_BASE_URL=http://127.0.0.1:18080 npm run test:e2e ``` > `ADMIN_ACCOUNT` / `ADMIN_PASSWORD` 必须显式传入,请使用测试环境账号,不要指向生产库。 脚本会自动创建临时 Bot、启动插件、模拟访客会话,验证「接收 → 回复 / 工具发消息 / 关键词转人工 / 转人工后拒绝回复」,结束后删除 Bot。 ## 三种 Agent 模式 ### echo(默认) 原样回显,用于验证链路。生产环境请切换到下面两种之一。 ### webhook(推荐:接入豆包办公智能体) 把消息 POST 给你的智能体/自建 Agent: ```ini AGENT_MODE=webhook WEBHOOK_URL=https://your-agent.example.com/invoke WEBHOOK_TOKEN=可选的 Bearer 令牌 ``` 请求: ```json { "session_id": "019a...", "visitor_id": "019a...", "visitor_name": "访客", "msg_id": "019a...", "message": "你们支持私有化部署吗?", "history": [ { "role": "user", "content": "你好" }, { "role": "assistant", "content": "您好,请问有什么可以帮您?" } ] } ``` 响应支持三种形态: ```json { "reply": "支持,私有化部署请联系商务。" } ``` ```json { "transfer": true, "reason": "需要报价" } ``` ``` 纯文本内容(非 JSON 时整体作为回复) ``` ### ark(豆包大模型直连) 走火山方舟 OpenAI 兼容接口: ```ini AGENT_MODE=ark ARK_API_KEY=你的方舟 API Key ARK_MODEL=你的推理接入点 ID ARK_BASE_URL=https://ark.cn-beijing.volces.com/api/v3 AGENT_SYSTEM_PROMPT=你是企业的在线客服助手,回答简洁礼貌,不确定时引导转人工。 ``` 插件会把系统提示词 + 最近 `HISTORY_LIMIT` 条消息(默认 20,上限 30,约 15 轮)+ 当前消息一起发给模型。 历史只取**最近**的消息(服务端 `latest=true`,旧服务端自动改取最后一页);总字符超过 `HISTORY_CHARS` 时从最早的消息开始丢弃,单条超过 2000 字符会被截断。 ## 与豆包办公的对接方式 插件提供了两条互补的通路,按豆包侧支持的能力选择: 1. **事件回调(接收消息)**:插件把访客消息 POST 到你的智能体 Webhook(`AGENT_MODE=webhook`)。适用于豆包办公智能体提供 HTTP 调用入口的场景。 2. **工具回调(主动发消息)**:把 `openapi.json` 导入为自定义插件/工具,智能体即可调用: - `send_message`:给访客发消息 - `transfer_to_human`:转人工 - `get_messages`:拉取会话上下文 工具接口默认监听 `127.0.0.1:18090`。供外部平台调用时: ```ini PLUGIN_HOST=0.0.0.0 TOOL_TOKEN=一个足够随机的令牌 ``` 调用示例: ```bash curl -X POST http://127.0.0.1:18090/tools/send_message \ -H 'Authorization: Bearer 你的TOOL_TOKEN' \ -H 'Content-Type: application/json' \ -d '{"session_id":"019a...","content":"已为您登记,稍后商务会联系您。"}' ``` > `TOOL_TOKEN` 为空时,工具接口只接受本机请求,避免被公网滥用。 ## 在豆包工作中接入(MCP) 插件内置 MCP 适配层(Streamable HTTP + JSON-RPC 2.0),可把三个工具直接挂到支持 MCP 的客户端(如豆包工作桌面版)。 1. 启动插件(`MCP_ENABLED=true` 默认开启)。 2. 打开豆包工作桌面版 → 左侧「技能 · 连接器 · 伙伴」→ 右上角「添加」→「新建自定义连接器」。 3. 传输类型选择 `HTTP`,MCP 服务器 URL 填: ``` http://127.0.0.1:18090/mcp ``` 4. 保存后即可在对话中调用: - `send_message`:给访客发消息 - `transfer_to_human`:转人工 - `get_messages`:读取会话历史 > 豆包工作的自定义连接器**仅桌面端、仅本机生效**,不会同步到手机或网页。连接器只能让豆包“调用工具”,不会把访客消息主动推给豆包;需要全自动托管请使用 `AGENT_MODE=ark` 或 `webhook`。 命令行自测: ```bash # 初始化(返回 serverInfo、protocolVersion,并带回 Mcp-Session-Id) curl -s http://127.0.0.1:18090/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}' # 工具列表 curl -s http://127.0.0.1:18090/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' # 调用发消息 curl -s http://127.0.0.1:18090/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"send_message","arguments":{"session_id":"01a1...","content":"已为您登记"}}}' ``` 配置了 `TOOL_TOKEN` 时,每个请求都要加 `-H 'Authorization: Bearer '`。豆包工作连接器若支持自定义 Header,可填入同样的 Bearer 令牌;不支持时请将 `TOOL_TOKEN` 留空并保持 `PLUGIN_HOST=127.0.0.1`(仅本机可访问)。 MCP 协议版本兼容 `2024-11-05` / `2025-03-26` / `2025-06-18`;仅支持 POST 请求-响应模式,不提供服务端主动推送的 SSE 流(GET 返回 405)。 ## 可靠性设计 | 风险 | 处理 | | --- | --- | | 服务端 60s 未收到回复会兜底 | Agent 默认 50s 超时,超时立刻发兜底文案(可配自动转人工);超时会 abort 请求,不存在“迟到答案”,因此没有宽限补发(openclaw / hermes 插件才有) | | 网络重试导致重复发送 | `client_msg_id` 使用访客消息 `msg_id`,服务端 24h 幂等 | | 断线重连后消息重复推送 | 本地按 `msg_id` 去重(10 分钟窗口) | | 同一会话连发多条导致乱序 | 会话级串行队列,全局并发上限 `MAX_CONCURRENCY` | | 多个插件实例重复消费 | 服务端保证同一 Bot 单活连接,旧连接被踢下线 | | 密钥重置 / Bot 禁用 | 服务端断开连接;插件按退避重连并提示授权失效 | | Agent 异常 / 返回空 | 发送兜底文案,可选自动转人工 | | 图片/文件等非文本消息 | 默认提示后转人工(`TRANSFER_ON_UNSUPPORTED`) | ## 配置项 完整配置见 [.env.example](.env.example),关键项: | 变量 | 说明 | 默认 | | --- | --- | --- | | `IM_BASE_URL` | 客服系统地址 | `https://kf.guoway.net` | | `BOT_ID` / `BOT_SECRET` | Bot 凭证 | 必填 | | `AGENT_MODE` | `echo` / `webhook` / `ark` | `echo` | | `AGENT_TIMEOUT_MS` | 单条消息处理超时 | `50000` | | `AUTO_TRANSFER_KEYWORDS` | 命中即转人工的关键词 | `转人工,人工客服,找人工,人工` | | `BLOCK_COMMAND_INPUT` | 拦截命令/注入类访客消息,不进入 Agent | `true` | | `COMMAND_BLOCK_REPLY` | 命令/注入被拦截时回复访客的话术 | 内置文案 | | `AGENT_GUARD_NOTICE` | 交 Agent 时声明「不可信输入 + 只读问答」(ark 拼 system,webhook 带 `untrusted`) | `true` | | `WEBHOOK_INLINE_GUARD` | webhook 模式把约束拼进 `message` 正文,原文放 `raw_message` | `true` | | `SANITIZE_AGENT_OUTPUT` | Agent 回复含终端输出/私钥等执行痕迹时替换为安全话术 | `true` | | `KNOWLEDGE_REQUIRE_SOURCE` | 严格模式:Agent 首行未声明 `[来源:本地\|服务端\|通用]` 时追加「AI 生成」说明(echo 模式不生效) | `true` | | `GREETING_FAST_REPLY` | 纯问候(「你好」「在吗」等)由插件直接回接待语,不调用 Agent | `true` | | `SKIP_KNOWLEDGE_FOR_GREETING` | 纯问候不检索知识库(必然未命中,白等最长 3s;仅在关闭上一项时生效) | `true` | | `HISTORY_LIMIT` | 带给 Agent 的最近历史条数(0 = 不带历史) | `20`(上限 30) | | `HISTORY_CHARS` | 历史总字符预算,超出后丢弃更早的消息 | `8000` | | `MAX_CONCURRENCY` | 全局并发 | `8` | | `PLUGIN_HOST` / `PLUGIN_PORT` | 插件 HTTP 监听 | `127.0.0.1:18090` | | `TOOL_TOKEN` | 工具接口/MCP 鉴权 | 空(仅本机) | | `MCP_ENABLED` / `MCP_PATH` | MCP 适配层开关与路径 | `true` / `/mcp` | ## 部署 ```bash # 后台运行 nohup npm start > plugin.out 2>&1 & ``` systemd 示例: ```ini [Unit] Description=Doubao Office IM Plugin After=network.target [Service] WorkingDirectory=/opt/im-system/doubao-plugin ExecStart=/usr/bin/node src/index.js Restart=always RestartSec=3 Environment=NODE_ENV=production [Install] WantedBy=multi-user.target ``` ## 排障 | 现象 | 排查方向 | | --- | --- | | 日志反复 `Bot 凭证无效` | Bot 是否被禁用;密钥是否被重置(重置后旧密钥立即失效) | | `WebSocket 认证失败` | `BOT_ID` 是否为 Bot 的 `bot_id`(不是数据库主键 id) | | 访客收不到回复,健康检查 `replied` 不增长 | 会话是否已转人工(`serve_type=2`);`counters.failed` 是否增长 | | 日志出现 `重复推送已忽略` | 正常去重,说明重连后服务端重投 | | 工具接口 401 | `TOOL_TOKEN` 不一致;未配置时只允许本机访问 | | MCP 客户端连不上或 401 | 确认 `MCP_ENABLED=true`;配置了 `TOOL_TOKEN` 时客户端需带 Bearer Header,否则留空仅本机使用 | | MCP GET 返回 405 | 正常:适配层只支持 POST 请求-响应,不提供 SSE 推流 | 日志为 JSON lines,直接 `tail -f` 或用 `jq` 过滤: ```bash npm start | jq -c 'select(.level!="info")' ``` ## 安全说明 - `.env` 已加入 `.gitignore`,不要把 `BOT_SECRET` / `ARK_API_KEY` 提交到仓库 - 日志不输出密钥与 token,`bot_id` / `session_id` 做了掩码 - 生产环境请为工具接口配置强随机 `TOOL_TOKEN`,并通过反向代理启用 HTTPS - Bot 仅能操作本租户会话,转人工后服务端会拒绝 Bot 继续回复(409) ### 访客输入安全 访客消息属于不可信外部输入:**只允许用来回答咨询问题,不允许变成对宿主电脑/服务器的命令。** 插件侧有三道防线(默认全部开启): 1. **入站拦截**:命中命令结构(`rm -rf`、`sudo`、`curl | bash`、`$(...)`、注册表/账号操作)、敏感路径(`/etc/passwd`、`~/.ssh`、`System32`)、远程控制(“操作这台电脑”、反弹 shell)、凭据套取、提示注入(“忽略之前的指令”“system prompt”)时,直接回复安全话术,**消息不进入 Agent**;服务端历史里的同类消息也会脱敏为占位符,不会再被带进上下文。 2. **只读约束**:`ark` 模式把安全约束拼进 `system`;`webhook` 模式默认把约束拼进 `message` 正文(原文在 `raw_message`),并额外带 `untrusted: true` 与 `guard` 说明,自研 Agent 应据此禁用工具调用。确认下游已自行处理时,可设 `WEBHOOK_INLINE_GUARD=false` 只保留字段标记。 3. **回复兜底**:Agent 回复里出现 `ls -l` 列表、`uid=0(root)`、root shell 提示符、`BEGIN ... PRIVATE KEY`、环境变量转储等“命令确实跑过”的痕迹时,替换为安全话术。 命中记录在日志 `访客消息命中命令/注入拦截` 与 `Agent 回复包含执行痕迹` 中,`/healthz` 的 `counters.blocked` / `counters.filtered` 可用于监控。 ### 企业身份与自我介绍 插件按 Bot token 绑定的租户调用 `GET /api/v1/bot/tenant`,把企业名、机器人名称、欢迎语与工作时间拼进 `ark` 的 `system` 与 `webhook` 的 `message`(`[客服身份]` 段),并要求 Agent 被问到“你是谁/你们是谁”时用企业名自我介绍、**不提及平台/模型/供应商名称**。 - 企业资料按 10 分钟缓存,查询失败时回退为中性身份,不会阻塞消息,也不会退回平台名; - 服务端未升级(接口 404)时同样走中性称呼,升级服务端后自动生效; - `AGENT_GUARD_NOTICE=false` 时不同步企业身份(与只读约束开关保持一致)。 注意边界: - 插件不能剥夺下游 Agent 的执行能力。**最终防线在 Agent 侧**:`webhook` 接自研 Agent 时不要给它挂 shell/文件写入工具,或只挂只读、本业务相关的工具;Agent 进程不要用高权限账号运行。 - 「命令报错怎么办」「`npm install` 卡住不动」「`docker` 启动不了」这类求助/故障提问会正常交给 Agent 回答;只有要求“现在就执行/操作设备”的消息才拦截。带疑问语气不构成豁免:「运行 whoami 吗」「执行 whoami?」仍会拦截。 - 需要完全关闭时设置 `BLOCK_COMMAND_INPUT=false`(不推荐);`AGENT_GUARD_NOTICE` / `SANITIZE_AGENT_OUTPUT` 可单独关闭。 ### 知识来源优先级:本地知识库 → 服务端知识库 → 通用知识 回答前 Agent 按固定顺序核对知识(顺序写在 `[回答依据]` 段里,`ark` 拼进 `system`,`webhook` 拼进 `message`/`guard`): 1. **本地知识库(最优先)**:Agent 本机/下游智能体能检索到的知识库、文档、长期记忆——查到就以它为准; 2. **服务端企业知识库(次优先)**:本地没有答案时,插件才用 `GET /api/v1/bot/knowledge/search?q=...` 检索本租户知识库(管理后台维护的问答对,支持精准与关键词模糊匹配)。命中时写入 `[知识库] 服务端知识库命中标准答案(关键词:xx)(次优先:本地知识库没有答案时才使用)`,要求仅依据该答案作答,可口语化改写但不得增删事实、不补充库外信息;未命中时写入 `[知识库] 服务端知识库未命中。请先检索本地知识库…`; 3. **通用知识(兜底)**:1、2 都查不到时才允许基于通用知识谨慎回答,并标注来源。 来源标记协议:Agent 在回复正文第一行输出来源标记,插件读取后**自动剥离**,访客看不到这一行: | 首行标记 | 插件行为 | | --- | --- | | `[来源:本地]` / `[来源:服务端]` | 有知识库依据,只剥离标记,原样发送 | | `[来源:通用]` | 追加 `(该问题知识库暂未收录,以上回答由 AI 生成,仅供参考)` | | 未输出标记 | 默认按通用知识处理(严谨优先),追加 `(以上回答由 AI 生成,仅供参考)` | 说明由插件在发送前**确定性追加**,不依赖模型自觉,同一条回复只加一次。兜底文案(Agent 超时/空回复)、安全话术与 `echo` 模式(链路自检)不会标注,对话历史也只存剥离标记后的正文。设置 `KNOWLEDGE_REQUIRE_SOURCE=false` 可退回「按服务端是否命中判断」的行为。 接口不可用(旧服务端 404、网络故障、服务端 503)时 5 分钟内不再重试,按**不可用**而降级到“未命中”:Agent 仍会先查本地知识库,本地也没有时按通用知识作答,但出站只说“以上回答由 AI 生成,仅供参考”,不会谎称“知识库暂未收录”;不会阻塞访客消息;升级服务端后自动生效。答案中的 `<<<`、`[知识库]` 等伪造标记会被剥离,避免知识库内容反过来篡改上下文结构。 ## 开源许可 本项目基于 [MIT License](LICENSE) 开源,可自由使用、修改与分发(需保留版权声明)。 果唯云客服服务端为闭源商业软件,不在本仓库范围内;插件仅通过公开的 Bot HTTP / WebSocket 协议与其通信。