from __future__ import annotations from .shared import * class SignalCapturePipelineCacheMixin: async def _sync_feishu_recent_cache( self, *, tenant_id: int, group_chat_id: str, sender: str, sender_open_id: Optional[str], message: str, pipeline_result: Dict[str, Any], ) -> None: if not isinstance(pipeline_result, dict): return conversation_key = _build_feishu_rag_conversation_key( source_chat_id=group_chat_id, source_sender_open_id=sender_open_id, sender=sender, ) external_session_id = AdaptiveRAGService._external_session_id_from_key(conversation_key) if external_session_id is None: return answer_text = str(pipeline_result.get("direct_reply_text") or "").strip() if not answer_text: answer_text = self._build_recent_cache_ack_text(pipeline_result) if not answer_text: return recent_messages = await context_cache_service.get_session_messages( tenant_id=int(tenant_id), session_id=int(external_session_id), ) recent_turns = context_cache_service.messages_to_turns(recent_messages) if recent_turns: latest_turn = recent_turns[-1] latest_question = str(latest_turn.get("question") or "").strip() latest_answer = str(latest_turn.get("answer") or "").strip() if latest_question == str(message or "").strip() and latest_answer == answer_text: return await context_cache_service.append_session_messages( tenant_id=int(tenant_id), session_id=int(external_session_id), question=str(message or "")[:3000], answer=answer_text[:4000], ) @staticmethod def _build_recent_cache_ack_text(pipeline_result: Dict[str, Any]) -> str: status = str(pipeline_result.get("status") or "").strip().lower() if status == "captured": return "已收到并记录该需求,已进入信号处理队列。" if status == "ignored": return "已收到该消息,本次未进入需求处理队列。" if status == "error": return str(pipeline_result.get("direct_reply_text") or "系统处理中出现异常,请稍后重试。").strip() return "已收到该消息。" async def _apply_pipeline_post_actions( self, *, trace_id: str, tenant_id: int, cleaned_message: str, proposer_name: str, captured_at: datetime, pipeline_result: Dict[str, Any], ) -> Dict[str, Any]: if not isinstance(pipeline_result, dict): return pipeline_result if str(pipeline_result.get("status") or "").lower() != "captured": return pipeline_result existing_tasks = pipeline_result.get("auto_tasks") if isinstance(existing_tasks, list) and existing_tasks: return pipeline_result linked_project_ids = pipeline_result.get("linked_project_ids") if not isinstance(linked_project_ids, list) or not linked_project_ids: return pipeline_result signal_id = self._to_int(pipeline_result.get("signal_id")) project_id = self._to_int(linked_project_ids[0]) if signal_id is None or project_id is None: return pipeline_result signal = ( await self.db.execute( select(Signal).where( and_( Signal.id == signal_id, Signal.tenant_id == tenant_id, ) ) ) ).scalar_one_or_none() if signal is None: return pipeline_result if self._is_question_like(cleaned_message): return pipeline_result source_orchestration_result: Optional[Dict[str, Any]] = None if SignalSourceOrchestrationService.looks_like_signal_source_request(cleaned_message): try: source_orchestration_result = await SignalSourceOrchestrationService(self.db).orchestrate_from_signal( tenant_id=tenant_id, project_id=project_id, signal_id=signal_id, initiating_text=cleaned_message, requested_via="feishu", operator_user_id=None, dry_run=False, ) except Exception as exc: self._emit_ai_think( "pipeline_post_source_onboard_failed", trace_id=trace_id, signal_id=signal_id, project_id=project_id, error=str(exc), ) else: orchestration_status = str(source_orchestration_result.get("status") or "").lower() if orchestration_status in {"completed", "partial", "planned", "pending_manual"}: auto_tasks = self._build_openclaw_auto_tasks( source_orchestration_result.get("execution") if isinstance(source_orchestration_result.get("execution"), dict) else source_orchestration_result.get("openclaw"), ) try: strategic_payload: Dict[str, Any] = {} raw_strategic = signal.strategic_analysis if isinstance(raw_strategic, dict): strategic_payload = dict(raw_strategic) elif isinstance(raw_strategic, str) and raw_strategic.strip(): parsed = json.loads(raw_strategic) if isinstance(parsed, dict): strategic_payload = parsed strategic_payload["signal_source_orchestration"] = source_orchestration_result strategic_payload["ai_execution"] = source_orchestration_result signal.strategic_analysis = json.dumps(strategic_payload, ensure_ascii=False) signal.tasks_generated = json.dumps(auto_tasks, ensure_ascii=False) signal.processed = orchestration_status in {"completed", "partial"} await self.db.commit() except Exception as exc: self._emit_ai_think( "pipeline_post_update_signal_failed", trace_id=trace_id, signal_id=signal_id, project_id=project_id, error=str(exc), ) pipeline_result["auto_tasks"] = auto_tasks pipeline_result["ai_execution"] = source_orchestration_result pipeline_result["signal_source_orchestration"] = source_orchestration_result return pipeline_result signal_type = str(signal.signal_type or "").strip() recommended_action = str(signal.recommended_action or "").strip() should_orchestrate = ( recommended_action in {"create_charter", "trigger_alert"} or signal_type == "新需求" ) if not should_orchestrate: return pipeline_result classification_payload: Dict[str, Any] = { "classification": str(pipeline_result.get("classification") or "signal"), "impact_score": float(signal.impact_score or 0.0), "priority": str(signal.priority or "medium"), "confidence": float(signal.confidence or 0.0), "reason": str(signal.summary or "")[:300], "matched_project_ids": [project_id], "matched_project_titles": [], } try: auto_tasks, ai_execution = await self._auto_orchestrate_project_tasks( trace_id=f"{trace_id}-pipeline", tenant_id=tenant_id, project_id=project_id, signal_id=signal_id, cleaned_message=cleaned_message, classification=classification_payload, proposer_name=proposer_name, captured_at=captured_at, ) except Exception as exc: self._emit_ai_think( "pipeline_post_orchestrate_failed", trace_id=trace_id, signal_id=signal_id, project_id=project_id, error=str(exc), ) return pipeline_result try: strategic_payload: Dict[str, Any] = {} raw_strategic = signal.strategic_analysis if isinstance(raw_strategic, dict): strategic_payload = dict(raw_strategic) elif isinstance(raw_strategic, str) and raw_strategic.strip(): parsed = json.loads(raw_strategic) if isinstance(parsed, dict): strategic_payload = parsed strategic_payload["ai_execution"] = ai_execution signal.strategic_analysis = json.dumps(strategic_payload, ensure_ascii=False) signal.tasks_generated = json.dumps(auto_tasks, ensure_ascii=False) signal.processed = bool(signal.processed or auto_tasks) await self.db.commit() except Exception as exc: self._emit_ai_think( "pipeline_post_update_signal_failed", trace_id=trace_id, signal_id=signal_id, project_id=project_id, error=str(exc), ) pipeline_result["auto_tasks"] = auto_tasks pipeline_result["ai_execution"] = ai_execution return pipeline_result @staticmethod def _build_openclaw_auto_tasks(openclaw_payload: Any) -> List[Dict[str, Any]]: if not isinstance(openclaw_payload, dict): return [] result = openclaw_payload.get("result") if isinstance(openclaw_payload.get("result"), dict) else openclaw_payload execution = result.get("execution") if isinstance(result, dict) and isinstance(result.get("execution"), dict) else {} rows = execution.get("results") if isinstance(execution.get("results"), list) else [] items: List[Dict[str, Any]] = [] for row in rows: if not isinstance(row, dict): continue operation_code = str(row.get("operation_code") or "").strip() payload = row.get("payload") if isinstance(row.get("payload"), dict) else {} status = str(row.get("status") or "").strip() if operation_code not in {"task.create", "task.assign", "task.prioritize", "task.status_transition"}: continue if status not in {"applied", "planned", "skipped"}: continue items.append( { "operation_code": operation_code, "status": status, "entity_id": row.get("entity_id"), "task_id": payload.get("task_id"), "title": payload.get("title"), "summary": str(row.get("message") or "").strip(), } ) return items