项目初始化

This commit is contained in:
2026-07-02 11:31:16 +08:00
commit eef4c76e3f
64 changed files with 13369 additions and 0 deletions

156
bot/main.py Normal file
View File

@@ -0,0 +1,156 @@
"""企业微信智能机器人 · Skills 助手skills 快查 + 截图预览 + Cursor 执行任务)。"""
from __future__ import annotations
import logging
import sys
import env_config
from bridge_manager import shutdown_cursor_bridge, warm_cursor_bridge
from router import route_message, routing_mode
from skills_service import handle_command, warm_feed_cache
from wecom_media import reply_image, upload_image
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger("skills-bot")
BOT_ID = env_config.env("WECOM_BOT_ID") or env_config.env("WECHAT_BOT_ID")
BOT_SECRET = env_config.env("WECOM_BOT_SECRET") or env_config.env("WECHAT_BOT_SECRET")
def _require_credentials() -> None:
if not BOT_ID or not BOT_SECRET:
print(
"请设置环境变量 WECOM_BOT_ID 和 WECOM_BOT_SECRET\n"
"(企业微信 → 智能机器人 → API 模式 → 长连接)",
file=sys.stderr,
)
sys.exit(1)
def create_client():
from aibot import WSClient, WSClientOptions, generate_req_id
ws_client = WSClient(
WSClientOptions(
bot_id=BOT_ID,
secret=BOT_SECRET,
)
)
@ws_client.on("authenticated")
def on_authenticated():
logger.info("企业微信长连接认证成功,路由模式=%s", routing_mode())
cursor_key = env_config.env("CURSOR_API_KEY")
if cursor_key:
logger.info("CURSOR_API_KEY 已加载(%s…)", cursor_key[:8])
try:
warm_cursor_bridge()
logger.info("Cursor bridge 预启动完成")
except Exception as exc:
logger.warning("Cursor bridge 预启动失败Cursor 任务时会重试): %s", exc)
else:
logger.warning("CURSOR_API_KEY 未配置Cursor 任务将失败")
try:
warm_feed_cache()
logger.info("skills 数据预加载完成")
except Exception as exc:
logger.warning("skills 数据预加载失败: %s", exc)
@ws_client.on("event.enter_chat")
async def on_enter_chat(frame):
help_text = handle_command("help")
extra = (
"\n\n---\n"
"**单页截图**`preview` / `截图` / `预览 [路径或URL]`\n"
"**网页操作**:自然语言多步操作,或 `browser 场景名`\n"
"例:`访问登录页,输入账号密码,点击登录,点击智能体管理,截图`\n"
"场景文件:`bot/scenarios/*.yaml`(可用 `browser xiaobao-agent-manage`"
)
await ws_client.reply_welcome(
frame,
{
"msgtype": "markdown",
"markdown": {"content": help_text + extra},
},
)
@ws_client.on("message.text")
async def on_text(frame):
body = frame.get("body", {})
content = body.get("text", {}).get("content", "")
logger.info("收到消息: %s", content)
stream_id = generate_req_id("stream")
last_progress = ""
async def on_progress(message: str) -> None:
nonlocal last_progress
if message != last_progress:
last_progress = message
await ws_client.reply_stream(frame, stream_id, message, False)
await ws_client.reply_stream(frame, stream_id, "收到,正在处理…", False)
try:
result = await route_message(content, on_progress=on_progress)
reply = result.text
logger.info(
"回复来源: %s, 文本长度=%d, 图片=%s",
result.source,
len(reply),
result.image_path or "-",
)
except Exception as exc:
logger.exception("处理失败")
reply = f"处理失败:{exc}"
result = None
if len(reply) > 3800:
reply = reply[:3800] + "\n\n> …内容已截断"
await ws_client.reply_stream(frame, stream_id, reply, True)
if result and result.image_path:
try:
media_id = await upload_image(ws_client, result.image_path)
await reply_image(ws_client, frame, media_id)
logger.info("图片已发送到企微: %s", result.image_path)
except Exception as exc:
logger.exception("发送图片失败")
await ws_client.reply(
frame,
{
"msgtype": "markdown",
"markdown": {
"content": f"截图文件:`{result.image_path}`\n发图失败:{exc}\n\n请确认 bot 已重启,或发送 `截图` 重试。",
},
},
)
@ws_client.on("error")
def on_error(error):
logger.error("连接错误: %s", error)
@ws_client.on("disconnected")
def on_disconnected(reason):
logger.warning("连接断开: %s", reason)
return ws_client
def main() -> None:
import atexit
atexit.register(shutdown_cursor_bridge)
_require_credentials()
client = create_client()
logger.info("启动 Skills 助手Bot ID=%s", BOT_ID[:8] if BOT_ID else "?")
client.run()
if __name__ == "__main__":
main()