feat: implement comment posting functionality and notify lead on task unassignment
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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 two‑step sequence:
|
||||
6a) Post the full response as a markdown comment using:
|
||||
POST $BASE_URL/api/v1/agent/boards/{BOARD_ID}/tasks/{TASK_ID}/comments
|
||||
|
||||
Reference in New Issue
Block a user