Initial commit
This commit is contained in:
@@ -0,0 +1,522 @@
|
||||
"""Company work-item read service."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter, defaultdict
|
||||
from typing import Any
|
||||
|
||||
from opc.layer2_organization.phase import coerce_phase, kanban_column, should_hide_work_item_from_company_kanban
|
||||
from opc.layer2_organization.work_item_links import task_by_linked_work_item_id
|
||||
|
||||
from .context import OfficeServiceContext
|
||||
from .models import ServiceError, ServiceResult
|
||||
|
||||
|
||||
class WorkItemService:
|
||||
def __init__(self, context: OfficeServiceContext) -> None:
|
||||
self.context = context
|
||||
|
||||
async def list(
|
||||
self,
|
||||
*,
|
||||
project_id: str,
|
||||
session_id: str | None = None,
|
||||
role_id: str | None = None,
|
||||
status: str | None = None,
|
||||
limit: int = 100,
|
||||
kanban_visible_only: bool = False,
|
||||
) -> ServiceResult:
|
||||
session_id = str(session_id or "").strip()
|
||||
engine = await self.context.engine_for_project(project_id)
|
||||
store = getattr(engine, "store", None)
|
||||
if not self.context.store_is_ready(store):
|
||||
raise ServiceError("store_not_ready", "store_not_ready", {"project_id": project_id})
|
||||
runs = await self._runs(store, project_id, session_id=session_id or None)
|
||||
tasks = await store.get_tasks(project_id=project_id) if hasattr(store, "get_tasks") else []
|
||||
hydrate = getattr(store, "hydrate_task_work_item_links", None)
|
||||
if callable(hydrate):
|
||||
await hydrate(tasks)
|
||||
linked_tasks = task_by_linked_work_item_id(tasks)
|
||||
|
||||
rows: list[dict[str, Any]] = []
|
||||
for run in runs:
|
||||
run_id = str(getattr(run, "run_id", "") or "")
|
||||
if not run_id:
|
||||
continue
|
||||
for item in await store.list_delegation_work_items(run_id):
|
||||
linked_task = linked_tasks.get(str(getattr(item, "work_item_id", "") or ""))
|
||||
if session_id and not self._matches_session_scope(session_id, run=run, item=item, linked_task=linked_task):
|
||||
continue
|
||||
if kanban_visible_only and not self._is_company_kanban_visible(item):
|
||||
continue
|
||||
payload = self._work_item_payload(item, run=run, linked_task=linked_task)
|
||||
if role_id and payload.get("role_id") != role_id:
|
||||
continue
|
||||
if status and payload.get("phase") != status and payload.get("kanban_column") != status:
|
||||
continue
|
||||
rows.append(payload)
|
||||
rows.sort(key=lambda item: str(item.get("updated_at", "")), reverse=True)
|
||||
return ServiceResult({
|
||||
"project_id": project_id,
|
||||
"session_id": session_id,
|
||||
"work_items": rows[: max(1, int(limit or 100))],
|
||||
})
|
||||
|
||||
async def show(self, *, project_id: str, work_item_id: str, limit: int = 100) -> ServiceResult:
|
||||
engine = await self.context.engine_for_project(project_id)
|
||||
store = getattr(engine, "store", None)
|
||||
if not self.context.store_is_ready(store):
|
||||
raise ServiceError("store_not_ready", "store_not_ready", {"project_id": project_id})
|
||||
runs = await self._runs(store, project_id)
|
||||
tasks = await store.get_tasks(project_id=project_id) if hasattr(store, "get_tasks") else []
|
||||
hydrate = getattr(store, "hydrate_task_work_item_links", None)
|
||||
if callable(hydrate):
|
||||
await hydrate(tasks)
|
||||
linked_tasks = task_by_linked_work_item_id(tasks)
|
||||
target = None
|
||||
target_run = None
|
||||
for run in runs:
|
||||
run_id = str(getattr(run, "run_id", "") or "")
|
||||
for item in await store.list_delegation_work_items(run_id):
|
||||
if str(getattr(item, "work_item_id", "") or "") == work_item_id:
|
||||
target = item
|
||||
target_run = run
|
||||
break
|
||||
if target is not None:
|
||||
break
|
||||
if target is None:
|
||||
raise ServiceError("work_item_not_found", "Work item not found", {"work_item_id": work_item_id})
|
||||
linked_task = linked_tasks.get(work_item_id)
|
||||
logs = await self.logs(project_id=project_id, work_item_id=work_item_id, limit=limit)
|
||||
return ServiceResult({
|
||||
"project_id": project_id,
|
||||
"work_item": self._work_item_payload(target, run=target_run, linked_task=linked_task),
|
||||
"logs": logs.payload,
|
||||
})
|
||||
|
||||
async def logs(
|
||||
self,
|
||||
*,
|
||||
project_id: str,
|
||||
session_id: str | None = None,
|
||||
work_item_id: str = "",
|
||||
role_id: str = "",
|
||||
limit: int = 100,
|
||||
) -> ServiceResult:
|
||||
session_id = str(session_id or "").strip()
|
||||
work_item_id = str(work_item_id or "").strip()
|
||||
role_id = str(role_id or "").strip()
|
||||
engine = await self.context.engine_for_project(project_id)
|
||||
store = getattr(engine, "store", None)
|
||||
if not self.context.store_is_ready(store):
|
||||
raise ServiceError("store_not_ready", "store_not_ready", {"project_id": project_id})
|
||||
runs = await self._runs(store, project_id, session_id=session_id or None)
|
||||
tasks = await store.get_tasks(project_id=project_id) if hasattr(store, "get_tasks") else []
|
||||
hydrate = getattr(store, "hydrate_task_work_item_links", None)
|
||||
if callable(hydrate):
|
||||
await hydrate(tasks)
|
||||
linked_tasks = task_by_linked_work_item_id(tasks)
|
||||
|
||||
target_items: list[tuple[Any, Any, Any | None]] = []
|
||||
target_work_item_ids: set[str] = set()
|
||||
for run in runs:
|
||||
run_id = str(getattr(run, "run_id", "") or "")
|
||||
if not run_id:
|
||||
continue
|
||||
for item in await store.list_delegation_work_items(run_id):
|
||||
item_id = str(getattr(item, "work_item_id", "") or "")
|
||||
if work_item_id and item_id != work_item_id:
|
||||
continue
|
||||
if role_id and str(getattr(item, "role_id", "") or "") != role_id:
|
||||
continue
|
||||
linked_task = linked_tasks.get(item_id)
|
||||
target_items.append((item, run, linked_task))
|
||||
if item_id:
|
||||
target_work_item_ids.add(item_id)
|
||||
|
||||
events: list[dict[str, Any]] = []
|
||||
target_run_ids = {str(getattr(run, "run_id", "") or "") for run in runs}
|
||||
for run_id in target_run_ids:
|
||||
if hasattr(store, "list_delegation_events"):
|
||||
for event in await store.list_delegation_events(run_id):
|
||||
event_work_item_id = str(getattr(event, "work_item_id", "") or "")
|
||||
event_role_id = str(getattr(event, "role_id", "") or "")
|
||||
if work_item_id and event_work_item_id != work_item_id:
|
||||
continue
|
||||
if role_id and event_role_id != role_id and event_work_item_id not in target_work_item_ids:
|
||||
continue
|
||||
events.append(self._event_payload(event))
|
||||
|
||||
runtime_sessions: list[dict[str, Any]] = []
|
||||
external_sessions: list[dict[str, Any]] = []
|
||||
runtime_events: list[dict[str, Any]] = []
|
||||
runtime_transcript_entries: list[dict[str, Any]] = []
|
||||
runtime_tool_calls: list[dict[str, Any]] = []
|
||||
runtime_tool_results: list[dict[str, Any]] = []
|
||||
runtime_permission_grants: list[dict[str, Any]] = []
|
||||
transcript: list[Any] = []
|
||||
handoffs: list[Any] = []
|
||||
|
||||
runtime_rows_by_id: dict[str, dict[str, Any]] = {}
|
||||
runtime_ids: set[str] = set()
|
||||
external_keys: set[tuple[str, str, str]] = set()
|
||||
|
||||
def add_runtime_row(row: Any) -> None:
|
||||
payload = self._model_payload(row)
|
||||
runtime_id = str(payload.get("runtime_session_id", "") or "").strip()
|
||||
if not runtime_id:
|
||||
return
|
||||
runtime_rows_by_id[runtime_id] = payload
|
||||
runtime_ids.add(runtime_id)
|
||||
|
||||
def add_runtime_id(runtime_id: Any, *, task: Any = None, role_session: Any = None, source: str = "") -> None:
|
||||
runtime_id = str(runtime_id or "").strip()
|
||||
if not runtime_id:
|
||||
return
|
||||
runtime_ids.add(runtime_id)
|
||||
if runtime_id in runtime_rows_by_id:
|
||||
return
|
||||
metadata = {"source": source} if source else {}
|
||||
if role_session is not None:
|
||||
metadata.update({"source": source or "role_runtime_session", "role_id": str(getattr(role_session, "role_id", "") or "")})
|
||||
runtime_rows_by_id[runtime_id] = {
|
||||
"runtime_session_id": runtime_id,
|
||||
"project_id": project_id,
|
||||
"session_id": str(getattr(task, "session_id", "") or session_id or ""),
|
||||
"task_id": str(getattr(task, "id", "") or ""),
|
||||
"status": str(getattr(role_session, "status", "") or ""),
|
||||
"metadata": metadata,
|
||||
"created_at": self._date_value(getattr(role_session, "created_at", None)),
|
||||
"updated_at": self._date_value(getattr(role_session, "updated_at", None)),
|
||||
}
|
||||
|
||||
def add_external_session(row: Any) -> None:
|
||||
payload = self._model_payload(row)
|
||||
key = (
|
||||
str(payload.get("agent_type", "") or ""),
|
||||
str(payload.get("session_id", "") or ""),
|
||||
str(payload.get("task_id", "") or ""),
|
||||
)
|
||||
if key in external_keys:
|
||||
return
|
||||
external_keys.add(key)
|
||||
external_sessions.append(payload)
|
||||
metadata = dict(payload.get("metadata", {}) or {})
|
||||
add_runtime_id(metadata.get("runtime_session_id"), source="external_session")
|
||||
add_runtime_id(metadata.get("delegation_role_session_id"), source="external_role_session")
|
||||
add_runtime_id(payload.get("opc_session_id"), source="external_opc_session")
|
||||
|
||||
for item, run, linked_task in target_items:
|
||||
item_id = str(getattr(item, "work_item_id", "") or "")
|
||||
if linked_task is not None:
|
||||
linked_session_id = str(getattr(linked_task, "session_id", "") or "")
|
||||
if linked_session_id and hasattr(store, "get_session_transcript"):
|
||||
transcript.extend((await store.get_session_transcript(linked_session_id))[-limit:])
|
||||
if hasattr(store, "list_runtime_sessions"):
|
||||
for row in await store.list_runtime_sessions(project_id=project_id, task_id=getattr(linked_task, "id", ""), limit=limit):
|
||||
add_runtime_row(row)
|
||||
if linked_session_id:
|
||||
for row in await store.list_runtime_sessions(project_id=project_id, session_id=linked_session_id, limit=limit):
|
||||
add_runtime_row(row)
|
||||
if hasattr(store, "list_external_sessions"):
|
||||
for row in await store.list_external_sessions(project_id=project_id, task_id=getattr(linked_task, "id", ""), limit=limit):
|
||||
add_external_session(row)
|
||||
for runtime_id in self._task_runtime_session_ids(linked_task):
|
||||
add_runtime_id(runtime_id, task=linked_task, source="task_metadata")
|
||||
for runtime_id in self._work_item_runtime_session_ids(item):
|
||||
add_runtime_id(runtime_id, task=linked_task, source="work_item")
|
||||
if hasattr(store, "get_handoff_records") and item_id:
|
||||
handoffs.extend(await store.get_handoff_records(project_id=project_id, target_work_item_id=item_id, limit=limit))
|
||||
|
||||
if hasattr(store, "list_role_runtime_sessions"):
|
||||
for run in runs:
|
||||
run_id = str(getattr(run, "run_id", "") or "")
|
||||
if not run_id:
|
||||
continue
|
||||
try:
|
||||
role_sessions = await store.list_role_runtime_sessions(run_id, role_id=role_id or None)
|
||||
except TypeError:
|
||||
role_sessions = await store.list_role_runtime_sessions(run_id)
|
||||
if role_id:
|
||||
role_sessions = [item for item in role_sessions if str(getattr(item, "role_id", "") or "") == role_id]
|
||||
for role_session in role_sessions:
|
||||
role_session_id = str(getattr(role_session, "role_session_id", "") or "")
|
||||
focused = str(getattr(role_session, "focused_work_item_id", "") or "")
|
||||
related = {focused}
|
||||
related.update(str(item or "").strip() for item in list(getattr(role_session, "background_work_item_ids", []) or []))
|
||||
related.update(str(item or "").strip() for item in list(getattr(role_session, "pending_work_item_ids", []) or []))
|
||||
if work_item_id and work_item_id not in related:
|
||||
continue
|
||||
add_runtime_id(role_session_id, role_session=role_session, source="role_runtime_session")
|
||||
if hasattr(store, "list_external_sessions") and role_session_id:
|
||||
for row in await store.list_external_sessions(project_id=project_id, opc_session_id=role_session_id, limit=limit):
|
||||
add_external_session(row)
|
||||
|
||||
for runtime_id in sorted(runtime_ids):
|
||||
if hasattr(store, "list_runtime_events"):
|
||||
runtime_events.extend(await store.list_runtime_events(runtime_id, limit=limit))
|
||||
if hasattr(store, "list_runtime_transcript_entries"):
|
||||
runtime_transcript_entries.extend((await store.list_runtime_transcript_entries(runtime_id))[-limit:])
|
||||
if hasattr(store, "list_runtime_tool_calls"):
|
||||
runtime_tool_calls.extend((await store.list_runtime_tool_calls(runtime_id))[-limit:])
|
||||
if hasattr(store, "list_runtime_tool_results"):
|
||||
runtime_tool_results.extend((await store.list_runtime_tool_results(runtime_id))[-limit:])
|
||||
if hasattr(store, "list_runtime_permission_grants"):
|
||||
runtime_permission_grants.extend((await store.list_runtime_permission_grants(runtime_session_id=runtime_id))[-limit:])
|
||||
|
||||
events.sort(key=lambda item: str(item.get("created_at", "")))
|
||||
runtime_sessions = list(runtime_rows_by_id.values())
|
||||
runtime_sessions.sort(key=lambda item: str(item.get("updated_at", "")), reverse=True)
|
||||
return ServiceResult({
|
||||
"project_id": project_id,
|
||||
"session_id": session_id,
|
||||
"work_item_id": work_item_id,
|
||||
"role_id": role_id,
|
||||
"work_items": [
|
||||
self._work_item_payload(item, run=run, linked_task=linked_task)
|
||||
for item, run, linked_task in target_items
|
||||
][:limit],
|
||||
"events": events[-limit:],
|
||||
"runtime_sessions": runtime_sessions[:limit],
|
||||
"external_sessions": external_sessions[-limit:],
|
||||
"runtime_events": runtime_events[-limit:],
|
||||
"runtime_transcript_entries": runtime_transcript_entries[-limit:],
|
||||
"runtime_tool_calls": runtime_tool_calls[-limit:],
|
||||
"runtime_tool_results": runtime_tool_results[-limit:],
|
||||
"runtime_permission_grants": runtime_permission_grants[-limit:],
|
||||
"transcript": transcript,
|
||||
"handoffs": [self._model_payload(item) for item in handoffs],
|
||||
})
|
||||
|
||||
async def status_by_role(self, *, project_id: str, session_id: str | None = None) -> ServiceResult:
|
||||
listing = await self.list(project_id=project_id, session_id=session_id, limit=10000)
|
||||
by_role: dict[str, Counter[str]] = defaultdict(Counter)
|
||||
for item in listing.payload.get("work_items", []):
|
||||
role_id = str(item.get("role_id") or "unassigned")
|
||||
by_role[role_id][str(item.get("kanban_column") or item.get("phase") or "unknown")] += 1
|
||||
return ServiceResult({
|
||||
"project_id": project_id,
|
||||
"session_id": str(session_id or "").strip(),
|
||||
"roles": [
|
||||
{"role_id": role_id, "counts": dict(counts), "total": sum(counts.values())}
|
||||
for role_id, counts in sorted(by_role.items())
|
||||
],
|
||||
})
|
||||
|
||||
async def role_detail(self, *, project_id: str, role_id: str, limit: int = 100) -> ServiceResult:
|
||||
role_id = str(role_id or "").strip()
|
||||
if not role_id:
|
||||
raise ServiceError("role_id_required", "role_id required")
|
||||
listing = await self.list(project_id=project_id, role_id=role_id, limit=limit)
|
||||
logs = await self.logs(project_id=project_id, role_id=role_id, limit=limit)
|
||||
counts: Counter[str] = Counter()
|
||||
for item in listing.payload.get("work_items", []):
|
||||
counts[str(item.get("kanban_column") or item.get("phase") or "unknown")] += 1
|
||||
return ServiceResult({
|
||||
"project_id": project_id,
|
||||
"role_id": role_id,
|
||||
"counts": dict(counts),
|
||||
"work_items": listing.payload.get("work_items", []),
|
||||
"logs": logs.payload,
|
||||
})
|
||||
|
||||
async def _runs(self, store: Any, project_id: str, *, session_id: str | None = None) -> list[Any]:
|
||||
session_id = str(session_id or "").strip()
|
||||
if hasattr(store, "list_open_delegation_runs"):
|
||||
runs = await store.list_open_delegation_runs(project_id=project_id)
|
||||
if session_id:
|
||||
matched = [run for run in runs if self._session_id_matches(session_id, getattr(run, "session_id", ""))]
|
||||
if matched:
|
||||
return list(matched)
|
||||
elif runs:
|
||||
return list(runs)
|
||||
if hasattr(store, "list_delegation_runs"):
|
||||
if session_id:
|
||||
for candidate_session_id in self._session_scope_candidates(session_id):
|
||||
try:
|
||||
runs = await store.list_delegation_runs(project_id=project_id, session_id=candidate_session_id)
|
||||
except TypeError:
|
||||
break
|
||||
if runs:
|
||||
return list(runs)
|
||||
runs = list(await store.list_delegation_runs(project_id=project_id))
|
||||
if session_id:
|
||||
return [run for run in runs if self._session_id_matches(session_id, getattr(run, "session_id", ""))]
|
||||
return runs
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _is_company_kanban_visible(item: Any) -> bool:
|
||||
metadata = dict(getattr(item, "metadata", {}) or {})
|
||||
return bool(
|
||||
str(getattr(item, "parent_work_item_id", "") or "").strip()
|
||||
and not bool(metadata.get("attention_work_item", False))
|
||||
and not should_hide_work_item_from_company_kanban(metadata)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _matches_session_scope(cls, session_id: str, *, run: Any = None, item: Any = None, linked_task: Any = None) -> bool:
|
||||
session_id = str(session_id or "").strip()
|
||||
if not session_id:
|
||||
return True
|
||||
for candidate in cls._session_candidates(run=run, item=item, linked_task=linked_task):
|
||||
if cls._session_id_matches(session_id, candidate):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _session_scope_candidates(session_id: str) -> list[str]:
|
||||
session_id = str(session_id or "").strip()
|
||||
if not session_id:
|
||||
return []
|
||||
candidates = [session_id]
|
||||
if ":" in session_id:
|
||||
candidates.append(session_id.split(":", 1)[0])
|
||||
return list(dict.fromkeys(candidates))
|
||||
|
||||
@classmethod
|
||||
def _session_candidates(cls, *, run: Any = None, item: Any = None, linked_task: Any = None) -> list[str]:
|
||||
values: list[str] = []
|
||||
|
||||
def add(value: Any) -> None:
|
||||
text = str(value or "").strip()
|
||||
if text:
|
||||
values.append(text)
|
||||
|
||||
def add_from_mapping(mapping: Any) -> None:
|
||||
if not isinstance(mapping, dict):
|
||||
return
|
||||
for key in (
|
||||
"session_id",
|
||||
"parent_session_id",
|
||||
"root_session_id",
|
||||
"origin_session_id",
|
||||
"opc_session_id",
|
||||
"company_runtime_root_session_id",
|
||||
):
|
||||
add(mapping.get(key))
|
||||
|
||||
if run is not None:
|
||||
add(getattr(run, "session_id", ""))
|
||||
add_from_mapping(getattr(run, "metadata", {}) or {})
|
||||
add_from_mapping(getattr(run, "recovery_pointer", {}) or {})
|
||||
if item is not None:
|
||||
add_from_mapping(getattr(item, "metadata", {}) or {})
|
||||
if linked_task is not None:
|
||||
add(getattr(linked_task, "session_id", ""))
|
||||
add(getattr(linked_task, "parent_session_id", ""))
|
||||
add_from_mapping(getattr(linked_task, "metadata", {}) or {})
|
||||
return list(dict.fromkeys(values))
|
||||
|
||||
@staticmethod
|
||||
def _session_id_matches(scope_session_id: str, candidate_session_id: Any) -> bool:
|
||||
scope = str(scope_session_id or "").strip()
|
||||
candidate = str(candidate_session_id or "").strip()
|
||||
if not scope or not candidate:
|
||||
return False
|
||||
return (
|
||||
scope == candidate
|
||||
or candidate.startswith(f"{scope}:")
|
||||
or scope.startswith(f"{candidate}:")
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _task_runtime_session_ids(cls, task: Any) -> list[str]:
|
||||
metadata = dict(getattr(task, "metadata", {}) or {})
|
||||
context_snapshot = dict(getattr(task, "context_snapshot", {}) or {})
|
||||
values = [
|
||||
(metadata.get("runtime_v2", {}) or {}).get("runtime_session_id") if isinstance(metadata.get("runtime_v2"), dict) else "",
|
||||
(context_snapshot.get("runtime_resume", {}) or {}).get("runtime_session_id") if isinstance(context_snapshot.get("runtime_resume"), dict) else "",
|
||||
metadata.get("_permission_bridge_runtime_session_id"),
|
||||
metadata.get("delegation_role_session_id"),
|
||||
metadata.get("assigned_role_runtime_id"),
|
||||
metadata.get("role_runtime_session_id"),
|
||||
metadata.get("runtime_session_id"),
|
||||
]
|
||||
return cls._dedupe_text(values)
|
||||
|
||||
@classmethod
|
||||
def _work_item_runtime_session_ids(cls, item: Any) -> list[str]:
|
||||
metadata = dict(getattr(item, "metadata", {}) or {})
|
||||
values = [
|
||||
getattr(item, "role_runtime_session_id", ""),
|
||||
getattr(item, "claimed_by_role_runtime_session_id", ""),
|
||||
metadata.get("assigned_role_runtime_id"),
|
||||
metadata.get("role_runtime_session_id"),
|
||||
metadata.get("claimed_by_role_runtime_session_id"),
|
||||
metadata.get("delegation_role_session_id"),
|
||||
metadata.get("runtime_session_id"),
|
||||
]
|
||||
return cls._dedupe_text(values)
|
||||
|
||||
@staticmethod
|
||||
def _dedupe_text(values: list[Any]) -> list[str]:
|
||||
result: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for value in values:
|
||||
text = str(value or "").strip()
|
||||
if not text or text in seen:
|
||||
continue
|
||||
seen.add(text)
|
||||
result.append(text)
|
||||
return result
|
||||
|
||||
def _work_item_payload(self, item: Any, *, run: Any = None, linked_task: Any = None) -> dict[str, Any]:
|
||||
phase = coerce_phase(getattr(item, "phase", ""))
|
||||
phase_value = phase.value if hasattr(phase, "value") else str(phase or "")
|
||||
return {
|
||||
"work_item_id": str(getattr(item, "work_item_id", "") or ""),
|
||||
"run_id": str(getattr(item, "run_id", "") or ""),
|
||||
"project_id": str(getattr(run, "project_id", "") or ""),
|
||||
"title": str(getattr(item, "title", "") or ""),
|
||||
"summary": str(getattr(item, "summary", "") or ""),
|
||||
"role_id": str(getattr(item, "role_id", "") or ""),
|
||||
"seat_id": str(getattr(item, "seat_id", "") or ""),
|
||||
"manager_role_id": str(getattr(item, "manager_role_id", "") or ""),
|
||||
"parent_work_item_id": str(getattr(item, "parent_work_item_id", "") or ""),
|
||||
"role_runtime_session_id": str(getattr(item, "role_runtime_session_id", "") or ""),
|
||||
"claimed_by_role_runtime_session_id": str(getattr(item, "claimed_by_role_runtime_session_id", "") or ""),
|
||||
"phase": phase_value,
|
||||
"kanban_column": kanban_column(phase),
|
||||
"deliverable_summary": str(getattr(item, "deliverable_summary", "") or ""),
|
||||
"blocked_reason": str(getattr(item, "blocked_reason", "") or ""),
|
||||
"handoff_status": str(getattr(item, "handoff_status", "") or ""),
|
||||
"metadata": dict(getattr(item, "metadata", {}) or {}),
|
||||
"runtime_task_id": str(getattr(linked_task, "id", "") or "") if linked_task is not None else "",
|
||||
"session_id": str(getattr(linked_task, "session_id", "") or "") if linked_task is not None else "",
|
||||
"runtime_status": (
|
||||
getattr(getattr(linked_task, "status", None), "value", getattr(linked_task, "status", ""))
|
||||
if linked_task is not None else ""
|
||||
),
|
||||
"created_at": self._date_value(getattr(item, "created_at", None)),
|
||||
"updated_at": self._date_value(getattr(item, "updated_at", None)),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _event_payload(event: Any) -> dict[str, Any]:
|
||||
return {
|
||||
"event_id": str(getattr(event, "event_id", "") or ""),
|
||||
"run_id": str(getattr(event, "run_id", "") or ""),
|
||||
"work_item_id": str(getattr(event, "work_item_id", "") or ""),
|
||||
"cell_id": str(getattr(event, "cell_id", "") or ""),
|
||||
"role_id": str(getattr(event, "role_id", "") or ""),
|
||||
"event_type": str(getattr(event, "event_type", "") or ""),
|
||||
"payload": dict(getattr(event, "payload", {}) or {}),
|
||||
"created_at": WorkItemService._date_value(getattr(event, "created_at", None)),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _model_payload(value: Any) -> dict[str, Any]:
|
||||
if hasattr(value, "model_dump"):
|
||||
return value.model_dump()
|
||||
if hasattr(value, "__dict__"):
|
||||
return dict(value.__dict__)
|
||||
if isinstance(value, dict):
|
||||
return dict(value)
|
||||
return {"value": value}
|
||||
|
||||
@staticmethod
|
||||
def _date_value(value: Any) -> Any:
|
||||
if hasattr(value, "isoformat"):
|
||||
return value.isoformat()
|
||||
return value
|
||||
Reference in New Issue
Block a user