调价后的新价格写入报价单;多段段前的货物带到各段,协同群不再把体积里的星号吃掉。海运字段齐了先核对再查价。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-03 22:39:54 +08:00
co-authored by Cursor
parent 0a14b4cc15
commit 7c77e45e03
32 changed files with 1384 additions and 75 deletions
@@ -64,6 +64,8 @@ class FlowSession:
handoff_offer: str = ""
handoff_committed: bool = False
virtual_work_order_no: str = ""
# 核对卡还没有工单号时,图片/附件先记在这里,确定建单后再挂到主账。
pending_files: tuple[dict[str, str], ...] = ()
def is_waiting_mode_session(sess) -> bool:
@@ -139,6 +141,15 @@ def _session_from_payload(raw: dict[str, Any]) -> FlowSession:
handoff_offer=str(raw.get("handoff_offer") or ""),
handoff_committed=bool(raw.get("handoff_committed")),
virtual_work_order_no=str(raw.get("virtual_work_order_no") or ""),
pending_files=tuple(
{
"file_name": str(item.get("file_name") or ""),
"object_key": str(item.get("object_key") or ""),
"content_type": str(item.get("content_type") or ""),
}
for item in (raw.get("pending_files") or [])
if isinstance(item, dict) and str(item.get("object_key") or "").strip()
),
)
@@ -174,6 +185,29 @@ class AirTextInquiryFlow:
"""空运群 BOT 出站;单测注入假客户端。"""
return self._group_client
def remember_pending_files(self, sender_id: str, files: list[dict[str, str]]) -> None:
"""
核对卡阶段还没有工单号,先把图片/附件键记在当前书签上。
确定建单后再挂主账。副作用:覆盖该书签的 pending_files 并持久化。
"""
sess = self.session_of(sender_id)
if sess is None:
return
cleaned = tuple(
{
"file_name": str(item.get("file_name") or ""),
"object_key": str(item.get("object_key") or ""),
"content_type": str(item.get("content_type") or ""),
}
for item in files
if str(item.get("object_key") or "").strip()
)
if not cleaned:
return
sess.pending_files = cleaned
self._save(sess)
def session_of(self, sender_id: str) -> Optional[FlowSession]:
sid = (sender_id or "").strip()
with self._lock:
@@ -417,6 +451,8 @@ class AirTextInquiryFlow:
from agent.policy.handoff_human import inherit_pre_create_ticket, note_clarify_round
if sess is not None:
if not new_sess.pending_files:
new_sess.pending_files = tuple(getattr(sess, "pending_files", ()) or ())
new_sess.clarify_missing_sig = sess.clarify_missing_sig
new_sess.clarify_retry = sess.clarify_retry
new_sess.handoff_offer = sess.handoff_offer
@@ -550,6 +586,7 @@ class AirTextInquiryFlow:
按工单号回到当时停点并重出对应卡片。
先用进程内该书签;没有再读主账推断节点。不重跑 TMS、不新建工单。
正在等未成交原因时,原话记成原因,不再把「请补充未成交原因」重发一遍。
"""
no = (work_order_no or "").strip().upper()
if not no:
@@ -566,6 +603,9 @@ class AirTextInquiryFlow:
closed = self._close_spoken_deal(keep, text, reply)
if closed:
return closed
taken = self._resume_lost_reason(keep, text, reply)
if taken:
return taken
return self._replay_wait(keep, reply, source="session")
getter = getattr(self._ledger, "get_for_agent", None)
if not callable(getter):
@@ -584,6 +624,9 @@ class AirTextInquiryFlow:
closed = self._close_spoken_deal(sess, text, reply)
if closed:
return closed
taken = self._resume_lost_reason(sess, text, reply)
if taken:
return taken
return self._replay_wait(sess, reply, source="ledger")
def _session_from_ledger(self, *, sender_id: str, view: dict[str, Any]) -> FlowSession:
@@ -652,6 +695,26 @@ class AirTextInquiryFlow:
logger.info("resume 报价文件已出,回到成交跟进 wo=%s was=%s", sess.work_order_no, sess.phase)
self._promote_wait_deal(sess)
def _resume_lost_reason(self, sess: FlowSession, text: str, reply: ReplyFn) -> str:
"""
等未成交原因时,续办带来的原话就是原因。
短句常被判成继续工单。继续工单若只重发提示,原因不会入账。
原话只有工单号时仍重发提示,不把工单号当成原因。
"""
if (sess.phase or "") != "wait_lost_reason":
return ""
body = (text or "").strip()
if not body:
return ""
named = copy.extract_work_order_no(body)
leftover = body
if named:
leftover = body.replace(named, "", 1).strip(" ,,。.;;")
if not leftover:
return ""
return self._capture_lost_reason(sess, leftover, reply)
def _replay_wait(self, sess: FlowSession, reply: ReplyFn, *, source: str) -> str:
"""按书签重出当时节点,不改六态。"""
logger.info(