落地空运群内协同与锁舱出站。
群里按 BOT 通道激活当前工单、航线报价和成交跟进;没有 TMS 报价编号时用工单字段补齐仍调锁舱,避免被本地规则拦住。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+130
-6
@@ -4,7 +4,8 @@
|
||||
* 本文件职责:
|
||||
* - /health 存活
|
||||
* - POST /forward 把已解析消息转到智能体 WECOM_AIBOT_INGRESS_URL(默认 8910/internal/aibot/inbound)
|
||||
* - 默认不连企微 Bot 长连接(并存期禁止双连);WECOM_AIBOT_ENABLED=true 时仅打警告
|
||||
* - POST /send-group 走长连接 aibot_send_msg(发文件后马上在群里说话)
|
||||
* - 默认不连企微(并存期禁止双连);WECOM_AIBOT_ENABLED=true 时订阅长连接
|
||||
*
|
||||
* 禁止:在本进程写工单/六态/Graph。
|
||||
*/
|
||||
@@ -15,6 +16,20 @@ const http = require("http");
|
||||
const { URL } = require("url");
|
||||
const { startLongConnectionLoop } = require("./long_connection.cjs");
|
||||
|
||||
let longConn = {
|
||||
sendGroupMessage: async () => {
|
||||
const err = new Error("ws_offline");
|
||||
err.code = "ws_offline";
|
||||
throw err;
|
||||
},
|
||||
sendGroupFile: async () => {
|
||||
const err = new Error("ws_offline");
|
||||
err.code = "ws_offline";
|
||||
throw err;
|
||||
},
|
||||
status: () => ({ enabled, connected: false, subscribed: false }),
|
||||
};
|
||||
|
||||
const port = Number(process.env.AIBOT_BRIDGE_PORT || 8913);
|
||||
const bind = process.env.AIBOT_BRIDGE_BIND || "127.0.0.1";
|
||||
const enabled = String(process.env.WECOM_AIBOT_ENABLED || "false").toLowerCase() === "true";
|
||||
@@ -94,10 +109,10 @@ const server = http.createServer(async (req, res) => {
|
||||
port,
|
||||
wecomAibotEnabled: enabled,
|
||||
ingressUrl,
|
||||
longConnectionShell: true,
|
||||
longConnection: longConn.status(),
|
||||
note: enabled
|
||||
? "enabled:长连接帧循环壳已起(默认无 Bot SDK);勿与现网双连"
|
||||
: "Bot 长连接默认关闭;可用 POST /forward 测转发",
|
||||
? "enabled:长连接已尝试订阅;POST /send-group 可主动往群里说话;勿与现网双连"
|
||||
: "Bot 长连接默认关闭;可用 POST /forward 测转发。主动发言需 WECOM_AIBOT_ENABLED=true",
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -130,9 +145,118 @@ const server = http.createServer(async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 官方桥口径:POST /send {chatid, operation:"text", content}
|
||||
if (req.method === "POST" && (url === "/send" || url === "/send/")) {
|
||||
if (bridgeToken) {
|
||||
const got = String(
|
||||
req.headers["x-aibot-bridge-token"] || req.headers["x-ytd-aibot-bridge-token"] || ""
|
||||
);
|
||||
if (got !== bridgeToken) {
|
||||
sendJson(res, 401, { error: "unauthorized" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const raw = await readBody(req);
|
||||
const body = raw ? JSON.parse(raw) : {};
|
||||
const chatId = String(body.chatid || body.chat_id || "").trim();
|
||||
const content = String(body.content || "").trim();
|
||||
if (!chatId || !content) {
|
||||
sendJson(res, 400, { error: "missing_chat_or_content" });
|
||||
return;
|
||||
}
|
||||
await longConn.sendGroupMessage({ chatId, content });
|
||||
sendJson(res, 200, { status: "sent", chatId });
|
||||
} catch (e) {
|
||||
const code = e && e.code ? e.code : "send_failed";
|
||||
const httpCode = code === "ws_offline" ? 503 : 502;
|
||||
sendJson(res, httpCode, {
|
||||
error: code,
|
||||
message: String(e && e.message ? e.message : e),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 空运群发填好的报价模板:上传素材后 aibot_send_msg file
|
||||
if (req.method === "POST" && (url === "/send-file" || url === "/send-file/")) {
|
||||
if (bridgeToken) {
|
||||
const got = String(
|
||||
req.headers["x-aibot-bridge-token"] || req.headers["x-ytd-aibot-bridge-token"] || ""
|
||||
);
|
||||
if (got !== bridgeToken) {
|
||||
sendJson(res, 401, { error: "unauthorized" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const raw = await readBody(req);
|
||||
const body = raw ? JSON.parse(raw) : {};
|
||||
const chatId = String(body.chat_id || body.chatid || "").trim();
|
||||
const filename = String(body.filename || "quote.xlsx").trim() || "quote.xlsx";
|
||||
const fileB64 = String(body.file_b64 || body.fileBase64 || "").trim();
|
||||
if (!chatId || !fileB64) {
|
||||
sendJson(res, 400, { error: "missing_chat_or_file" });
|
||||
return;
|
||||
}
|
||||
await longConn.sendGroupFile({ chatId, filename, fileB64 });
|
||||
sendJson(res, 200, { status: "sent", chatId, filename });
|
||||
} catch (e) {
|
||||
const code = e && e.code ? e.code : "send_failed";
|
||||
const httpCode = code === "ws_offline" ? 503 : 502;
|
||||
sendJson(res, httpCode, {
|
||||
error: code,
|
||||
message: String(e && e.message ? e.message : e),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 智能体 Worker:存档听到附件后马上在群里说话(aibot_send_msg)
|
||||
if (req.method === "POST" && (url === "/send-group" || url === "/send-group/")) {
|
||||
if (bridgeToken) {
|
||||
const got = String(
|
||||
req.headers["x-aibot-bridge-token"] || req.headers["x-ytd-aibot-bridge-token"] || ""
|
||||
);
|
||||
if (got !== bridgeToken) {
|
||||
sendJson(res, 401, { error: "unauthorized" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const raw = await readBody(req);
|
||||
const body = raw ? JSON.parse(raw) : {};
|
||||
const chatId = String(body.chat_id || body.chatid || "").trim();
|
||||
const content = String(body.content || "").trim();
|
||||
if (!chatId || !content) {
|
||||
sendJson(res, 400, { error: "missing_chat_or_content" });
|
||||
return;
|
||||
}
|
||||
await longConn.sendGroupMessage({ chatId, content });
|
||||
sendJson(res, 200, { status: "sent", chatId });
|
||||
} catch (e) {
|
||||
const code = e && e.code ? e.code : "send_failed";
|
||||
const httpCode = code === "ws_offline" ? 503 : 502;
|
||||
sendJson(res, httpCode, {
|
||||
error: code,
|
||||
message: String(e && e.message ? e.message : e),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sendJson(res, 404, { error: "not_found", path: req.url });
|
||||
});
|
||||
|
||||
process.on("uncaughtException", (e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("[aibot-bridge] uncaught", String(e && e.stack ? e.stack : e).slice(0, 400));
|
||||
});
|
||||
process.on("unhandledRejection", (e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("[aibot-bridge] rejection", String(e && e.stack ? e.stack : e).slice(0, 400));
|
||||
});
|
||||
|
||||
server.listen(port, bind, () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
@@ -144,8 +268,8 @@ server.listen(port, bind, () => {
|
||||
"[aibot-bridge] WECOM_AIBOT_ENABLED=true:请确认已停现网桥且不会双连同一 Bot"
|
||||
);
|
||||
}
|
||||
// 传输壳:enabled 时跑心跳循环;未接 SDK 前不会产生入站帧
|
||||
startLongConnectionLoop({
|
||||
// 启用时订阅企微长连接;关闭时 /send-group 会回 ws_offline
|
||||
longConn = startLongConnectionLoop({
|
||||
enabled,
|
||||
forwardToAgent,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user