96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
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
|