feat: auto-trigger Discord wakeup when slot becomes ONGOING
This commit is contained in:
@@ -53,6 +53,7 @@ from app.services.agent_status import (
|
|||||||
transition_to_offline,
|
transition_to_offline,
|
||||||
transition_to_exhausted,
|
transition_to_exhausted,
|
||||||
)
|
)
|
||||||
|
from app.services.discord_wakeup import create_private_wakeup_channel
|
||||||
from app.services.minimum_workload import (
|
from app.services.minimum_workload import (
|
||||||
get_workload_config,
|
get_workload_config,
|
||||||
get_workload_warnings_for_date,
|
get_workload_warnings_for_date,
|
||||||
@@ -299,6 +300,45 @@ def _apply_agent_slot_update(slot: TimeSlot, payload: SlotAgentUpdate) -> None:
|
|||||||
slot.attended = True
|
slot.attended = True
|
||||||
|
|
||||||
|
|
||||||
|
def _maybe_trigger_discord_wakeup(db: Session, slot: TimeSlot) -> dict | None:
|
||||||
|
"""Trigger Discord wakeup if slot became ONGOING and not already sent."""
|
||||||
|
# Only trigger for ONGOING status and if not already sent
|
||||||
|
if slot.status != SlotStatus.ONGOING or slot.wakeup_sent_at is not None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get user and check for discord_user_id
|
||||||
|
user = db.query(User).filter(User.id == slot.user_id).first()
|
||||||
|
if not user or not user.discord_user_id:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get agent for this user
|
||||||
|
agent = db.query(Agent).filter(Agent.user_id == user.id).first()
|
||||||
|
agent_id_str = agent.agent_id if agent else "unknown"
|
||||||
|
|
||||||
|
# Build wakeup message
|
||||||
|
title = f"HarborForge Slot: {slot.event_type.value if slot.event_type else 'work'}"
|
||||||
|
message = (
|
||||||
|
f"🎯 **Slot started**\n"
|
||||||
|
f"Agent: `{agent_id_str}`\n"
|
||||||
|
f"Type: {slot.slot_type.value}\n"
|
||||||
|
f"Duration: {slot.estimated_duration}min\n"
|
||||||
|
f"Priority: {slot.priority}\n"
|
||||||
|
f"Use `hf calendar slot {slot.id}` for details."
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = create_private_wakeup_channel(
|
||||||
|
discord_user_id=user.discord_user_id,
|
||||||
|
title=title,
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
slot.wakeup_sent_at = datetime.now(timezone.utc)
|
||||||
|
return {"ok": True, "channel_id": result.get("channel_id")}
|
||||||
|
except Exception as e:
|
||||||
|
# Log but don't fail the slot update
|
||||||
|
return {"ok": False, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
@router.api_route(
|
@router.api_route(
|
||||||
"/agent/heartbeat",
|
"/agent/heartbeat",
|
||||||
methods=["GET", "POST"],
|
methods=["GET", "POST"],
|
||||||
@@ -338,6 +378,7 @@ def agent_update_real_slot(
|
|||||||
if slot is None:
|
if slot is None:
|
||||||
raise HTTPException(status_code=404, detail="Slot not found")
|
raise HTTPException(status_code=404, detail="Slot not found")
|
||||||
_apply_agent_slot_update(slot, payload)
|
_apply_agent_slot_update(slot, payload)
|
||||||
|
_maybe_trigger_discord_wakeup(db, slot)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(slot)
|
db.refresh(slot)
|
||||||
return TimeSlotEditResponse(slot=_slot_to_response(slot), warnings=[])
|
return TimeSlotEditResponse(slot=_slot_to_response(slot), warnings=[])
|
||||||
@@ -361,6 +402,7 @@ def agent_update_virtual_slot(
|
|||||||
db.rollback()
|
db.rollback()
|
||||||
raise HTTPException(status_code=404, detail="Slot not found")
|
raise HTTPException(status_code=404, detail="Slot not found")
|
||||||
_apply_agent_slot_update(slot, payload)
|
_apply_agent_slot_update(slot, payload)
|
||||||
|
_maybe_trigger_discord_wakeup(db, slot)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(slot)
|
db.refresh(slot)
|
||||||
return TimeSlotEditResponse(slot=_slot_to_response(slot), warnings=[])
|
return TimeSlotEditResponse(slot=_slot_to_response(slot), warnings=[])
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ def config_status():
|
|||||||
return {
|
return {
|
||||||
"initialized": cfg.get("initialized", False),
|
"initialized": cfg.get("initialized", False),
|
||||||
"backend_url": cfg.get("backend_url"),
|
"backend_url": cfg.get("backend_url"),
|
||||||
|
"discord": cfg.get("discord") or {},
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
return {"initialized": False}
|
return {"initialized": False}
|
||||||
@@ -358,6 +359,10 @@ def _migrate_schema():
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
|
# --- time_slots: add wakeup_sent_at for Discord wakeup tracking ---
|
||||||
|
if _has_table(db, "time_slots") and not _has_column(db, "time_slots", "wakeup_sent_at"):
|
||||||
|
db.execute(text("ALTER TABLE time_slots ADD COLUMN wakeup_sent_at DATETIME NULL"))
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
|
|||||||
@@ -165,6 +165,12 @@ class TimeSlot(Base):
|
|||||||
comment="Lifecycle status of this slot",
|
comment="Lifecycle status of this slot",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
wakeup_sent_at = Column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
nullable=True,
|
||||||
|
comment="When Discord wakeup was sent for this slot",
|
||||||
|
)
|
||||||
|
|
||||||
plan_id = Column(
|
plan_id = Column(
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("schedule_plans.id"),
|
ForeignKey("schedule_plans.id"),
|
||||||
|
|||||||
Reference in New Issue
Block a user