feat(agent): add task-oriented run logs and execution details

This commit is contained in:
RockChinQ
2026-09-18 00:59:17 +08:00
parent b264d46d77
commit a6ac25cf84
20 changed files with 1080 additions and 11 deletions
@@ -83,6 +83,7 @@ class AgentRunJournal:
run_id=context['run_id'],
event_id=event.event_id,
binding_id=binding.binding_id,
agent_id=binding.agent_id,
runner_id=descriptor.id,
conversation_id=event.conversation_id,
thread_id=event.thread_id,
@@ -100,6 +101,19 @@ class AgentRunJournal:
if binding.processor_type == 'event_processor'
else {}
),
**(
{
**({'input_event': event.data} if event.event_type != 'message.received' else {}),
'input': {
'text': context.get('input', {}).get('text', ''),
'contents': self._sanitize_contents(context.get('input', {}).get('contents', [])),
'attachments': self._sanitize_attachments(context.get('input', {}).get('attachments', [])),
},
'delivery': event.delivery.model_dump(mode='json'),
}
if binding.processor_type == 'agent'
else {}
),
},
)
@@ -636,6 +636,7 @@ class RunLedgerStore:
strict_thread: bool = False,
runner_id: str | None = None,
binding_id: str | None = None,
agent_id: str | None = None,
) -> tuple[list[dict[str, typing.Any]], int | None, bool, int]:
"""Page runs by scope.
@@ -643,9 +644,21 @@ class RunLedgerStore:
Tuple of (items, next_cursor, has_more, total_count).
"""
limit = min(max(int(limit), 1), 100)
agent_filter = sqlalchemy.or_(
AgentRun.agent_id == agent_id,
sqlalchemy.and_(
AgentRun.agent_id.is_(None),
sqlalchemy.or_(
AgentRun.binding_id.startswith(f'agent_{agent_id}_', autoescape=True),
AgentRun.binding_id.startswith(f'debug:{agent_id}:', autoescape=True),
),
),
)
async with self._session_factory() as session:
# First get total count
count_query = sqlalchemy.select(sqlalchemy.func.count(AgentRun.id))
if agent_id is not None:
count_query = count_query.where(agent_filter)
if conversation_id is not None:
count_query = count_query.where(AgentRun.conversation_id == conversation_id)
if statuses:
@@ -660,6 +673,8 @@ class RunLedgerStore:
# Then get items
query = sqlalchemy.select(AgentRun)
if agent_id is not None:
query = query.where(agent_filter)
if conversation_id is not None:
query = query.where(AgentRun.conversation_id == conversation_id)
if statuses:
+19 -7
View File
@@ -529,16 +529,20 @@ class AgentService:
return config, component_ref, descriptor.supported_event_patterns
async def get_processor_runs(self, context, processor_id, *, before_id=None):
"""Read only this Workspace's explicitly created processor instance."""
"""Read only this Workspace's Agent or plugin processor runs."""
from ....agent.runner.run_ledger_store import RunLedgerStore
processor = await self.get_agent(context, processor_id)
if processor is None or processor.get('kind') != AGENT_KIND_EVENT_PROCESSOR:
raise ValueError('Event processor not found')
if processor is None or processor.get('kind') not in {'agent', AGENT_KIND_EVENT_PROCESSOR}:
raise ValueError('Processor not found')
store = RunLedgerStore(self.ap.persistence_mgr.get_db_engine())
items, cursor, has_more, total = await store.list_runs(
workspace_id=require_workspace_uuid(context),
binding_id=f'event_processor:{processor_id}',
**(
{'binding_id': f'event_processor:{processor_id}'}
if processor.get('kind') == AGENT_KIND_EVENT_PROCESSOR
else {'agent_id': processor_id}
),
before_id=before_id,
)
return {'items': items, 'next_cursor': cursor, 'has_more': has_more, 'total': total}
@@ -548,14 +552,22 @@ class AgentService:
from ....agent.runner.run_ledger_store import RunLedgerStore
processor = await self.get_agent(context, processor_id)
if processor is None or processor.get('kind') != AGENT_KIND_EVENT_PROCESSOR:
raise ValueError('Event processor not found')
if processor is None or processor.get('kind') not in {'agent', AGENT_KIND_EVENT_PROCESSOR}:
raise ValueError('Processor not found')
store = RunLedgerStore(self.ap.persistence_mgr.get_db_engine())
run = await store.get_run(run_id)
if (
run is None
or run.get('workspace_id') != require_workspace_uuid(context)
or run.get('binding_id') != f'event_processor:{processor_id}'
or not (
run.get('binding_id') == f'event_processor:{processor_id}'
if processor.get('kind') == AGENT_KIND_EVENT_PROCESSOR
else run.get('agent_id') == processor_id
or (
run.get('agent_id') is None
and str(run.get('binding_id', '')).startswith((f'agent_{processor_id}_', f'debug:{processor_id}:'))
)
)
):
raise ValueError('Processor run not found')
items, next_cursor, _, has_more = await store.page_run_events(
+4 -2
View File
@@ -227,14 +227,16 @@ class LangBotMCPServer:
return _dump(await ap.agent_service.get_agent_metadata(context))
@mcp.tool(
description='List one Event processor instance run history; use before_id to page older runs. '
description='List one Agent or plugin processor run history; use before_id to page older runs. '
'created_at_ms, started_at_ms and finished_at_ms are Host lifecycle times in epoch milliseconds.'
)
async def list_processor_runs(processor_uuid: str, before_id: int | None = None) -> str:
context = _authorized(Permission.RESOURCE_VIEW)
return _dump(await ap.agent_service.get_processor_runs(context, processor_uuid, before_id=before_id))
@mcp.tool(description='Read logs and action results for an Event processor run; page using after_sequence.')
@mcp.tool(
description='Read logs and action results for an Agent or plugin processor run; page using after_sequence.'
)
async def get_processor_run_events(
processor_uuid: str,
run_id: str,