OpenClaw agent sends a message to a person or another agent. Routes automatically based on target type: - Agent target: triggers LLM processing, reply returned via next poll - Human target: sends via available channel (feishu, etc.)
(
body: GatewaySendMessageRequest,
x_api_key: str = Header(..., alias="X-Api-Key"),
db: AsyncSession = Depends(get_db),
)
| 471 | |
| 472 | @router.post("/send-message") |
| 473 | async def send_message( |
| 474 | body: GatewaySendMessageRequest, |
| 475 | x_api_key: str = Header(..., alias="X-Api-Key"), |
| 476 | db: AsyncSession = Depends(get_db), |
| 477 | ): |
| 478 | """OpenClaw agent sends a message to a person or another agent. |
| 479 | |
| 480 | Routes automatically based on target type: |
| 481 | - Agent target: triggers LLM processing, reply returned via next poll |
| 482 | - Human target: sends via available channel (feishu, etc.) |
| 483 | """ |
| 484 | agent = await _get_agent_by_key(x_api_key, db) |
| 485 | agent.openclaw_last_seen = datetime.now(timezone.utc) |
| 486 | |
| 487 | target_name = body.target.strip() |
| 488 | content = body.content.strip() |
| 489 | channel_hint = (body.channel or "").strip().lower() |
| 490 | |
| 491 | # 1. Try to find target as another Agent, limited to active relationships. |
| 492 | from app.models.org import AgentAgentRelationship |
| 493 | from sqlalchemy.orm import selectinload |
| 494 | |
| 495 | rel_result = await db.execute( |
| 496 | select(AgentAgentRelationship) |
| 497 | .where(AgentAgentRelationship.agent_id == agent.id) |
| 498 | .options(selectinload(AgentAgentRelationship.target_agent)) |
| 499 | ) |
| 500 | target_agent = None |
| 501 | for rel in rel_result.scalars().all(): |
| 502 | candidate = rel.target_agent |
| 503 | if not candidate: |
| 504 | continue |
| 505 | status_info = await evaluate_agent_relationship_status(db, rel) |
| 506 | if status_info["access_status"] != "active": |
| 507 | continue |
| 508 | if candidate.name.lower() == target_name.lower() or target_name.lower() in candidate.name.lower(): |
| 509 | target_agent = candidate |
| 510 | break |
| 511 | |
| 512 | logger.info(f"[Gateway] send_message: target='{target_name}', found_agent={target_agent.name if target_agent else None}, agent_type={getattr(target_agent, 'agent_type', None) if target_agent else None}, channel_hint='{channel_hint}'") |
| 513 | |
| 514 | if target_agent and (not channel_hint or channel_hint == "agent"): |
| 515 | conv_id = f"gw_agent_{agent.id}_{target_agent.id}" |
| 516 | |
| 517 | if getattr(target_agent, 'agent_type', None) == 'openclaw': |
| 518 | # OpenClaw-to-OpenClaw: write to gateway_messages directly |
| 519 | gw_msg = GatewayMessage( |
| 520 | agent_id=target_agent.id, |
| 521 | sender_agent_id=agent.id, |
| 522 | content=content, |
| 523 | status="pending", |
| 524 | conversation_id=conv_id, |
| 525 | ) |
| 526 | db.add(gw_msg) |
| 527 | await db.commit() |
| 528 | return { |
| 529 | "status": "accepted", |
| 530 | "target": target_agent.name, |
nothing calls this directly
no test coverage detected