Files
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

352 lines
9.7 KiB
Go

package agentruntime
import (
"fmt"
"sort"
"strings"
agentsessionstore "github.com/tutti-os/tutti/packages/agent/daemon/activity"
"github.com/tutti-os/tutti/packages/agent/store-sqlite/canonical"
)
const (
maxReportLogMessageUpdates = 8
maxReportLogStatePatches = 6
maxReportLogEntitiesPerPatch = 3
maxReportLogSummaryLength = 600
maxReportLogErrorLength = 360
)
func SummarizeReportActivityInputForLog(
input agentsessionstore.ReportActivityInput,
) ([]string, []string) {
messageUpdates := summarizeMessageUpdatesForLog(input.MessageUpdates)
statePatches := summarizeStatePatchesForLog(input.StatePatches)
return messageUpdates, statePatches
}
func summarizeMessageUpdatesForLog(updates []agentsessionstore.WorkspaceAgentMessageUpdate) []string {
if len(updates) == 0 {
return nil
}
limit := minReportLogInt(len(updates), maxReportLogMessageUpdates)
out := make([]string, 0, limit+1)
for _, update := range updates[:limit] {
bodyKinds := summarizeBodyKinds(
payloadHasNonEmptyField(update.Payload, "input"),
payloadHasNonEmptyField(update.Payload, "output"),
payloadHasNonEmptyField(update.Payload, "error"),
payloadHasNonEmptyField(update.Payload, "text"),
)
summary := fmt.Sprintf(
"%s|%s|%s|call=%s|turn=%s|body=%s|name=%s",
logSummaryValue(update.MessageID),
logSummaryValue(update.Kind),
logSummaryValue(update.Status),
logSummaryValue(update.CallID),
logSummaryValue(update.TurnID),
bodyKinds,
logSummaryValue(messageUpdateNameForLog(update)),
)
if shouldLogMessageUpdateError(update) {
if errorSummary := messageUpdateErrorSummaryForLog(update.Payload); errorSummary != "" {
summary += fmt.Sprintf("|error=%q", errorSummary)
}
}
out = append(out, trimReportLogSummary(summary))
}
if len(updates) > limit {
out = append(out, fmt.Sprintf("...+%d more message updates", len(updates)-limit))
}
return out
}
func summarizeStatePatchesForLog(patches []agentsessionstore.WorkspaceAgentStatePatch) []string {
if len(patches) == 0 {
return nil
}
limit := minReportLogInt(len(patches), maxReportLogStatePatches)
out := make([]string, 0, limit+1)
for _, patch := range patches[:limit] {
entityLimit := minReportLogInt(len(patch.Entities), maxReportLogEntitiesPerPatch)
entitySummaries := make([]string, 0, entityLimit+1)
for _, entity := range patch.Entities[:entityLimit] {
entitySummaries = append(entitySummaries, trimReportLogSummary(fmt.Sprintf(
"%s|%s|call=%s|turn=%s|body=%s|done=%t",
logSummaryValue(firstNonEmpty(entity.Name, entity.CallType)),
logSummaryValue(entity.Status),
logSummaryValue(entity.CallID),
logSummaryValue(entity.TurnID),
summarizeBodyKinds(
len(entity.Input) > 0,
len(entity.Output) > 0,
len(entity.Error) > 0,
false,
),
entity.CompletedAtUnixMS > 0,
)))
}
if len(patch.Entities) > entityLimit {
entitySummaries = append(entitySummaries, fmt.Sprintf("...+%d more entities", len(patch.Entities)-entityLimit))
}
out = append(out, trimReportLogSummary(fmt.Sprintf(
"session=%s|phase=%s|lifecycle=%s|turn=%s|turnLifecycle=%s|rootProviderTurn=%s|entities=%s",
logSummaryValue(patch.AgentSessionID),
logSummaryValue(patch.CurrentPhase),
logSummaryValue(patch.LifecycleStatus),
logSummaryValue(turnPatchSummary(patch.Turn)),
logSummaryValue(turnLifecycleSummary(patch.TurnLifecycle)),
logSummaryValue(rootProviderTurnSummary(patch.RootProviderTurn)),
strings.Join(entitySummaries, ";"),
)))
}
if len(patches) > limit {
out = append(out, fmt.Sprintf("...+%d more state patches", len(patches)-limit))
}
return out
}
func summarizeLogValueCounts(values []string) []string {
if len(values) == 0 {
return nil
}
counts := make(map[string]int, len(values))
order := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" {
continue
}
if counts[value] == 0 {
order = append(order, value)
}
counts[value]++
}
if len(order) == 0 {
return nil
}
out := make([]string, 0, len(order))
for _, value := range order {
out = append(out, fmt.Sprintf("%s=%d", value, counts[value]))
}
return out
}
func payloadHasNonEmptyField(payload map[string]any, key string) bool {
if len(payload) == 0 {
return false
}
value, ok := payload[key]
if !ok {
return false
}
return !payloadValueIsEmpty(value)
}
func messageUpdateNameForLog(update agentsessionstore.WorkspaceAgentMessageUpdate) string {
return firstNonEmpty(
payloadStringValueForLog(update.Payload, "toolName"),
payloadStringValueForLog(update.Payload, "name"),
update.Title,
)
}
func shouldLogMessageUpdateError(update agentsessionstore.WorkspaceAgentMessageUpdate) bool {
if !messageUpdateIsCallForLog(update) {
return false
}
status := strings.ToLower(strings.TrimSpace(update.Status))
if status == "failed" || status == "failure" || status == "error" || status == "errored" {
return true
}
kind := strings.ToLower(strings.TrimSpace(update.Kind))
return kind == "call.failed" || kind == "call.errored"
}
func messageUpdateIsCallForLog(update agentsessionstore.WorkspaceAgentMessageUpdate) bool {
kind := strings.ToLower(strings.TrimSpace(update.Kind))
if kind == "tool_call" || strings.HasPrefix(kind, "call.") {
return true
}
return strings.TrimSpace(update.CallID) != ""
}
func messageUpdateErrorSummaryForLog(payload map[string]any) string {
for _, candidate := range []any{
payloadFieldValueForLog(payload, "error", "message"),
payloadFieldValueForLog(payload, "error", "stderr"),
payloadFieldValueForLog(payload, "error", "reason"),
payloadFieldValueForLog(payload, "error", "output"),
payloadFieldValueForLog(payload, "error", "aggregated_output"),
payloadFieldValueForLog(payload, "error", "text"),
payloadValueForLog(payload, "error"),
payloadFieldValueForLog(payload, "output", "stderr"),
payloadFieldValueForLog(payload, "output", "aggregated_output"),
payloadFieldValueForLog(payload, "output", "output"),
} {
if summary := summarizePayloadValueForLog(candidate, maxReportLogErrorLength); summary != "" {
return summary
}
}
return ""
}
func payloadStringValueForLog(payload map[string]any, key string) string {
value := payloadValueForLog(payload, key)
switch typed := value.(type) {
case string:
return typed
default:
return ""
}
}
func payloadValueForLog(payload map[string]any, key string) any {
if len(payload) == 0 {
return nil
}
return payload[key]
}
func payloadFieldValueForLog(payload map[string]any, key string, field string) any {
value := payloadValueForLog(payload, key)
object, ok := value.(map[string]any)
if !ok {
return nil
}
return object[field]
}
func summarizePayloadValueForLog(value any, limit int) string {
if payloadValueIsEmpty(value) {
return ""
}
var summary string
switch typed := value.(type) {
case string:
summary = typed
case map[string]any:
summary = summarizePayloadMapForLog(typed)
default:
summary = fmt.Sprint(typed)
}
summary = strings.Join(strings.Fields(summary), " ")
return trimReportLogSummaryToLength(summary, limit)
}
func summarizePayloadMapForLog(payload map[string]any) string {
if len(payload) == 0 {
return ""
}
keys := make([]string, 0, len(payload))
for key := range payload {
keys = append(keys, key)
}
sort.Strings(keys)
parts := make([]string, 0, len(keys))
for _, key := range keys {
value := summarizePayloadValueForLog(payload[key], maxReportLogErrorLength)
if value == "" {
continue
}
parts = append(parts, fmt.Sprintf("%s=%s", key, value))
}
return strings.Join(parts, " ")
}
func summarizeBodyKinds(hasInput, hasOutput, hasError, hasText bool) string {
kinds := make([]string, 0, 4)
if hasInput {
kinds = append(kinds, "input")
}
if hasOutput {
kinds = append(kinds, "output")
}
if hasError {
kinds = append(kinds, "error")
}
if hasText {
kinds = append(kinds, "text")
}
if len(kinds) == 0 {
return "-"
}
return strings.Join(kinds, "+")
}
func turnPatchSummary(turn *agentsessionstore.WorkspaceAgentTurnPatch) string {
if turn == nil {
return ""
}
parts := []string{logSummaryValue(turn.TurnID)}
if phase := logSummaryValue(turn.Phase); phase != "-" {
parts = append(parts, phase)
}
if outcome := logSummaryValue(turn.Outcome); outcome != "-" {
parts = append(parts, outcome)
}
return strings.Join(parts, "/")
}
func turnLifecycleSummary(lifecycle *canonical.WorkspaceAgentTurnLifecycle) string {
if lifecycle == nil {
return ""
}
activeTurnID := ""
if lifecycle.ActiveTurnID != nil {
activeTurnID = *lifecycle.ActiveTurnID
}
parts := []string{logSummaryValue(activeTurnID), logSummaryValue(lifecycle.Phase)}
if lifecycle.Outcome != nil {
if outcome := logSummaryValue(*lifecycle.Outcome); outcome != "-" {
parts = append(parts, outcome)
}
}
return strings.Join(parts, "/")
}
func rootProviderTurnSummary(turn *canonical.WorkspaceAgentRootProviderTurnTransition) string {
if turn == nil {
return ""
}
parts := []string{
logSummaryValue(turn.RootTurnID),
logSummaryValue(turn.ProviderTurnID),
logSummaryValue(turn.Phase),
}
if outcome := logSummaryValue(turn.Outcome); outcome != "-" {
parts = append(parts, outcome)
}
return strings.Join(parts, "/")
}
func logSummaryValue(value string) string {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return "-"
}
return trimReportLogSummary(trimmed)
}
func trimReportLogSummary(value string) string {
return trimReportLogSummaryToLength(value, maxReportLogSummaryLength)
}
func trimReportLogSummaryToLength(value string, limit int) string {
trimmed := strings.TrimSpace(value)
if limit <= 3 {
return trimmed
}
if len(trimmed) <= limit {
return trimmed
}
return trimmed[:limit-3] + "..."
}
func minReportLogInt(left, right int) int {
if left < right {
return left
}
return right
}