feat: implement comment posting functionality and notify lead on task unassignment

This commit is contained in:
Abhimanyu Saharan
2026-02-05 23:19:46 +05:30
parent fa91352430
commit 51b352df45
3 changed files with 132 additions and 1 deletions

View File

@@ -277,6 +277,65 @@ def _notify_lead_on_task_create(
session.commit()
def _notify_lead_on_task_unassigned(
*,
session: Session,
board: Board,
task: Task,
) -> None:
lead = session.exec(
select(Agent)
.where(Agent.board_id == board.id)
.where(Agent.is_board_lead.is_(True))
).first()
if lead is None or not lead.openclaw_session_id:
return
config = _gateway_config(session, board)
if config is None:
return
description = (task.description or "").strip()
if len(description) > 500:
description = f"{description[:497]}..."
details = [
f"Board: {board.name}",
f"Task: {task.title}",
f"Task ID: {task.id}",
f"Status: {task.status}",
]
if description:
details.append(f"Description: {description}")
message = (
"TASK BACK IN INBOX\n"
+ "\n".join(details)
+ "\n\nTake action: assign a new owner or adjust the plan."
)
try:
asyncio.run(
_send_lead_task_message(
session_key=lead.openclaw_session_id,
config=config,
message=message,
)
)
record_activity(
session,
event_type="task.lead_unassigned_notified",
message=f"Lead notified task returned to inbox: {task.title}.",
agent_id=lead.id,
task_id=task.id,
)
session.commit()
except OpenClawGatewayError as exc:
record_activity(
session,
event_type="task.lead_unassigned_notify_failed",
message=f"Lead notify failed: {exc}",
agent_id=lead.id,
task_id=task.id,
)
session.commit()
@router.get("/stream")
async def stream_tasks(
request: Request,
@@ -509,6 +568,15 @@ def update_task(
agent_id=actor.agent.id if actor.actor_type == "agent" and actor.agent else None,
)
session.commit()
if task.status == "inbox" and task.assigned_agent_id is None:
if previous_status != "inbox" or previous_assigned is not None:
board = session.get(Board, task.board_id) if task.board_id else None
if board:
_notify_lead_on_task_unassigned(
session=session,
board=board,
task=task,
)
if task.assigned_agent_id and task.assigned_agent_id != previous_assigned:
if (
actor.actor_type == "agent"

View File

@@ -130,6 +130,9 @@ export default function BoardDetailPage() {
const [comments, setComments] = useState<TaskComment[]>([]);
const [isCommentsLoading, setIsCommentsLoading] = useState(false);
const [commentsError, setCommentsError] = useState<string | null>(null);
const [newComment, setNewComment] = useState("");
const [isPostingComment, setIsPostingComment] = useState(false);
const [postCommentError, setPostCommentError] = useState<string | null>(null);
const [isDetailOpen, setIsDetailOpen] = useState(false);
const tasksRef = useRef<Task[]>([]);
const approvalsRef = useRef<Approval[]>([]);
@@ -777,9 +780,48 @@ export default function BoardDetailPage() {
setSelectedTask(null);
setComments([]);
setCommentsError(null);
setNewComment("");
setPostCommentError(null);
setIsEditDialogOpen(false);
};
const handlePostComment = async () => {
if (!selectedTask || !boardId || !isSignedIn) return;
const trimmed = newComment.trim();
if (!trimmed) {
setPostCommentError("Write a message before sending.");
return;
}
setIsPostingComment(true);
setPostCommentError(null);
try {
const token = await getToken();
const response = await fetch(
`${apiBase}/api/v1/boards/${boardId}/tasks/${selectedTask.id}/comments`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: token ? `Bearer ${token}` : "",
},
body: JSON.stringify({ message: trimmed }),
},
);
if (!response.ok) {
throw new Error("Unable to send message.");
}
const created = (await response.json()) as TaskComment;
setComments((prev) => [created, ...prev]);
setNewComment("");
} catch (err) {
setPostCommentError(
err instanceof Error ? err.message : "Unable to send message.",
);
} finally {
setIsPostingComment(false);
}
};
const handleTaskSave = async (closeOnSuccess = false) => {
if (!selectedTask || !isSignedIn || !boardId) return;
const trimmedTitle = editTitle.trim();
@@ -1489,6 +1531,26 @@ export default function BoardDetailPage() {
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Comments
</p>
<div className="space-y-2 rounded-xl border border-slate-200 bg-slate-50 p-3">
<Textarea
value={newComment}
onChange={(event) => setNewComment(event.target.value)}
placeholder="Write a message for the assigned agent…"
className="min-h-[80px] bg-white"
/>
{postCommentError ? (
<p className="text-xs text-rose-600">{postCommentError}</p>
) : null}
<div className="flex justify-end">
<Button
size="sm"
onClick={handlePostComment}
disabled={isPostingComment || !newComment.trim()}
>
{isPostingComment ? "Sending…" : "Send message"}
</Button>
</div>
</div>
{isCommentsLoading ? (
<p className="text-sm text-slate-500">Loading comments</p>
) : commentsError ? (

View File

@@ -68,10 +68,11 @@ curl -s "$BASE_URL/api/v1/agent/boards/{BOARD_ID}/tasks?status=inbox&unassigned=
5) If you do NOT have an in_progress task:
- If you have **assigned inbox** tasks, move one to in_progress and add a markdown comment describing the update.
- Else if there are **unassigned inbox** tasks, claim one and move it to in_progress with a comment.
- If there are **unassigned inbox** tasks, do **not** claim them. Wait for the board lead to assign work.
6) Work the task:
- Post progress comments as you go.
- Before working, fetch the latest task comments and respond in the task thread if the human asked a question.
- Completion is a twostep sequence:
6a) Post the full response as a markdown comment using:
POST $BASE_URL/api/v1/agent/boards/{BOARD_ID}/tasks/{TASK_ID}/comments