from __future__ import annotations import hashlib from asgiref.sync import async_to_sync from core.clients.transport import send_message_raw from core.models import CodexPermissionRequest, ExternalSyncEvent, TaskProviderConfig def _deterministic_approval_key(idempotency_key: str) -> str: digest = hashlib.sha1(str(idempotency_key or "").encode("utf-8")).hexdigest()[:12] return f"pre-{digest}" def queue_codex_event_with_pre_approval( *, user, run, task, task_event, action: str, provider_payload: dict, idempotency_key: str, provider: str = "codex_cli", ) -> tuple[ExternalSyncEvent, CodexPermissionRequest]: provider = str(provider or "codex_cli").strip() or "codex_cli" approval_key = _deterministic_approval_key(idempotency_key) waiting_event, _ = ExternalSyncEvent.objects.update_or_create( idempotency_key=f"codex_waiting:{idempotency_key}", defaults={ "user": user, "task": task, "task_event": task_event, "provider": provider, "status": "waiting_approval", "payload": { "action": str(action or "append_update"), "provider_payload": dict(provider_payload or {}), }, "error": "", }, ) run.status = "waiting_approval" run.error = "" run.save(update_fields=["status", "error", "updated_at"]) provider_label = "Claude" if provider == "claude_cli" else "Codex" xmpp_cmd = ".claude" if provider == "claude_cli" else ".codex" request, _ = CodexPermissionRequest.objects.update_or_create( approval_key=approval_key, defaults={ "user": user, "codex_run": run, "external_sync_event": waiting_event, "summary": f"Pre-submit approval required before sending to {provider_label}", "requested_permissions": { "type": "pre_submit", "provider": provider, "action": str(action or "append_update"), }, "resume_payload": { "gate_type": "pre_submit", "action": str(action or "append_update"), "provider_payload": dict(provider_payload or {}), "idempotency_key": str(idempotency_key or ""), }, "status": "pending", "resolved_at": None, "resolved_by_identifier": "", "resolution_note": "", }, ) cfg = TaskProviderConfig.objects.filter( user=user, provider=provider, enabled=True ).first() settings_payload = dict(getattr(cfg, "settings", {}) or {}) approver_service = ( str(settings_payload.get("approver_service") or "").strip().lower() ) approver_identifier = str(settings_payload.get("approver_identifier") or "").strip() if approver_service and approver_identifier: try: async_to_sync(send_message_raw)( approver_service, approver_identifier, text=( f"[{provider} approval] key={approval_key}\n" f"summary=Pre-submit approval required before sending to {provider_label}\n" "requested=pre_submit\n" f"use: {xmpp_cmd} approve {approval_key} or {xmpp_cmd} deny {approval_key}" ), attachments=[], metadata={"origin_tag": f"codex-pre-approval:{approval_key}"}, ) except Exception: pass return waiting_event, request