Produce the `` `` envelope for a terminal task. Pure function — no registry mutation, no queue push. ``enqueue_agent_notification`` composes this with the WI-3.2 check-and-set; tests use it directly for snapshot comparisons. Format matches TS LocalAgentTask.tsx:25
(
*,
task_id: str,
description: str,
status: NotificationStatus,
output_file: str,
error: str | None = None,
final_message: str | None = None,
usage: dict[str, int] | None = None,
tool_use_id: str | None = None,
)
| 51 | |
| 52 | |
| 53 | def build_task_notification_xml( |
| 54 | *, |
| 55 | task_id: str, |
| 56 | description: str, |
| 57 | status: NotificationStatus, |
| 58 | output_file: str, |
| 59 | error: str | None = None, |
| 60 | final_message: str | None = None, |
| 61 | usage: dict[str, int] | None = None, |
| 62 | tool_use_id: str | None = None, |
| 63 | ) -> str: |
| 64 | """Produce the ``<task-notification>`` envelope for a terminal task. |
| 65 | |
| 66 | Pure function — no registry mutation, no queue push. ``enqueue_agent_notification`` |
| 67 | composes this with the WI-3.2 check-and-set; tests use it directly |
| 68 | for snapshot comparisons. |
| 69 | |
| 70 | Format matches TS LocalAgentTask.tsx:252-257 byte-for-byte (modulo |
| 71 | optional sections that disappear when their inputs are absent). All |
| 72 | values are rendered as-is (no XML escaping) — the chapter shape |
| 73 | treats these as model-facing user content, and TS does the same |
| 74 | raw concatenation. Callers are responsible for not embedding |
| 75 | closing tags inside summary/result text. |
| 76 | """ |
| 77 | summary = _build_summary(description, status, error) |
| 78 | tool_use_line = ( |
| 79 | f"\n<{TOOL_USE_ID_TAG}>{tool_use_id}</{TOOL_USE_ID_TAG}>" |
| 80 | if tool_use_id |
| 81 | else "" |
| 82 | ) |
| 83 | result_section = ( |
| 84 | f"\n<{RESULT_TAG}>{final_message}</{RESULT_TAG}>" |
| 85 | if final_message |
| 86 | else "" |
| 87 | ) |
| 88 | if usage is not None: |
| 89 | usage_section = ( |
| 90 | f"\n<{USAGE_TAG}>" |
| 91 | f"<{TOTAL_TOKENS_TAG}>{usage.get('total_tokens', 0)}</{TOTAL_TOKENS_TAG}>" |
| 92 | f"<{TOOL_USES_TAG}>{usage.get('tool_uses', 0)}</{TOOL_USES_TAG}>" |
| 93 | f"<{DURATION_MS_TAG}>{usage.get('duration_ms', 0)}</{DURATION_MS_TAG}>" |
| 94 | f"</{USAGE_TAG}>" |
| 95 | ) |
| 96 | else: |
| 97 | usage_section = "" |
| 98 | return ( |
| 99 | f"<{TASK_NOTIFICATION_TAG}>\n" |
| 100 | f"<{TASK_ID_TAG}>{task_id}</{TASK_ID_TAG}>{tool_use_line}\n" |
| 101 | f"<{OUTPUT_FILE_TAG}>{output_file}</{OUTPUT_FILE_TAG}>\n" |
| 102 | f"<{STATUS_TAG}>{status}</{STATUS_TAG}>\n" |
| 103 | f"<{SUMMARY_TAG}>{summary}</{SUMMARY_TAG}>{result_section}{usage_section}\n" |
| 104 | f"</{TASK_NOTIFICATION_TAG}>" |
| 105 | ) |
| 106 | |
| 107 | |
| 108 | def enqueue_agent_notification( |