feat(agent-runner): add event-first context facts and pull APIs

Add EventLog and Transcript persistence entities for storing auditable
event facts and conversation history projection. Implement event-first
AgentRunContext builder that produces Protocol v1 compliant context
payloads with required fields: event, delivery, context (ContextAccess).

Key changes:
- EventLog ORM: auditable event records with indexes
- Transcript ORM: conversation history projection with composite indexes
- AgentRunContextBuilder: Protocol v1 payload with delivery, context, bootstrap
- EventLogStore/TranscriptStore: async stores for fact sources
- Host action handlers: HISTORY_PAGE, HISTORY_SEARCH, EVENT_GET, EVENT_PAGE
- Context validation: build_context output validates via SDK AgentRunContext
- Alembic migration for event_log and transcript tables
- Alembic env.py imports all ORM models for autogenerate discovery

Legacy compatibility: max-round messages go into bootstrap.messages and
compatibility.legacy_messages, not top-level messages field.
This commit is contained in:
huanghuoguoguo
2026-05-23 16:07:46 +08:00
parent 8063303cfa
commit 8db23bf950
18 changed files with 3705 additions and 60 deletions

View File

@@ -288,7 +288,8 @@ async def test_orchestrator_runs_fake_plugin_with_authorized_context():
context = plugin_connector.contexts[0]
assert context["config"]["timeout"] == 30
assert context["runtime"]["deadline_at"] is not None
assert context["params"] == {"public_param": "visible"}
# Protocol v1: params is in compatibility.extra
assert context["compatibility"]["extra"]["params"] == {"public_param": "visible"}
assert context["event"]["event_type"] == "message.received"
assert context["event"]["event_data"]["source_event_type"] == "FriendMessage"
assert context["actor"]["actor_id"] == "user_001"
@@ -337,7 +338,16 @@ async def test_orchestrator_packages_legacy_max_round_without_mutating_query():
assert len(messages) == 1
context = plugin_connector.contexts[0]
assert [message["content"] for message in context["messages"]] == [
# Protocol v1: legacy messages are in bootstrap.messages
assert context["bootstrap"] is not None
assert [message["content"] for message in context["bootstrap"]["messages"]] == [
"message 2",
"response 2",
"message 3",
"response 3",
]
# Also in compatibility.legacy_messages for legacy runners
assert [message["content"] for message in context["compatibility"]["legacy_messages"]] == [
"message 2",
"response 2",
"message 3",