122 lines
3.6 KiB
Python
122 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
import tempfile
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
|
|
REPO_ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
EVENTS_DIR = REPO_ROOT / "tools" / "orchestrator-matrix" / "events"
|
|
if str(EVENTS_DIR) not in sys.path:
|
|
sys.path.insert(0, str(EVENTS_DIR))
|
|
|
|
|
|
@dataclass
|
|
class TempPaths:
|
|
root: pathlib.Path
|
|
events_path: pathlib.Path
|
|
cursor_path: pathlib.Path
|
|
config_path: pathlib.Path
|
|
|
|
|
|
def make_temp_paths() -> tuple[tempfile.TemporaryDirectory[str], TempPaths]:
|
|
tempdir = tempfile.TemporaryDirectory()
|
|
root = pathlib.Path(tempdir.name)
|
|
orch_dir = root / ".mosaic" / "orchestrator"
|
|
return tempdir, TempPaths(
|
|
root=root,
|
|
events_path=orch_dir / "events.ndjson",
|
|
cursor_path=orch_dir / "event_cursor.json",
|
|
config_path=orch_dir / "config.json",
|
|
)
|
|
|
|
|
|
def sample_events() -> list[dict[str, Any]]:
|
|
return [
|
|
{
|
|
"event_type": "task.assigned",
|
|
"task_id": "TASK-001",
|
|
"message": "Assigned to codex",
|
|
"metadata": {"runtime": "codex", "dispatch": "exec"},
|
|
},
|
|
{
|
|
"event_type": "task.started",
|
|
"task_id": "TASK-001",
|
|
"message": "Worker execution started",
|
|
"metadata": {"runtime": "codex", "dispatch": "exec", "attempt": 1, "max_attempts": 3},
|
|
},
|
|
{
|
|
"event_type": "task.completed",
|
|
"task_id": "TASK-001",
|
|
"message": "Completed successfully",
|
|
"metadata": {"task_title": "Finish test suite", "duration_seconds": 12.4},
|
|
},
|
|
{
|
|
"event_type": "task.failed",
|
|
"task_id": "TASK-002",
|
|
"message": "Exploded with stack trace",
|
|
"metadata": {"attempt": 2, "max_attempts": 3},
|
|
},
|
|
{
|
|
"event_type": "task.escalated",
|
|
"task_id": "TASK-003",
|
|
"message": "Human review required",
|
|
"metadata": {"attempt": 1},
|
|
},
|
|
{
|
|
"event_type": "task.gated",
|
|
"task_id": "TASK-004",
|
|
"message": "Waiting for quality gates",
|
|
"metadata": {},
|
|
},
|
|
{
|
|
"event_type": "task.retry.scheduled",
|
|
"task_id": "TASK-002",
|
|
"message": "Retry scheduled",
|
|
"metadata": {"attempt": 3, "max_attempts": 3},
|
|
},
|
|
]
|
|
|
|
|
|
def sample_config(
|
|
*,
|
|
enabled: bool = True,
|
|
event_filter: list[str] | None = None,
|
|
url: str = "https://hooks.example.com/macp",
|
|
retry_count: int = 1,
|
|
timeout_seconds: float = 2.0,
|
|
auth_token: str = "token-123",
|
|
) -> dict[str, Any]:
|
|
return {
|
|
"macp": {
|
|
"watch_poll_interval_seconds": 0.1,
|
|
"webhook": {
|
|
"enabled": enabled,
|
|
"url": url,
|
|
"event_filter": list(event_filter or []),
|
|
"retry_count": retry_count,
|
|
"timeout_seconds": timeout_seconds,
|
|
"auth_token": auth_token,
|
|
},
|
|
}
|
|
}
|
|
|
|
|
|
def write_ndjson(path: pathlib.Path, events: list[dict[str, Any]], extra_lines: list[str] | None = None) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
lines = [json.dumps(event) for event in events]
|
|
if extra_lines:
|
|
lines.extend(extra_lines)
|
|
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
|
|
|
|
def append_ndjson(path: pathlib.Path, events: list[dict[str, Any]]) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
with path.open("a", encoding="utf-8") as handle:
|
|
for event in events:
|
|
handle.write(json.dumps(event))
|
|
handle.write("\n")
|