131 lines
4.7 KiB
Python
Executable File
131 lines
4.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Submit an OpenClaw/Codex task lifecycle event over HTTP or a polled inbox."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|
|
|
from services.aios_orchestrator_gateway.models import Event
|
|
from services.aios_orchestrator_gateway.securefs import atomic_write_bytes_shared, read_secure_json, read_secure_text
|
|
|
|
|
|
def parser() -> argparse.ArgumentParser:
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument("--project-key", required=True)
|
|
p.add_argument("--contract-commit", required=True)
|
|
p.add_argument("--event-id", required=True)
|
|
p.add_argument("--source-agent", required=True, choices=["openclaw", "codex", "gitea", "system"])
|
|
p.add_argument("--event-type", required=True)
|
|
p.add_argument("--work-unit-id", required=True)
|
|
p.add_argument(
|
|
"--status",
|
|
required=True,
|
|
choices=["READY", "RUNNING", "PASS", "BLOCKED", "FAIL", "TIMEOUT", "NOT_EXECUTED"],
|
|
)
|
|
p.add_argument("--state-version", type=int, required=True)
|
|
p.add_argument("--implementation-commit")
|
|
p.add_argument("--result-commit")
|
|
p.add_argument("--blocker")
|
|
p.add_argument("--evidence-ref", action="append", default=[])
|
|
p.add_argument("--payload-file", type=Path)
|
|
p.add_argument("--endpoint")
|
|
p.add_argument("--token-file", type=Path)
|
|
p.add_argument("--inbox-dir", type=Path)
|
|
p.add_argument("--timeout-seconds", type=int, default=15)
|
|
return p
|
|
|
|
|
|
def main() -> int:
|
|
args = parser().parse_args()
|
|
if not args.endpoint and not args.inbox_dir:
|
|
raise SystemExit("either --endpoint or --inbox-dir is required")
|
|
if args.endpoint and not args.token_file:
|
|
raise SystemExit("--token-file is required with --endpoint")
|
|
payload = {}
|
|
if args.payload_file:
|
|
if not args.payload_file.is_absolute():
|
|
raise SystemExit("--payload-file must be absolute")
|
|
payload = read_secure_json(
|
|
args.payload_file, expected_uid=os.getuid(), max_bytes=262_144
|
|
)
|
|
if not isinstance(payload, dict):
|
|
raise SystemExit("payload root must be an object")
|
|
|
|
event = Event.from_dict(
|
|
{
|
|
"schema_version": 1,
|
|
"project_key": args.project_key,
|
|
"contract_commit": args.contract_commit,
|
|
"event_id": args.event_id,
|
|
"source_agent": args.source_agent,
|
|
"event_type": args.event_type,
|
|
"work_unit_id": args.work_unit_id,
|
|
"status": args.status,
|
|
"state_version": args.state_version,
|
|
"implementation_commit": args.implementation_commit,
|
|
"result_commit": args.result_commit,
|
|
"blocker": args.blocker,
|
|
"evidence_refs": args.evidence_ref,
|
|
"payload": payload,
|
|
}
|
|
)
|
|
encoded = event.canonical_json().encode()
|
|
http_error: Exception | None = None
|
|
|
|
if args.endpoint:
|
|
token = read_secure_text(
|
|
args.token_file,
|
|
expected_uid=None,
|
|
max_bytes=64_000,
|
|
)
|
|
request = urllib.request.Request(
|
|
args.endpoint.rstrip("/") + "/v1/orchestrator/events",
|
|
data=encoded,
|
|
method="POST",
|
|
headers={
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json",
|
|
"Idempotency-Key": event.event_id,
|
|
"User-Agent": "aios-agent-event-reporter/0.2",
|
|
},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=args.timeout_seconds) as response:
|
|
response.read(65_536)
|
|
if response.status not in {200, 202}:
|
|
raise RuntimeError(f"unexpected HTTP {response.status}")
|
|
print(f"EVENT_SUBMIT=PASS event_id={event.event_id} channel=http")
|
|
return 0
|
|
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, OSError) as exc:
|
|
http_error = exc
|
|
|
|
if args.inbox_dir:
|
|
pending = args.inbox_dir / "pending"
|
|
path = pending / f"{event.event_id}.json"
|
|
atomic_write_bytes_shared(
|
|
path,
|
|
(json.dumps(event.to_dict(), indent=2, sort_keys=True) + "\n").encode(),
|
|
mode=0o640,
|
|
group_writable_directory=True,
|
|
)
|
|
print(f"EVENT_SUBMIT=PASS event_id={event.event_id} channel=file_inbox")
|
|
return 0
|
|
|
|
print(
|
|
f"EVENT_SUBMIT=FAIL event_id={event.event_id} reason={type(http_error).__name__}",
|
|
file=sys.stderr,
|
|
)
|
|
return 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|