feat(ops): run artefacts in API/UI + llm-connect host access design
ACTIVITY-WP-0027: document durable ClusterIP host access for llm-connect; join activity_runs to ops_runs; surface Forgejo artefact links on /ops/ui automation detail and new run detail pages.
This commit is contained in:
parent
9965af2096
commit
7711bf9c70
6 changed files with 604 additions and 55 deletions
|
|
@ -28,7 +28,7 @@ from activity_core.automation_status import (
|
|||
)
|
||||
from activity_core.models import ActivityDefinition, CronTriggerConfig, ScheduledTriggerConfig
|
||||
from activity_core.orm import ActivityDefinition as ActivityDefinitionRow
|
||||
from activity_core.orm import ActivityRun, TaskSpawnLog
|
||||
from activity_core.orm import ActivityRun
|
||||
from activity_core.runtime_paths import custodian_working_memory_dir
|
||||
from activity_core.schedule_manager import pause_schedule, unpause_schedule, upsert_schedule
|
||||
from activity_core.state_hub_write import idempotency_headers, parse_state_hub_write_response
|
||||
|
|
@ -207,6 +207,9 @@ async def ops_runs(
|
|||
since: datetime | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
"""Recent activity_runs enriched with ops_runs + artefact links (WP-0027)."""
|
||||
from activity_core.run_artifacts import enrich_activity_runs
|
||||
|
||||
limit = max(1, min(limit, 200))
|
||||
async with session_factory() as session:
|
||||
stmt = (
|
||||
|
|
@ -218,54 +221,8 @@ async def ops_runs(
|
|||
if since is not None:
|
||||
stmt = stmt.where(ActivityRun.fired_at >= since)
|
||||
runs = list((await session.scalars(stmt)).all())
|
||||
|
||||
run_ids = [r.run_id for r in runs]
|
||||
spawn_by_run: dict[str, list[dict[str, Any]]] = {str(rid): [] for rid in run_ids}
|
||||
if run_ids:
|
||||
# Spawns keyed by triggering_event_id often equal run_id or workflow key;
|
||||
# also collect by activity_def_id for recent window.
|
||||
spawn_stmt = (
|
||||
select(TaskSpawnLog)
|
||||
.where(TaskSpawnLog.activity_def_id == definition_id)
|
||||
.order_by(TaskSpawnLog.id.desc())
|
||||
.limit(limit * 5)
|
||||
)
|
||||
for log in (await session.scalars(spawn_stmt)).all():
|
||||
entry = {
|
||||
"task_ref": log.task_ref,
|
||||
"source_type": log.source_type,
|
||||
"source_id": log.source_id,
|
||||
"triggering_event_id": log.triggering_event_id,
|
||||
"condition_matched": log.condition_matched,
|
||||
}
|
||||
# Attach if triggering_event_id matches a run_id string, else keep under activity.
|
||||
tid = log.triggering_event_id or ""
|
||||
matched = False
|
||||
for rid in run_ids:
|
||||
if tid == str(rid) or str(rid) in tid:
|
||||
spawn_by_run[str(rid)].append(entry)
|
||||
matched = True
|
||||
break
|
||||
if not matched and runs:
|
||||
# bucket orphan spawns onto most recent run for operator visibility
|
||||
spawn_by_run[str(runs[0].run_id)].append(entry)
|
||||
|
||||
items = []
|
||||
for r in runs:
|
||||
items.append(
|
||||
{
|
||||
"run_id": str(r.run_id),
|
||||
"activity_id": str(r.activity_id),
|
||||
"scheduled_for": r.scheduled_for.isoformat() if r.scheduled_for else None,
|
||||
"fired_at": r.fired_at.isoformat() if r.fired_at else None,
|
||||
"tasks_spawned": r.tasks_spawned,
|
||||
"version_used": r.version_used,
|
||||
"evidence": {
|
||||
"task_spawns": spawn_by_run.get(str(r.run_id), [])[:20],
|
||||
# context_snapshot may be large; only surface shallow keys
|
||||
"context_keys": sorted((r.context_snapshot or {}).keys())[:40],
|
||||
},
|
||||
}
|
||||
items = await enrich_activity_runs(
|
||||
session, definition_id, runs, since=since
|
||||
)
|
||||
return {
|
||||
"activity_id": str(definition_id),
|
||||
|
|
@ -274,6 +231,17 @@ async def ops_runs(
|
|||
}
|
||||
|
||||
|
||||
async def ops_run_detail(
|
||||
session_factory: async_sessionmaker[AsyncSession],
|
||||
definition_id: uuid.UUID,
|
||||
run_id: uuid.UUID,
|
||||
) -> dict[str, Any] | None:
|
||||
from activity_core.run_artifacts import get_enriched_run
|
||||
|
||||
async with session_factory() as session:
|
||||
return await get_enriched_run(session, definition_id, run_id)
|
||||
|
||||
|
||||
async def record_ops_audit(
|
||||
*,
|
||||
action: str,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue