207 lines
9.9 KiB
Python
207 lines
9.9 KiB
Python
"""Feishu signal pipeline mixin."""
|
|
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
|
|
|
|
class FeishuPipelineGateTaskMixin:
|
|
async def _handle_create_task_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]:
|
|
fields = intent_gate.get("fields") if isinstance(intent_gate.get("fields"), dict) else {}
|
|
operation_payload = {
|
|
"project_id": _safe_int(fields.get("project_id")),
|
|
"title": str(fields.get("title") or "").strip(),
|
|
"description": str(fields.get("description") or "").strip() or None,
|
|
}
|
|
clarification_message = str(intent_gate.get("clarification_template") or "").strip() or self._build_task_clarification_message(
|
|
list(fields.get("missing_fields") or []),
|
|
)
|
|
occurred_at = captured_at or _utc_now()
|
|
if occurred_at.tzinfo is None:
|
|
occurred_at = occurred_at.replace(tzinfo=CN_TZ)
|
|
|
|
operator_user = await self._resolve_command_operator_user(sender=sender)
|
|
if not fields.get("ready"):
|
|
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=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_CLARIFICATION,
|
|
routing_reason="建任务缺少必填字段,已转待澄清,禁止猜测执行。",
|
|
reasoning_bullets=[
|
|
"intent gate 命中 create_task。",
|
|
"当前缺少 task.create 必填字段。",
|
|
"仅保留结构化待澄清项,不触发执行器。",
|
|
],
|
|
processing_status="success",
|
|
error_message=None,
|
|
duration_ms=int((time.perf_counter() - process_started_at) * 1000),
|
|
normalized_text=message,
|
|
route=ROUTING_INTENT_GATE_CLARIFICATION,
|
|
intent=INTENT_CLARIFICATION_REQUIRED,
|
|
executor="explicit_service",
|
|
operation_code="task.create",
|
|
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-task-clarify-{(feishu_message_id or 'noid')[:32]}",
|
|
"results": [],
|
|
"intent": INTENT_CLARIFICATION_REQUIRED,
|
|
"clarification_message": clarification_message,
|
|
"direct_reply_required": True,
|
|
"direct_reply_text": clarification_message,
|
|
}
|
|
|
|
if not self._can_execute_high_value_action(operator_user):
|
|
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="task.create",
|
|
operation_payload={k: v for k, v in operation_payload.items() if v is not None},
|
|
blocking_reason=clarification_message if not fields.get("ready") else "建任务属于高风险动作,当前需要你确认后再执行。",
|
|
decision_reason="high_value_action_guard",
|
|
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_CLARIFICATION,
|
|
routing_reason="建任务命中高风险保护,已转待决策/待澄清。",
|
|
reasoning_bullets=[
|
|
"intent gate 命中 create_task。",
|
|
"当前未满足自动执行条件。",
|
|
"仅保留结构化待决策项,不做猜测执行。",
|
|
],
|
|
processing_status="success",
|
|
error_message=None,
|
|
duration_ms=int((time.perf_counter() - process_started_at) * 1000),
|
|
normalized_text=message,
|
|
route=ROUTING_INTENT_GATE_CLARIFICATION,
|
|
intent=INTENT_CLARIFICATION_REQUIRED if not fields.get("ready") else INTENT_CREATE_TASK,
|
|
executor="explicit_service",
|
|
operation_code="task.create",
|
|
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-task-pending-{queued.get('item_id')}",
|
|
"results": [queued],
|
|
"intent": INTENT_CLARIFICATION_REQUIRED if not fields.get("ready") else INTENT_CREATE_TASK,
|
|
"clarification_message": queued.get("clarification_message"),
|
|
"direct_reply_required": True,
|
|
"direct_reply_text": str(queued.get("clarification_message") or "已转待决策。"),
|
|
}
|
|
|
|
trace_id = f"intent-task-{self._stable_positive_int(message, sender, feishu_message_id)}"
|
|
execution = await self._execute_controlled_operation(
|
|
operator_user=operator_user,
|
|
operation_code="task.create",
|
|
payload={k: v for k, v in operation_payload.items() if v is not None},
|
|
trace_id=trace_id,
|
|
)
|
|
first = execution["results"][0] if execution.get("results") else {}
|
|
status = str(first.get("status") or "").strip().lower()
|
|
if status not in {"applied", "skipped"}:
|
|
raise ValueError(str(first.get("message") or "task.create failed"))
|
|
project_id = _safe_int(fields.get("project_id"))
|
|
item_id = await self._record_committed_operation_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,
|
|
captured_at=captured_at,
|
|
intent=INTENT_CREATE_TASK,
|
|
operation_code="task.create",
|
|
execution_result=first,
|
|
matched_project_id=project_id,
|
|
)
|
|
reply_text = self._build_operation_commit_reply({"execution": {"results": [first], "applied_count": 1 if status == "applied" else 0, "skipped_count": 1 if status == "skipped" else 0, "failed_count": 0}})
|
|
await self._write_processing_log(
|
|
feishu_message_id=feishu_message_id,
|
|
feishu_item_id=item_id,
|
|
raw_message=message,
|
|
ai_raw_output=json.dumps({"intent_gate": intent_gate, "execution": execution}, ensure_ascii=False)[:120000],
|
|
project_score=1.0 if project_id is not None else None,
|
|
signal_confidence=0.0 if project_id is not None else None,
|
|
level="command",
|
|
routing_decision=ROUTING_OPERATION_COMMITTED,
|
|
routing_reason="建任务已通过 deterministic operation executor 提交,并以真实 DB 结果回执。",
|
|
reasoning_bullets=[
|
|
"intent gate 命中 create_task。",
|
|
"执行器仅走 task.create。",
|
|
"回复绑定真实 commit 结果。",
|
|
],
|
|
processing_status="success",
|
|
error_message=None,
|
|
duration_ms=int((time.perf_counter() - process_started_at) * 1000),
|
|
normalized_text=message,
|
|
route=ROUTING_OPERATION_COMMITTED,
|
|
intent=INTENT_CREATE_TASK,
|
|
executor="explicit_service",
|
|
operation_code="task.create",
|
|
operation_result=first if isinstance(first, dict) else None,
|
|
reply_source="db_commit",
|
|
)
|
|
return {
|
|
"status": "captured",
|
|
"classification": "command",
|
|
"project_scope": "command",
|
|
"linked_project_ids": [int(project_id)] if project_id is not None else [],
|
|
"signal_id": None,
|
|
"created_project_id": None,
|
|
"auto_tasks": [],
|
|
"ai_trace_id": trace_id,
|
|
"results": [
|
|
{
|
|
"item_id": item_id,
|
|
"level": "command",
|
|
"project_id": int(project_id) if project_id is not None else None,
|
|
"signal_id": None,
|
|
"route_status": ROUTE_STATUS_AUTO_DONE,
|
|
}
|
|
],
|
|
"intent": INTENT_CREATE_TASK,
|
|
"direct_reply_required": True,
|
|
"direct_reply_text": reply_text,
|
|
"command_result": {"execution": execution},
|
|
}
|
|
|