refactor(protocol): add toolName to ToolResult, remove proto typealiases, rename id→toolCallId, drop Conversation.events()

Three protocol-level changes from Android-client review (items 1-3, 5-6):

1) toolName denormalization in ToolResult (3 layers):
   - :outbox-api/Event.ToolResult: +toolName: String? = null
   - :journal-api/MessageRecord.ToolResult: +toolName: String? = null
   - :proto/Message.ToolResult: +toolName: String? = null
   - :storage-ksqlite, :journal-ksqlite ResultPayload codec: +toolName
   - :standalone/ToolDispatcher, ConversationLoop: thread toolName = call.name
   Nullable + default = backward-compat for already-persisted histories
   and existing clients.

2) Drop proto/Event.kt, AgentEvent.kt, CommonEvent.kt typealiases.
   is proto.Event.End failed with 'Unresolved reference End' (alias
   loses nested-class access). Use pw.binom.agentik.outbox.{Event,
   AgentEvent, CommonEvent} directly everywhere — :proto already has
   api(:outbox-api), the package is visible to consumers, no shim
   needed. 21 files rewired, 3 files deleted.

3) Rename Event.ToolResult.id → toolCallId (option B per user).
   In :outbox-api Event.ToolResult.id == Event.ToolCall.id (one value,
   one name); the persistent journal keeps MessageRecord.ToolResult.id
   as its own PK + toolCallId as FK to the call — different semantics,
   left untouched. Fixed ToolDispatcher bug: emitted id = resultId
   while KDoc claimed id == ToolCall.id; now emits toolCallId = callId.

4) Remove Conversation.events() from :proto; OutboxStore is sole event source.
   Conversation is a pure per-conversation abstraction (send/getMessages/
   rename/close). Live events only via agent.outbox.conversationEvents/
   agentEvents/events. HTTP route /conversations/{id}/events stays for
   wire-compat but routes through outbox internally (map { it.event }).

jvmTest green (95 tasks).
This commit is contained in:
2026-09-22 02:55:02 +03:00
parent 7865eed836
commit c9995b263e
24 changed files with 129 additions and 118 deletions
@@ -3,8 +3,8 @@ package pw.binom.agentik.server
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import pw.binom.agentik.outbox.CommonEvent
import pw.binom.agentik.outbox.OutboxStore
import pw.binom.agentik.proto.CommonEvent
/**
* HTTP-фасад для [OutboxStore] (bounded-tail live event stream агента).
@@ -20,11 +20,11 @@ import kotlinx.coroutines.flow.map
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import pw.binom.agentik.proto.Agent
import pw.binom.agentik.proto.AgentEvent
import pw.binom.agentik.proto.CommonEvent
import pw.binom.agentik.proto.Conversation
import pw.binom.agentik.proto.Content
import pw.binom.agentik.proto.Event
import pw.binom.agentik.outbox.AgentEvent
import pw.binom.agentik.outbox.CommonEvent
import pw.binom.agentik.outbox.Event
import kotlin.time.Instant
internal fun Route.agentikRoutes(agent: Agent) {
@@ -128,7 +128,10 @@ internal fun Route.agentikRoutes(agent: Agent) {
return@get
}
val after = call.parseAfter() ?: return@get
call.streamJsonSse(c.events(after), Event.serializer())
// Live-источник событий — `OutboxStore` (единая точка истины);
// разворачиваем `CommonEvent.Conversation` → `Event` для совместимости
// wire-формата (клиент десериализует как `Event`, не как `CommonEvent.Conversation`).
call.streamJsonSse(agent.outbox.conversationEvents(after, id).map { it.event }, Event.serializer())
}
get("/events") {