"""Feishu signal pipeline mixin.""" from __future__ import annotations from .shared import * class FeishuPipelineGateDispatchMixin: async def _handle_external_connect_gate( self, *, message: str, sender: str, source_chat_id: str, source_sender_open_id: Optional[str], feishu_message_id: Optional[str], captured_at: Optional[datetime], intent_gate: dict[str, Any], process_started_at: float, ) -> dict[str, Any]: occurred_at = captured_at or _utc_now() if occurred_at.tzinfo is None: occurred_at = occurred_at.replace(tzinfo=CN_TZ) fields = intent_gate.get("fields") if isinstance(intent_gate.get("fields"), dict) else {} blocking_reason = str(intent_gate.get("clarification_template") or "").strip() or "外部系统接入默认进入待决策队列。" queued = await self._queue_command_intent_item( message=message, sender=sender, source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, feishu_message_id=feishu_message_id, occurred_at=occurred_at, intent_gate=intent_gate, operation_code="source.connect", operation_payload={"project_id": _safe_int(fields.get("project_id"))}, blocking_reason=blocking_reason, decision_reason="external_connect_requires_manual_decision", matched_project_id=_safe_int(fields.get("project_id")), ) await self._write_processing_log( feishu_message_id=feishu_message_id, feishu_item_id=_safe_int(queued.get("item_id")), raw_message=message, ai_raw_output=json.dumps({"intent_gate": intent_gate}, ensure_ascii=False), project_score=1.0 if fields.get("project_id") is not None else None, signal_confidence=0.0 if fields.get("project_id") is not None else None, level="command", routing_decision=ROUTING_INTENT_GATE_EXTERNAL_CONNECT, routing_reason="外部系统接入被收口到待决策队列,不允许自由执行。", reasoning_bullets=[ "intent gate 命中 external_connect。", "当前仅允许受控工具链执行。", "系统已转待决策,不做任意 HTTP/shell。", ], processing_status="success", error_message=None, duration_ms=int((time.perf_counter() - process_started_at) * 1000), normalized_text=message, route=ROUTING_INTENT_GATE_EXTERNAL_CONNECT, intent=INTENT_EXTERNAL_CONNECT, executor="router", operation_code="source.connect", reply_source="deterministic_reply", ) return { "status": "captured", "classification": "command", "project_scope": "command", "linked_project_ids": [int(fields["project_id"])] if fields.get("project_id") is not None else [], "signal_id": None, "created_project_id": None, "auto_tasks": [], "ai_trace_id": f"feishu-external-{queued.get('item_id')}", "results": [queued], "intent": INTENT_EXTERNAL_CONNECT, "clarification_message": queued.get("clarification_message"), "direct_reply_required": True, "direct_reply_text": str(queued.get("clarification_message") or blocking_reason), } async def _maybe_handle_intent_gate_capture( self, *, intent_gate: dict[str, Any], message: str, sender: str, source_chat_id: str, source_sender_open_id: Optional[str], feishu_message_id: Optional[str], captured_at: Optional[datetime], inferred_project_id: Optional[int], projects: list[dict[str, Any]], process_started_at: float, normalized_chat_type: str, ) -> Optional[dict[str, Any]]: gate_intent = str(intent_gate.get("intent") or INTENT_SIGNAL_INTAKE) if gate_intent == INTENT_SMALLTALK: duration_ms = int((time.perf_counter() - process_started_at) * 1000) await self._write_processing_log( feishu_message_id=feishu_message_id, feishu_item_id=None, raw_message=message, ai_raw_output=json.dumps({"intent_gate": intent_gate}, ensure_ascii=False), project_score=None, signal_confidence=None, level="smalltalk", routing_decision=ROUTING_NOISE_FILTERED, routing_reason="intent gate 识别为 smalltalk/短确认,不进入 Inbox。", reasoning_bullets=[ "消息不包含可执行主数据动作。", "命中 short confirmation / smalltalk。", "系统直接忽略,不写通知。", ], processing_status="success", error_message=None, duration_ms=duration_ms, ) await self.db.commit() return self._decorate_capture_result( { "status": "ignored", "reason": ROUTING_NOISE_FILTERED, "classification": "smalltalk", "project_scope": "smalltalk", "linked_project_ids": [], "signal_id": None, "created_project_id": None, "auto_tasks": [], "ai_trace_id": f"feishu-smalltalk-{(feishu_message_id or 'noid')[:32]}", "results": [], }, chat_type=normalized_chat_type, normalized_text=message, route=f"{normalized_chat_type}.intent_gate.smalltalk", db_mutations=[], ) if gate_intent == INTENT_HEALTHCHECK: healthcheck_capture = await self._build_healthcheck_capture( message=message, feishu_message_id=feishu_message_id, process_started_at=process_started_at, ) await self.db.commit() return self._decorate_capture_result( healthcheck_capture, chat_type=normalized_chat_type, normalized_text=message, route=f"{normalized_chat_type}.intent_gate.healthcheck", db_mutations=[], ) if gate_intent == INTENT_QUERY: rag_service = AdaptiveRAGService(self.db, tenant_id=self.tenant_id) conversation_key = self._build_rag_conversation_key( source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, sender=sender, ) rag_result = await rag_service.answer_question( question=message, asked_by=None, asked_via="feishu", project_id=inferred_project_id, source_chat_id=source_chat_id, conversation_key=conversation_key, ) qa_capture = await self._build_direct_qa_capture( message=message, rag_result=rag_result, inferred_project_id=inferred_project_id, feishu_message_id=feishu_message_id, process_started_at=process_started_at, route_reason="intent gate 判定为 query,直接走 RAG 直答通道,不进入决策队列。", intent_label=INTENT_QUERY, ) await self.db.commit() return self._decorate_capture_result( qa_capture, chat_type=normalized_chat_type, normalized_text=message, route=f"{normalized_chat_type}.intent_gate.query", db_mutations=[], ) if gate_intent == INTENT_CREATE_PROJECT or ( gate_intent == INTENT_CLARIFICATION_REQUIRED and str(intent_gate.get("canonical_action") or "") == "project.create" ): project_capture = await self._handle_create_project_gate( message=message, sender=sender, source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, feishu_message_id=feishu_message_id, captured_at=captured_at, intent_gate=intent_gate, process_started_at=process_started_at, ) await self.db.commit() return self._decorate_capture_result( project_capture, chat_type=normalized_chat_type, normalized_text=message, route=f"{normalized_chat_type}.intent_gate.project_create", db_mutations=self._summarize_db_mutations(project_capture), ) if gate_intent == INTENT_CREATE_TASK or ( gate_intent == INTENT_CLARIFICATION_REQUIRED and str(intent_gate.get("canonical_action") or "") == "task.create" ): task_capture = await self._handle_create_task_gate( message=message, sender=sender, source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, feishu_message_id=feishu_message_id, captured_at=captured_at, intent_gate=intent_gate, process_started_at=process_started_at, ) await self.db.commit() return self._decorate_capture_result( task_capture, chat_type=normalized_chat_type, normalized_text=message, route=f"{normalized_chat_type}.intent_gate.task_create", db_mutations=self._summarize_db_mutations(task_capture), ) if gate_intent == INTENT_EXTERNAL_CONNECT: external_capture = await self._handle_external_connect_gate( message=message, sender=sender, source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, feishu_message_id=feishu_message_id, captured_at=captured_at, intent_gate=intent_gate, process_started_at=process_started_at, ) await self.db.commit() return self._decorate_capture_result( external_capture, chat_type=normalized_chat_type, normalized_text=message, route=f"{normalized_chat_type}.intent_gate.external_connect", db_mutations=self._summarize_db_mutations(external_capture), ) command_capture = await self._maybe_block_legacy_command_request( message=message, sender=sender, source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, feishu_message_id=feishu_message_id, captured_at=captured_at, inferred_project_id=inferred_project_id, projects=projects, process_started_at=process_started_at, ) if command_capture is not None: await self.db.commit() return self._decorate_capture_result( command_capture, chat_type=normalized_chat_type, normalized_text=message, route=f"{normalized_chat_type}.command", db_mutations=self._summarize_db_mutations(command_capture), ) return None