Files
tutti/packages/agent/daemon/runtime/codex_appserver_mcp_test.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

173 lines
5.8 KiB
Go

package agentruntime
import (
"reflect"
"testing"
"time"
activityshared "github.com/tutti-os/tutti/packages/agent/daemon/activity/events"
)
func TestAppServerMCPToolCallProjectsResultBody(t *testing.T) {
content := []any{
map[string]any{"type": "text", "text": "MCP result"},
}
structuredContent := map[string]any{
"items": []any{map[string]any{"id": "item-1"}},
}
update, ok := appServerItemToolCallUpdate(map[string]any{
"id": "mcp-1",
"type": "mcpToolCall",
"status": "completed",
"server": "example",
"tool": "search",
"arguments": map[string]any{"query": "tutti"},
"result": map[string]any{
"content": content,
"structuredContent": structuredContent,
"isError": false,
"_meta": map[string]any{"provider": "discarded"},
},
}, true)
if !ok {
t.Fatal("expected MCP item to produce an update")
}
if !reflect.DeepEqual(update["content"], content) {
t.Fatalf("content = %#v, want %#v", update["content"], content)
}
output := payloadMap(update, "rawOutput")
if output["isError"] != false || !reflect.DeepEqual(output["structuredContent"], structuredContent) {
t.Fatalf("rawOutput = %#v, want canonical MCP result", output)
}
if output["result"] != nil || output["_meta"] != nil {
t.Fatalf("rawOutput retained MCP result envelope: %#v", output)
}
session := Session{Provider: "codex", AgentSessionID: "agent-1", RoomID: "room-1"}
event, ok := acpToolCallEventWithID(session, "event-1", "turn-1", update)
if !ok || event.Type != activityshared.EventCallCompleted {
t.Fatalf("event = %#v ok=%v, want completed call", event, ok)
}
if event.Payload.Output["stdout"] != "MCP result" ||
event.Payload.Output["isError"] != false ||
!reflect.DeepEqual(event.Payload.Output["structuredContent"], structuredContent) {
t.Fatalf("event output = %#v, want canonical MCP result fields", event.Payload.Output)
}
}
func TestAppServerMCPToolCallPreservesStructuredErrorResult(t *testing.T) {
update, ok := appServerItemToolCallUpdate(map[string]any{
"id": "mcp-2",
"type": "mcpToolCall",
"status": "completed",
"result": map[string]any{
"content": []any{
map[string]any{"type": "text", "text": "permission denied"},
},
"structuredContent": map[string]any{"code": "permission_denied"},
"isError": true,
},
}, true)
if !ok {
t.Fatal("expected MCP item to produce an update")
}
session := Session{Provider: "codex", AgentSessionID: "agent-1", RoomID: "room-1"}
event, ok := acpToolCallEventWithID(session, "event-2", "turn-1", update)
if !ok || event.Type != activityshared.EventCallFailed {
t.Fatalf("event = %#v ok=%v, want failed call", event, ok)
}
if event.Payload.Error["isError"] != true ||
event.Payload.Error["stdout"] != "permission denied" ||
event.Payload.Output["isError"] != true ||
event.Payload.Output["structuredContent"] == nil {
t.Fatalf("failed MCP payload = error:%#v output:%#v", event.Payload.Error, event.Payload.Output)
}
}
func TestCodexAppServerReducerProjectsMCPStartupStatus(t *testing.T) {
adapter, _, session := startedAppServerAdapter(t)
statusEvents := make(chan activityshared.Event, 16)
adapter.SetSessionEventSink(func(_ string, events []activityshared.Event) {
for _, event := range events {
statusEvents <- event
}
})
appSession := adapter.getSession(session.AgentSessionID)
if appSession == nil {
t.Fatal("missing app-server session")
}
newCodexAppServerReducer(adapter).ReduceNotification(
appSession.client,
session,
"",
acpMessage{
Method: appServerNotifyMCPServerStartupStatusUpdated,
Params: mustJSONRawMessage(t, map[string]any{
"threadId": session.ProviderSessionID,
"name": "figma",
"status": "failed",
"failureReason": "reauthenticationRequired",
"error": "MCP server requires authentication",
}),
},
nil,
nil,
)
statusFound := false
warningFound := false
deadline := time.After(time.Second)
for {
select {
case event := <-statusEvents:
status, ok := event.Payload.Metadata["mcpServerStartupStatus"].(map[string]any)
if ok {
if event.Type != EventSessionUpdated || asString(status["name"]) != "figma" ||
asString(status["status"]) != "failed" ||
asString(status["failureReason"]) != "reauthenticationRequired" {
t.Fatalf("MCP status event = %#v", event)
}
statusFound = true
}
metadata := event.Payload.Metadata
if asString(metadata["kind"]) == "agent_system_notice" {
if event.Type != activityshared.EventSessionAudit || event.Payload.TurnID != "" {
t.Fatalf("turnless MCP warning event = %#v, want session audit without turnId", event)
}
if asString(metadata["noticeKind"]) != "warning" ||
asString(metadata["severity"]) != "warning" ||
asString(metadata["detail"]) != "MCP server requires authentication" ||
asString(metadata["code"]) != "mcp_server_startup_failed" {
t.Fatalf("MCP warning event = %#v", event)
}
warningFound = true
}
if statusFound && warningFound {
return
}
case <-deadline:
t.Fatalf("MCP startup status/warning projection missing: status=%t warning=%t", statusFound, warningFound)
}
}
}
func TestCodexAppServerStartupWarningKeepsTurnScopedProjection(t *testing.T) {
adapter, _, session := startedAppServerAdapter(t)
appSession := adapter.getSession(session.AgentSessionID)
if appSession == nil {
t.Fatal("missing app-server session")
}
event := codexMCPServerStartupWarningEvent(appSession.client, session, "turn-1", map[string]any{
"name": "figma",
"status": "failed",
"failureReason": "reauthenticationRequired",
"error": "MCP server requires authentication",
})
if event.Type != activityshared.EventMessageAppended || event.Payload.TurnID != "turn-1" {
t.Fatalf("turn-scoped MCP warning event = %#v, want message on turn-1", event)
}
}