ba32a44cb4
* refactor(backend): drop legacy orchestrator WS handlers superseded by session.launch The orchestrator exposed six WS handlers (orchestrator.start, orchestrator.trigger, orchestrator.prompt, orchestrator.complete, task.session.resume, task.session.prepare) that all delegated to the unified LaunchSession or are no longer reachable from any client. Drop the handlers, their DTOs, and the dead action constants (orchestrator.pause, orchestrator.resume) that were never registered. Migrate the remaining integration tests to the unified session.launch action. * refactor(backend): drop orphan DTO request types and fix stale doc reference Address PR review feedback: the DTO types TaskSessionStatusRequest, StopTaskRequest, and PermissionRespondRequest had no consumers (each was shadowed by an inline private ws*Request type in the handler). AGENTS.md still referenced the removed task.start action; switch it to session.launch. --------- Co-authored-by: Kandev Agent <agent@kandev.dev>
621 lines
19 KiB
Go
621 lines
19 KiB
Go
// Package integration provides end-to-end integration tests for the Kandev backend.
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/kandev/kandev/internal/events"
|
|
"github.com/kandev/kandev/internal/events/bus"
|
|
"github.com/kandev/kandev/pkg/acp/protocol"
|
|
ws "github.com/kandev/kandev/pkg/websocket"
|
|
)
|
|
|
|
func TestOrchestratorTaskPriority(t *testing.T) {
|
|
ts := NewOrchestratorTestServer(t)
|
|
defer ts.Close()
|
|
|
|
// Create tasks with different priorities
|
|
lowPriorityTask := ts.CreateTestTask(t, "augment-agent", 1)
|
|
highPriorityTask := ts.CreateTestTask(t, "augment-agent", 3)
|
|
medPriorityTask := ts.CreateTestTask(t, "augment-agent", 2)
|
|
|
|
client := NewOrchestratorWSClient(t, ts.Server.URL)
|
|
defer client.Close()
|
|
|
|
// Start low priority first
|
|
_, err := client.SendRequest("start-1", ws.ActionSessionLaunch, map[string]interface{}{
|
|
"task_id": lowPriorityTask,
|
|
"agent_profile_id": "augment-agent",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Then high priority
|
|
_, err = client.SendRequest("start-2", ws.ActionSessionLaunch, map[string]interface{}{
|
|
"task_id": highPriorityTask,
|
|
"agent_profile_id": "augment-agent",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Then medium priority
|
|
_, err = client.SendRequest("start-3", ws.ActionSessionLaunch, map[string]interface{}{
|
|
"task_id": medPriorityTask,
|
|
"agent_profile_id": "augment-agent",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// All should start (we have capacity)
|
|
assert.Equal(t, 3, ts.AgentManager.GetLaunchCount())
|
|
}
|
|
|
|
// TestOrchestratorAgentMessagePersistence validates that agent messages are stored
|
|
// correctly in the database without missing data or ordering issues.
|
|
func TestOrchestratorAgentMessagePersistence(t *testing.T) {
|
|
ts := NewOrchestratorTestServer(t)
|
|
defer ts.Close()
|
|
|
|
// This test verifies that message_streaming and log events ARE correctly persisted.
|
|
// Progress messages (protocol.MessageTypeProgress) are NOT persisted - only broadcast via WebSocket.
|
|
// Log messages (protocol.MessageTypeLog) ARE now persisted to the database.
|
|
|
|
// We'll manually publish message_streaming and log events to test persistence
|
|
ts.AgentManager.SetACPMessageFn(func(taskID, executionID string) []protocol.Message {
|
|
// Just emit a simple progress message - won't be stored
|
|
return []protocol.Message{
|
|
{
|
|
Type: protocol.MessageTypeProgress,
|
|
TaskID: taskID,
|
|
Timestamp: time.Now(),
|
|
Data: map[string]interface{}{
|
|
"progress": 10,
|
|
"stage": "starting",
|
|
"message": "Starting...",
|
|
},
|
|
},
|
|
}
|
|
})
|
|
|
|
// Create test task
|
|
taskID := ts.CreateTestTask(t, "test-profile-id", 2)
|
|
|
|
client := NewOrchestratorWSClient(t, ts.Server.URL)
|
|
defer client.Close()
|
|
|
|
// Subscribe to task
|
|
_, err := client.SendRequest("sub-1", ws.ActionTaskSubscribe, map[string]string{"task_id": taskID})
|
|
require.NoError(t, err)
|
|
|
|
// Start task execution
|
|
startResp, err := client.SendRequest("start-1", ws.ActionSessionLaunch, map[string]interface{}{
|
|
"task_id": taskID,
|
|
"agent_profile_id": "test-profile-id",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var startPayload map[string]interface{}
|
|
require.NoError(t, startResp.ParsePayload(&startPayload))
|
|
require.True(t, startPayload["success"].(bool), "Task should start successfully")
|
|
sessionID, _ := startPayload["session_id"].(string)
|
|
require.NotEmpty(t, sessionID, "Session ID should be returned")
|
|
|
|
// Subscribe to session for agent stream events
|
|
_, err = client.SendRequest("session-sub-1", ws.ActionSessionSubscribe, map[string]string{"session_id": sessionID})
|
|
require.NoError(t, err)
|
|
|
|
// Wait for simulated agent to start
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Now manually publish message_streaming events which ARE persisted
|
|
// Generate unique message IDs for each streaming message
|
|
messageIDs := []string{
|
|
"msg-" + uuid.New().String()[:8],
|
|
"msg-" + uuid.New().String()[:8],
|
|
"msg-" + uuid.New().String()[:8],
|
|
}
|
|
|
|
expectedContents := []string{
|
|
"I'm analyzing the codebase structure.",
|
|
"I found several files that need modification.",
|
|
"Here is my recommendation for the changes.",
|
|
}
|
|
|
|
// Log messages to publish
|
|
logMessages := []struct {
|
|
Level string
|
|
Message string
|
|
}{
|
|
{"info", "Found 10 files to process"},
|
|
{"debug", "Processing file: main.go"},
|
|
{"warning", "Deprecated API usage detected"},
|
|
}
|
|
|
|
// Publish message_streaming events - these ARE persisted
|
|
for i, msgID := range messageIDs {
|
|
payload := map[string]interface{}{
|
|
"type": "agent/event",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339Nano),
|
|
"agent_id": "test-agent",
|
|
"task_id": taskID,
|
|
"session_id": sessionID,
|
|
"data": map[string]interface{}{
|
|
"type": "message_streaming",
|
|
"text": expectedContents[i],
|
|
"message_id": msgID,
|
|
"is_append": false, // Each is a new message
|
|
},
|
|
}
|
|
event := bus.NewEvent(events.AgentStream, "test", payload)
|
|
subject := events.BuildAgentStreamSubject(sessionID)
|
|
err := ts.EventBus.Publish(context.Background(), subject, event)
|
|
require.NoError(t, err)
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
|
|
// Publish log events - these ARE now persisted
|
|
for _, logMsg := range logMessages {
|
|
payload := map[string]interface{}{
|
|
"type": "agent/event",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339Nano),
|
|
"agent_id": "test-agent",
|
|
"task_id": taskID,
|
|
"session_id": sessionID,
|
|
"data": map[string]interface{}{
|
|
"type": "log",
|
|
"data": map[string]interface{}{
|
|
"level": logMsg.Level,
|
|
"message": logMsg.Message,
|
|
},
|
|
},
|
|
}
|
|
event := bus.NewEvent(events.AgentStream, "test", payload)
|
|
subject := events.BuildAgentStreamSubject(sessionID)
|
|
err := ts.EventBus.Publish(context.Background(), subject, event)
|
|
require.NoError(t, err)
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
|
|
// Wait for all messages to be processed and stored
|
|
time.Sleep(300 * time.Millisecond)
|
|
|
|
// Query the database to verify stored messages
|
|
storedMessages, err := ts.TaskRepo.ListMessages(context.Background(), sessionID)
|
|
require.NoError(t, err)
|
|
|
|
t.Logf("Found %d total stored messages", len(storedMessages))
|
|
|
|
type msgEntry struct {
|
|
ID string
|
|
Type string
|
|
Content string
|
|
AuthorType string
|
|
Metadata map[string]interface{}
|
|
CreatedAt time.Time
|
|
TaskSessionID string
|
|
TurnID string
|
|
}
|
|
var agentMessages []*msgEntry
|
|
for _, msg := range storedMessages {
|
|
if string(msg.AuthorType) == "agent" {
|
|
agentMessages = append(agentMessages, &msgEntry{
|
|
ID: msg.ID,
|
|
Type: string(msg.Type),
|
|
Content: msg.Content,
|
|
AuthorType: string(msg.AuthorType),
|
|
Metadata: msg.Metadata,
|
|
CreatedAt: msg.CreatedAt,
|
|
TaskSessionID: msg.TaskSessionID,
|
|
TurnID: msg.TurnID,
|
|
})
|
|
}
|
|
}
|
|
t.Logf("Found %d agent messages in database", len(agentMessages))
|
|
|
|
// Verify messages are stored
|
|
require.GreaterOrEqual(t, len(agentMessages), 3, "Should have at least 3 agent messages from streaming events")
|
|
|
|
// Verify messages are in correct chronological order
|
|
for i := 1; i < len(agentMessages); i++ {
|
|
prev := agentMessages[i-1]
|
|
curr := agentMessages[i]
|
|
assert.True(t, !curr.CreatedAt.Before(prev.CreatedAt),
|
|
"Messages should be in chronological order: message %d (%s) at %v should not be before message %d (%s) at %v",
|
|
i, curr.ID, curr.CreatedAt, i-1, prev.ID, prev.CreatedAt)
|
|
}
|
|
|
|
// Verify no duplicate messages exist (check for duplicate IDs)
|
|
seenIDs := make(map[string]bool)
|
|
for _, msg := range agentMessages {
|
|
assert.False(t, seenIDs[msg.ID], "Duplicate message ID found: %s", msg.ID)
|
|
seenIDs[msg.ID] = true
|
|
}
|
|
|
|
// Verify all messages are associated with the correct TaskSessionID
|
|
for _, msg := range agentMessages {
|
|
assert.Equal(t, sessionID, msg.TaskSessionID,
|
|
"Message %s should be associated with session %s", msg.ID, sessionID)
|
|
}
|
|
|
|
// Verify all agent messages have a TurnID
|
|
for _, msg := range agentMessages {
|
|
assert.NotEmpty(t, msg.TurnID,
|
|
"Message %s should have a TurnID", msg.ID)
|
|
}
|
|
|
|
// Verify message content matches expected content
|
|
var contentMessages []*msgEntry
|
|
for _, msg := range agentMessages {
|
|
if msg.Type == "content" {
|
|
contentMessages = append(contentMessages, msg)
|
|
}
|
|
}
|
|
require.Len(t, contentMessages, 3, "Should have exactly 3 content messages")
|
|
|
|
// Verify content is in correct order
|
|
for i, expected := range expectedContents {
|
|
if i < len(contentMessages) {
|
|
assert.Equal(t, expected, contentMessages[i].Content,
|
|
"Message %d content should match expected", i)
|
|
}
|
|
}
|
|
|
|
// Log summary of stored messages for debugging
|
|
t.Logf("Message storage verification summary:")
|
|
for i, msg := range agentMessages {
|
|
t.Logf(" [%d] ID=%s Type=%s Content=%q CreatedAt=%v TurnID=%s",
|
|
i, msg.ID[:8], msg.Type, truncateString(msg.Content, 50), msg.CreatedAt, msg.TurnID[:8])
|
|
}
|
|
}
|
|
|
|
// TestOrchestratorAgentMessagePersistenceWithToolCalls validates that tool call messages
|
|
// are stored correctly with proper metadata including tool_call_id, title, status, and args.
|
|
func TestOrchestratorAgentMessagePersistenceWithToolCalls(t *testing.T) {
|
|
ts := NewOrchestratorTestServer(t)
|
|
defer ts.Close()
|
|
|
|
// Configure simulated agent to emit tool call events via direct event publishing
|
|
toolCallID1 := "tc-" + uuid.New().String()[:8]
|
|
toolCallID2 := "tc-" + uuid.New().String()[:8]
|
|
|
|
ts.AgentManager.SetACPMessageFn(func(taskID, executionID string) []protocol.Message {
|
|
baseTime := time.Now()
|
|
return []protocol.Message{
|
|
// Initial progress
|
|
{
|
|
Type: protocol.MessageTypeProgress,
|
|
TaskID: taskID,
|
|
Timestamp: baseTime,
|
|
Data: map[string]interface{}{
|
|
"progress": 10,
|
|
"stage": "starting",
|
|
"message": "Starting tool execution...",
|
|
},
|
|
},
|
|
// Log message
|
|
{
|
|
Type: protocol.MessageTypeLog,
|
|
TaskID: taskID,
|
|
Timestamp: baseTime.Add(20 * time.Millisecond),
|
|
Data: map[string]interface{}{
|
|
"level": "info",
|
|
"message": "Executing view tool",
|
|
},
|
|
},
|
|
// Final progress
|
|
{
|
|
Type: protocol.MessageTypeProgress,
|
|
TaskID: taskID,
|
|
Timestamp: baseTime.Add(100 * time.Millisecond),
|
|
Data: map[string]interface{}{
|
|
"progress": 100,
|
|
"stage": "completed",
|
|
"message": "Tool execution completed",
|
|
},
|
|
},
|
|
}
|
|
})
|
|
|
|
// Create test task
|
|
taskID := ts.CreateTestTask(t, "test-profile-id", 2)
|
|
|
|
client := NewOrchestratorWSClient(t, ts.Server.URL)
|
|
defer client.Close()
|
|
|
|
// Subscribe to task
|
|
_, err := client.SendRequest("sub-1", ws.ActionTaskSubscribe, map[string]string{"task_id": taskID})
|
|
require.NoError(t, err)
|
|
|
|
// Start task execution
|
|
startResp, err := client.SendRequest("start-1", ws.ActionSessionLaunch, map[string]interface{}{
|
|
"task_id": taskID,
|
|
"agent_profile_id": "test-profile-id",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var startPayload map[string]interface{}
|
|
require.NoError(t, startResp.ParsePayload(&startPayload))
|
|
require.True(t, startPayload["success"].(bool))
|
|
sessionID, _ := startPayload["session_id"].(string)
|
|
require.NotEmpty(t, sessionID)
|
|
|
|
// Subscribe to session
|
|
_, err = client.SendRequest("session-sub-1", ws.ActionSessionSubscribe, map[string]string{"session_id": sessionID})
|
|
require.NoError(t, err)
|
|
|
|
// Now publish tool call events directly to the event bus to simulate real tool usage
|
|
// This mimics what the lifecycle manager does when processing agent tool calls
|
|
publishToolCallEvent := func(toolCallID, title, status string, args map[string]interface{}) {
|
|
payload := map[string]interface{}{
|
|
"type": "agent/event",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339Nano),
|
|
"agent_id": "test-agent",
|
|
"task_id": taskID,
|
|
"session_id": sessionID,
|
|
"data": map[string]interface{}{
|
|
"type": "tool_call",
|
|
"tool_call_id": toolCallID,
|
|
"tool_title": title,
|
|
"tool_name": "view",
|
|
"tool_status": status,
|
|
"tool_args": args,
|
|
},
|
|
}
|
|
event := bus.NewEvent(events.AgentStream, "test", payload)
|
|
subject := events.BuildAgentStreamSubject(sessionID)
|
|
err := ts.EventBus.Publish(context.Background(), subject, event)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
publishToolUpdateEvent := func(toolCallID, status, result string) {
|
|
payload := map[string]interface{}{
|
|
"type": "agent/event",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339Nano),
|
|
"agent_id": "test-agent",
|
|
"task_id": taskID,
|
|
"session_id": sessionID,
|
|
"data": map[string]interface{}{
|
|
"type": "tool_update",
|
|
"tool_call_id": toolCallID,
|
|
"tool_status": status,
|
|
"tool_result": result,
|
|
},
|
|
}
|
|
event := bus.NewEvent(events.AgentStream, "test", payload)
|
|
subject := events.BuildAgentStreamSubject(sessionID)
|
|
err := ts.EventBus.Publish(context.Background(), subject, event)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Wait a bit for the initial messages to be processed
|
|
time.Sleep(300 * time.Millisecond)
|
|
|
|
// Publish tool call start events
|
|
publishToolCallEvent(toolCallID1, "View main.go", "running", map[string]interface{}{
|
|
"path": "main.go",
|
|
"kind": "view",
|
|
})
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
publishToolCallEvent(toolCallID2, "View utils.go", "running", map[string]interface{}{
|
|
"path": "utils.go",
|
|
"kind": "view",
|
|
})
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Publish tool update events (completion)
|
|
publishToolUpdateEvent(toolCallID1, "complete", "File content: package main...")
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
publishToolUpdateEvent(toolCallID2, "complete", "File content: package utils...")
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Wait for processing
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// Query stored messages
|
|
storedMessages, err := ts.TaskRepo.ListMessages(context.Background(), sessionID)
|
|
require.NoError(t, err)
|
|
|
|
t.Logf("Found %d total stored messages", len(storedMessages))
|
|
|
|
// Find tool_call messages
|
|
var toolCallMessages []*struct {
|
|
ID string
|
|
Type string
|
|
Content string
|
|
Metadata map[string]interface{}
|
|
}
|
|
for _, msg := range storedMessages {
|
|
if string(msg.Type) == "tool_call" {
|
|
toolCallMessages = append(toolCallMessages, &struct {
|
|
ID string
|
|
Type string
|
|
Content string
|
|
Metadata map[string]interface{}
|
|
}{
|
|
ID: msg.ID,
|
|
Type: string(msg.Type),
|
|
Content: msg.Content,
|
|
Metadata: msg.Metadata,
|
|
})
|
|
}
|
|
}
|
|
|
|
t.Logf("Found %d tool_call messages", len(toolCallMessages))
|
|
|
|
// Verify tool call messages exist and have correct metadata
|
|
assert.GreaterOrEqual(t, len(toolCallMessages), 2,
|
|
"Should have at least 2 tool_call messages")
|
|
|
|
// Verify tool call metadata structure
|
|
for _, msg := range toolCallMessages {
|
|
assert.NotNil(t, msg.Metadata, "Tool call message should have metadata")
|
|
if msg.Metadata != nil {
|
|
toolCallIDMeta, _ := msg.Metadata["tool_call_id"].(string)
|
|
assert.NotEmpty(t, toolCallIDMeta,
|
|
"Tool call message should have tool_call_id in metadata")
|
|
|
|
title, _ := msg.Metadata["title"].(string)
|
|
t.Logf("Tool call message: ID=%s, tool_call_id=%s, title=%s, status=%v",
|
|
msg.ID[:8], toolCallIDMeta, title, msg.Metadata["status"])
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestOrchestratorAgentMessageChunkStreaming validates that streaming message chunks
|
|
// are correctly aggregated and stored as complete messages.
|
|
func TestOrchestratorAgentMessageChunkStreaming(t *testing.T) {
|
|
ts := NewOrchestratorTestServer(t)
|
|
defer ts.Close()
|
|
|
|
// Configure minimal ACP messages - we'll manually publish streaming chunks
|
|
ts.AgentManager.SetACPMessageFn(func(taskID, executionID string) []protocol.Message {
|
|
return []protocol.Message{
|
|
{
|
|
Type: protocol.MessageTypeProgress,
|
|
TaskID: taskID,
|
|
Timestamp: time.Now(),
|
|
Data: map[string]interface{}{
|
|
"progress": 10,
|
|
"stage": "starting",
|
|
"message": "Starting...",
|
|
},
|
|
},
|
|
}
|
|
})
|
|
|
|
// Create test task
|
|
taskID := ts.CreateTestTask(t, "test-profile-id", 2)
|
|
|
|
client := NewOrchestratorWSClient(t, ts.Server.URL)
|
|
defer client.Close()
|
|
|
|
// Subscribe and start task
|
|
_, err := client.SendRequest("sub-1", ws.ActionTaskSubscribe, map[string]string{"task_id": taskID})
|
|
require.NoError(t, err)
|
|
|
|
startResp, err := client.SendRequest("start-1", ws.ActionSessionLaunch, map[string]interface{}{
|
|
"task_id": taskID,
|
|
"agent_profile_id": "test-profile-id",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var startPayload map[string]interface{}
|
|
require.NoError(t, startResp.ParsePayload(&startPayload))
|
|
require.True(t, startPayload["success"].(bool))
|
|
sessionID, _ := startPayload["session_id"].(string)
|
|
require.NotEmpty(t, sessionID)
|
|
|
|
_, err = client.SendRequest("session-sub-1", ws.ActionSessionSubscribe, map[string]string{"session_id": sessionID})
|
|
require.NoError(t, err)
|
|
|
|
// Wait for initial setup
|
|
time.Sleep(300 * time.Millisecond)
|
|
|
|
// Simulate streaming message chunks by publishing message_streaming events
|
|
messageID := uuid.New().String()
|
|
chunks := []string{
|
|
"Hello, ",
|
|
"this is ",
|
|
"a streaming ",
|
|
"message that ",
|
|
"should be ",
|
|
"aggregated.",
|
|
}
|
|
|
|
for i, chunk := range chunks {
|
|
payload := map[string]interface{}{
|
|
"type": "agent/event",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339Nano),
|
|
"agent_id": "test-agent",
|
|
"task_id": taskID,
|
|
"session_id": sessionID,
|
|
"data": map[string]interface{}{
|
|
"type": "message_streaming",
|
|
"text": chunk,
|
|
"message_id": messageID,
|
|
"is_append": i > 0, // First chunk creates, subsequent append
|
|
},
|
|
}
|
|
event := bus.NewEvent(events.AgentStream, "test", payload)
|
|
subject := events.BuildAgentStreamSubject(sessionID)
|
|
err := ts.EventBus.Publish(context.Background(), subject, event)
|
|
require.NoError(t, err)
|
|
time.Sleep(30 * time.Millisecond)
|
|
}
|
|
|
|
// Wait for processing
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// Query stored messages
|
|
storedMessages, err := ts.TaskRepo.ListMessages(context.Background(), sessionID)
|
|
require.NoError(t, err)
|
|
|
|
// Find the streaming message
|
|
var streamingMessage *struct {
|
|
ID string
|
|
Content string
|
|
Type string
|
|
}
|
|
for _, msg := range storedMessages {
|
|
if msg.ID == messageID {
|
|
streamingMessage = &struct {
|
|
ID string
|
|
Content string
|
|
Type string
|
|
}{
|
|
ID: msg.ID,
|
|
Content: msg.Content,
|
|
Type: string(msg.Type),
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Verify the streaming message was created and aggregated
|
|
if streamingMessage != nil {
|
|
expectedContent := strings.Join(chunks, "")
|
|
assert.Equal(t, expectedContent, streamingMessage.Content,
|
|
"Streaming message content should be aggregated from all chunks")
|
|
t.Logf("Streaming message aggregated correctly: %q", streamingMessage.Content)
|
|
} else {
|
|
// The message might be stored with agent-generated ID
|
|
// Look for content messages from the agent
|
|
var agentContentMessages []*struct {
|
|
ID string
|
|
Content string
|
|
Type string
|
|
}
|
|
for _, msg := range storedMessages {
|
|
if string(msg.AuthorType) == "agent" && (string(msg.Type) == "message" || string(msg.Type) == "content") {
|
|
agentContentMessages = append(agentContentMessages, &struct {
|
|
ID string
|
|
Content string
|
|
Type string
|
|
}{
|
|
ID: msg.ID,
|
|
Content: msg.Content,
|
|
Type: string(msg.Type),
|
|
})
|
|
}
|
|
}
|
|
t.Logf("Found %d agent content messages", len(agentContentMessages))
|
|
for _, msg := range agentContentMessages {
|
|
t.Logf(" ID=%s Type=%s Content=%q", msg.ID[:8], msg.Type, truncateString(msg.Content, 100))
|
|
}
|
|
}
|
|
}
|
|
|
|
// truncateString truncates a string to the specified length, appending "..." if truncated.
|
|
func truncateString(s string, maxLen int) string {
|
|
if len(s) <= maxLen {
|
|
return s
|
|
}
|
|
return s[:maxLen] + "..."
|
|
}
|