初始化询价智能体新仓:同步本地工程并排除密钥与 .env。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-09-13 14:30:44 +08:00
co-authored by Cursor
commit 2a14def882
2621 changed files with 393165 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
"""
通道消费循环:inbox → echo handler;outbox → 企微发送。
本文件职责:在 HTTP 进程内独立线程跑快路径,不与 LibreOffice 串队。
禁止:在回调请求线程调用本循环的阻塞逻辑;禁止 Redis 全局单例锁。
"""
from __future__ import annotations
import logging
import threading
from typing import Optional
from agent.channel.outbox.sender import PERMANENT_SEND_ERRORS, WeComAppClient
from agent.channel.queue import MemoryMessageStore, get_message_store
from agent.config import Settings
from agent.handlers.echo_text import handle_echo_text
logger = logging.getLogger(__name__)
# 避免 httpx 把 access_token / secret 打进 INFO
logging.getLogger("httpx").setLevel(logging.WARNING)
class ChannelRuntime:
"""
企微通道运行时:inbox/outbox 各一条守护线程。
启动时机:FastAPI lifespan;停止:进程退出。
"""
def __init__(self, settings: Settings) -> None:
self._settings = settings
self._store = get_message_store()
self._stop = threading.Event()
self._threads: list[threading.Thread] = []
agent_id = int(settings.wecom_agent_id or "0") or 0
self._client = WeComAppClient(
corp_id=settings.wecom_corp_id,
secret=settings.wecom_secret,
agent_id=agent_id,
)
@property
def store(self) -> MemoryMessageStore:
return self._store
def start(self) -> None:
"""启动 inbox/outbox 消费线程。"""
self._stop.clear()
self._threads = [
threading.Thread(target=self._inbox_loop, name="wecom-inbox", daemon=True),
threading.Thread(target=self._outbox_loop, name="wecom-outbox", daemon=True),
]
for t in self._threads:
t.start()
logger.info("ChannelRuntime 已启动(inbox/outbox 独立线程)")
def stop(self) -> None:
self._stop.set()
def _inbox_loop(self) -> None:
while not self._stop.is_set():
item = self._store.claim_inbound()
if item is None:
self._store.wait_inbox(1.0)
continue
try:
handle_echo_text(
item.message,
self._store,
ytd_env=self._settings.ytd_env,
reply_template=self._settings.wecom_callback_reply_text,
)
self._store.complete_inbound(item.id)
except Exception as exc: # noqa: BLE001
logger.exception("inbox 处理失败 id=%s", item.id)
self._store.complete_inbound(item.id, error=str(exc))
def _outbox_loop(self) -> None:
while not self._stop.is_set():
item = self._store.claim_outbound()
if item is None:
self._store.wait_outbox(1.0)
continue
# 过期丢弃:超过 30 分钟的出站不再发(对齐「过期不计业务成功」)
import time
if time.time() - item.created_at > 1800:
logger.warning("outbox 过期丢弃 id=%s", item.id)
self._store.complete_outbound(item.id)
continue
result = self._client.send_text(touser=item.touser, content=item.content)
if result.ok:
self._store.complete_outbound(item.id)
elif result.errcode in PERMANENT_SEND_ERRORS:
logger.error(
"outbox 永久失败不再重试 id=%s err=%s:%s",
item.id,
result.errcode,
result.errmsg,
)
self._store.complete_outbound(
item.id,
error=f"permanent:{result.errcode}:{result.errmsg}",
permanent=True,
)
else:
self._store.complete_outbound(
item.id,
error=f"{result.errcode}:{result.errmsg}",
)
_RUNTIME: Optional[ChannelRuntime] = None
def get_channel_runtime() -> Optional[ChannelRuntime]:
return _RUNTIME
def start_channel_runtime(settings: Settings) -> ChannelRuntime:
"""幂等启动通道运行时。"""
global _RUNTIME
if _RUNTIME is None:
_RUNTIME = ChannelRuntime(settings)
_RUNTIME.start()
return _RUNTIME