Implement executing tasks

This commit is contained in:
2026-03-03 16:41:28 +00:00
parent d6bd56dace
commit 9c14e51b43
42 changed files with 3410 additions and 121 deletions

View File

@@ -24,8 +24,9 @@ from core.models import (
PersonIdentifier,
PlatformChatLink,
Chat,
ExternalChatLink,
)
from core.tasks.providers.mock import get_provider
from core.tasks.providers import get_provider
SAFE_TASK_FLAGS_DEFAULTS = {
"derive_enabled": True,
@@ -170,6 +171,13 @@ def _service_label(service: str) -> str:
return labels.get(key, key.title() if key else "Unknown")
def _provider_row_map(user):
return {
str(row.provider or "").strip().lower(): row
for row in TaskProviderConfig.objects.filter(user=user).order_by("provider")
}
def _resolve_channel_display(user, service: str, identifier: str) -> dict:
service_key = str(service or "").strip().lower()
raw_identifier = str(identifier or "").strip()
@@ -408,12 +416,33 @@ class TaskSettings(LoginRequiredMixin, View):
for row in sources:
row.settings_effective = _flags_with_defaults(row.settings)
row.allowed_prefixes_csv = ",".join(row.settings_effective["allowed_prefixes"])
provider_map = _provider_row_map(request.user)
codex_cfg = provider_map.get("codex_cli")
codex_settings = dict(getattr(codex_cfg, "settings", {}) or {})
mock_cfg = provider_map.get("mock")
external_chat_links = list(
ExternalChatLink.objects.filter(user=request.user).select_related(
"person", "person_identifier"
).order_by("-updated_at")[:200]
)
return {
"projects": projects,
"epics": TaskEpic.objects.filter(project__user=request.user).select_related("project").order_by("project__name", "name"),
"sources": sources,
"patterns": TaskCompletionPattern.objects.filter(user=request.user).order_by("position", "created_at"),
"provider_configs": TaskProviderConfig.objects.filter(user=request.user).order_by("provider"),
"provider_configs": list(provider_map.values()),
"mock_provider_config": mock_cfg,
"codex_provider_config": codex_cfg,
"codex_provider_settings": {
"command": str(codex_settings.get("command") or "codex"),
"workspace_root": str(codex_settings.get("workspace_root") or ""),
"default_profile": str(codex_settings.get("default_profile") or ""),
"timeout_seconds": int(codex_settings.get("timeout_seconds") or 60),
"chat_link_mode": str(codex_settings.get("chat_link_mode") or "task-sync"),
},
"person_identifiers": PersonIdentifier.objects.filter(user=request.user).select_related("person").order_by("person__name", "service", "identifier")[:600],
"external_chat_links": external_chat_links,
"sync_events": ExternalSyncEvent.objects.filter(user=request.user).order_by("-updated_at")[:100],
"prefill_service": prefill_service,
"prefill_identifier": prefill_identifier,
@@ -537,18 +566,81 @@ class TaskSettings(LoginRequiredMixin, View):
defaults={"enabled": False, "settings": {}},
)
row.enabled = bool(request.POST.get("enabled"))
row.save(update_fields=["enabled", "updated_at"])
settings_payload = dict(row.settings or {})
if provider == "codex_cli":
timeout_raw = str(request.POST.get("timeout_seconds") or "60").strip()
try:
timeout_value = max(1, int(timeout_raw))
except Exception:
timeout_value = 60
settings_payload = {
"command": str(request.POST.get("command") or "codex").strip() or "codex",
"workspace_root": str(request.POST.get("workspace_root") or "").strip(),
"default_profile": str(request.POST.get("default_profile") or "").strip(),
"timeout_seconds": timeout_value,
"chat_link_mode": "task-sync",
}
row.settings = settings_payload
row.save(update_fields=["enabled", "settings", "updated_at"])
return _settings_redirect(request)
if action == "external_chat_link_upsert":
provider = str(request.POST.get("provider") or "codex_cli").strip().lower() or "codex_cli"
external_chat_id = str(request.POST.get("external_chat_id") or "").strip()
person_identifier_id = str(request.POST.get("person_identifier_id") or "").strip()
if not external_chat_id:
messages.error(request, "External chat ID is required.")
return _settings_redirect(request)
identifier = None
if person_identifier_id:
identifier = get_object_or_404(
PersonIdentifier,
user=request.user,
id=person_identifier_id,
)
row, _ = ExternalChatLink.objects.update_or_create(
user=request.user,
provider=provider,
external_chat_id=external_chat_id,
defaults={
"person": getattr(identifier, "person", None),
"person_identifier": identifier,
"enabled": bool(request.POST.get("enabled")),
"metadata": {
"chat_link_mode": "task-sync",
"notes": str(request.POST.get("metadata_notes") or "").strip(),
},
},
)
if identifier and row.person_id != identifier.person_id:
row.person = identifier.person
row.save(update_fields=["person", "updated_at"])
return _settings_redirect(request)
if action == "external_chat_link_delete":
row = get_object_or_404(
ExternalChatLink,
id=request.POST.get("external_link_id"),
user=request.user,
)
row.delete()
return _settings_redirect(request)
if action == "sync_retry":
event = get_object_or_404(ExternalSyncEvent, id=request.POST.get("event_id"), user=request.user)
provider = get_provider(event.provider)
payload = dict(event.payload or {})
result = provider.append_update({}, payload)
event.status = "ok" if result.ok else "failed"
event.error = str(result.error or "")
event.payload = dict(payload, retried=True)
event.save(update_fields=["status", "error", "payload", "updated_at"])
if bool(getattr(provider, "run_in_worker", False)):
event.status = "pending"
event.error = ""
event.payload = dict(event.payload or {}, retried=True)
event.save(update_fields=["status", "error", "payload", "updated_at"])
else:
payload = dict(event.payload or {})
result = provider.append_update({}, payload)
event.status = "ok" if result.ok else "failed"
event.error = str(result.error or "")
event.payload = dict(payload, retried=True)
event.save(update_fields=["status", "error", "payload", "updated_at"])
return _settings_redirect(request)
return _settings_redirect(request)