Deliver a workflow's terminal result to the model via the shared `` `` queue (mirrors ``enqueue_agent_notification``). The ``notified`` flag is check-and-set atomically, so exactly one envelope is delivered even if complete/fail/kill race.
(
task_id: str,
registry: "RuntimeTaskRegistry",
*,
status: str,
error: str | None = None,
)
| 208 | |
| 209 | |
| 210 | def enqueue_workflow_notification( |
| 211 | task_id: str, |
| 212 | registry: "RuntimeTaskRegistry", |
| 213 | *, |
| 214 | status: str, |
| 215 | error: str | None = None, |
| 216 | ) -> bool: |
| 217 | """Deliver a workflow's terminal result to the model via the shared |
| 218 | ``<task-notification>`` queue (mirrors ``enqueue_agent_notification``). |
| 219 | |
| 220 | The ``notified`` flag is check-and-set atomically, so exactly one envelope |
| 221 | is delivered even if complete/fail/kill race.""" |
| 222 | from src.utils.message_queue_manager import enqueue_pending_notification |
| 223 | from src.utils.task_notification import build_task_notification_xml |
| 224 | |
| 225 | # Read the token total OUTSIDE the registry mutator: progress is mutated by |
| 226 | # the engine on its own daemon thread, so iterating it under the registry |
| 227 | # lock could raise "list changed size during iteration". _safe_token_total |
| 228 | # guards it; the mutator itself only flips a scalar flag (the stock-agent |
| 229 | # pattern in enqueue_agent_notification). |
| 230 | snapshot = registry.get(task_id) |
| 231 | tokens = _safe_token_total(getattr(snapshot, "progress", None)) if snapshot is not None else 0 |
| 232 | |
| 233 | captured: dict[str, Any] = {} |
| 234 | should_enqueue = False |
| 235 | |
| 236 | def _mark(prev: TaskStateBase) -> TaskStateBase: |
| 237 | nonlocal should_enqueue |
| 238 | if not isinstance(prev, LocalWorkflowTaskState) or prev.notified: |
| 239 | return prev |
| 240 | should_enqueue = True |
| 241 | captured.update( |
| 242 | name=prev.workflow_name or "workflow", |
| 243 | output_file=prev.output_file, |
| 244 | result=prev.result, |
| 245 | tool_use_id=prev.tool_use_id, |
| 246 | ) |
| 247 | return replace(prev, notified=True) |
| 248 | |
| 249 | registry.update(task_id, _mark) |
| 250 | if not should_enqueue: |
| 251 | return False |
| 252 | |
| 253 | final_message = _render_result(captured["result"]) if status == "completed" else None |
| 254 | xml = build_task_notification_xml( |
| 255 | task_id=task_id, |
| 256 | description=captured["name"], |
| 257 | status=status, # type: ignore[arg-type] |
| 258 | output_file=captured["output_file"], |
| 259 | error=error, |
| 260 | final_message=final_message, |
| 261 | usage={"total_tokens": tokens, "tool_uses": 0, "duration_ms": 0}, |
| 262 | tool_use_id=captured["tool_use_id"], |
| 263 | ) |
| 264 | enqueue_pending_notification(value=xml, mode="task-notification") |
| 265 | return True |
| 266 | |
| 267 |
no test coverage detected