落地海运协同群:测服靠会话存档听群话,报价单能带上协同字段。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-15 18:12:45 +08:00
co-authored by Cursor
parent 235ecd8c83
commit 12219cacb1
39 changed files with 4436 additions and 256 deletions
+29
View File
@@ -110,6 +110,7 @@ class WorkerRuntime:
threading.Thread(target=self._llm_loop, name="llm-loop", daemon=True),
threading.Thread(target=self._recognition_loop, name="recog-loop", daemon=True),
threading.Thread(target=self._archive_loop, name="archive-loop", daemon=True),
threading.Thread(target=self._wshoto_poll_loop, name="wshoto-poll", daemon=True),
threading.Thread(target=self._pdf_loop, name="pdf-loop", daemon=True),
]
for t in self._threads:
@@ -241,6 +242,34 @@ class WorkerRuntime:
self.http_jobs.run(_job, label="archive")
time.sleep(0.2)
def _wshoto_poll_loop(self) -> None:
"""
独立线程轮询微盛群消息,写入 inbox。
不占回调、不占 HTTP 槽、不与 soffice 串队。建群或刚有人说话会立刻再扫。
未配钥匙时只空等,避免打无密钥请求。
"""
from agent.channel.wshoto.client import wshoto_ready
from agent.channel.wshoto.poll import poll_once
from agent.channel.wshoto.rooms import has_hot_room, wait_wshoto_poll
interval = float(getattr(self.settings, "wshoto_poll_interval_sec", 2.0) or 2.0)
interval = min(30.0, max(1.0, interval))
logged_skip = False
while not self._stop.is_set():
if not wshoto_ready(self.settings):
if not logged_skip:
logger.info("微盛轮询未开启:缺少 WSHOTO_APP_ID/SECRET 或开关已关")
logged_skip = True
self._stop.wait(15.0)
continue
try:
poll_once(settings=self.settings, redis=self.redis.client if self.redis else None)
except Exception:
logger.exception("微盛轮询失败")
wait = 1.0 if has_hot_room() else interval
wait_wshoto_poll(wait)
def _pdf_loop(self) -> None:
"""仅 LibreOffice 单槽消费 PDF Stream。"""
if not self.redis:
+2 -1
View File
@@ -1,7 +1,8 @@
"""
微盛存档任务壳:独立 Stream,与 LibreOffice 槽隔离。
本文件职责:enqueue / process_stub;正式下载与登记后续接对象存储 + 主账附件元数据。
本文件职责:enqueue / process_stub;附件落对象存储后续再接。
群消息轮询不在本 Stream,见 jobs/archive_poll(Worker HTTP 槽拉微盛)。
禁止:与 soffice 共用阻塞线;禁止把存档成功冒充业务成交成功。
"""
+12
View File
@@ -0,0 +1,12 @@
"""
微盛轮询入口(Worker 槽调用)。
实现在 channel/wshoto/poll.py,避免 jobs 包导入把通道单测拖进 Graph。
"""
from __future__ import annotations
from agent.channel.wshoto.client import wshoto_ready
from agent.channel.wshoto.poll import archive_seat_userids, ingest_records, poll_once
__all__ = ["archive_seat_userids", "ingest_records", "poll_once", "wshoto_ready"]
+14 -12
View File
@@ -136,28 +136,30 @@ def _deliver_quote_file(data: dict[str, Any], result: LibreOfficeResult) -> None
from agent.policy.inquiry_copy import file_ready
store = get_message_store()
quote = data.get("quote") if isinstance(data.get("quote"), dict) else {}
facts = data.get("facts") if isinstance(data.get("facts"), dict) else {}
from agent.policy import inquiry_copy as copy
store.enqueue_outbound(
touser=touser,
content=file_ready(kind="PDF", work_order_no=wo, filename=filename),
content="\n".join(
[
file_ready(kind="PDF", work_order_no=wo, filename=filename),
copy.deal_card(work_order_no=wo, quote=quote, facts=facts),
]
),
dedupe_key=f"pdf-file:{wo}:{filename}",
payload={
"msgtype": "file",
"filepath": result.pdf_path,
"filename": filename,
"followup_card": copy.deal_wecom_payload(
work_order_no=wo, quote=quote, facts=facts
).get("template_card")
or {},
},
)
# 文件先入队,成交卡后入队;出站单线程保证销售先看到文件
quote = data.get("quote") if isinstance(data.get("quote"), dict) else {}
facts = data.get("facts") if isinstance(data.get("facts"), dict) else {}
if wo:
from agent.policy import inquiry_copy as copy
store.enqueue_outbound(
touser=touser,
content=copy.deal_card(work_order_no=wo, quote=quote, facts=facts),
dedupe_key=f"deal-card:{wo}:{filename}",
payload=copy.deal_wecom_payload(work_order_no=wo, quote=quote, facts=facts),
)
try:
from agent.policy.air_text_flow import get_air_text_flow