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
120 lines
4.0 KiB
Go
120 lines
4.0 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
agentactivitybiz "github.com/tutti-os/tutti/packages/agent/store-sqlite"
|
|
"github.com/tutti-os/tutti/packages/agent/store-sqlite/canonical"
|
|
"github.com/tutti-os/tutti/services/tuttid/biz/agentanalytics"
|
|
agentnoderesult "github.com/tutti-os/tutti/services/tuttid/service/reporter/events/agent/node_result"
|
|
)
|
|
|
|
func canonicalRailSection(placement *canonical.RailPlacement) *agentactivitybiz.RailSection {
|
|
if placement == nil {
|
|
return nil
|
|
}
|
|
return &agentactivitybiz.RailSection{
|
|
Kind: strings.TrimSpace(placement.Kind),
|
|
ProjectPath: strings.TrimSpace(placement.ProjectPath),
|
|
Key: strings.TrimSpace(placement.SectionKey),
|
|
}
|
|
}
|
|
|
|
func (p *ActivityProjection) reportFailedRuntimeNodeResult(ctx context.Context, input canonical.ReportSessionStateInput) {
|
|
if p == nil || p.analyticsReporter == nil {
|
|
return
|
|
}
|
|
if !isFailedAgentLifecycleStatus(input.State.LifecycleStatus) {
|
|
return
|
|
}
|
|
errorMessage := strings.TrimSpace(input.State.LastError)
|
|
if errorMessage == "" {
|
|
errorMessage = "Agent runtime session failed."
|
|
}
|
|
agentnoderesult.Track(ctx, p.analyticsReporter, agentnoderesult.BuildParams(agentnoderesult.NodeResultInput{
|
|
AgentSessionID: input.AgentSessionID,
|
|
ErrorCode: classifyRuntimeNodeErrorCode(errorMessage),
|
|
ErrorMessage: errorMessage,
|
|
Flow: "runtime_activity",
|
|
Node: "runtime_exec",
|
|
Provider: firstNonEmptyString(input.State.Provider, input.Source.Provider),
|
|
Status: "failure",
|
|
}))
|
|
}
|
|
|
|
func sessionStateTitle(state canonical.WorkspaceAgentSessionStateUpdate) string {
|
|
return firstNonEmptyString(
|
|
state.Title,
|
|
payloadString(state.RuntimeContext, "title"),
|
|
)
|
|
}
|
|
|
|
func activitySessionUpdateEventPayload(workspaceID string, agentSessionID string, lastEventUnixMS int64, agentTargetID ...string) map[string]any {
|
|
if lastEventUnixMS <= 0 {
|
|
lastEventUnixMS = time.Now().UnixMilli()
|
|
}
|
|
payload := map[string]any{
|
|
"agentSessionId": strings.TrimSpace(agentSessionID),
|
|
"eventType": "session_reconcile_required",
|
|
"lastEventUnixMs": lastEventUnixMS,
|
|
"workspaceId": strings.TrimSpace(workspaceID),
|
|
}
|
|
if len(agentTargetID) > 0 {
|
|
if value := strings.TrimSpace(agentTargetID[0]); value != "" {
|
|
payload["agentTargetId"] = value
|
|
}
|
|
}
|
|
return payload
|
|
}
|
|
|
|
func activitySessionDeletedEventPayload(workspaceID string, agentSessionID string) map[string]any {
|
|
return map[string]any{
|
|
"agentSessionId": strings.TrimSpace(agentSessionID),
|
|
"deletedAtUnixMs": time.Now().UnixMilli(),
|
|
"eventType": "session_deleted",
|
|
"workspaceId": strings.TrimSpace(workspaceID),
|
|
}
|
|
}
|
|
|
|
func activitySessionRestoredEventPayload(workspaceID string, agentSessionID string) map[string]any {
|
|
return map[string]any{
|
|
"agentSessionId": strings.TrimSpace(agentSessionID),
|
|
"eventType": "session_restored",
|
|
"restoredAtUnixMs": time.Now().UnixMilli(),
|
|
"workspaceId": strings.TrimSpace(workspaceID),
|
|
}
|
|
}
|
|
|
|
func (p *ActivityProjection) PublishSessionDeleted(ctx context.Context, workspaceID string, agentSessionID string) {
|
|
p.publishActivityUpdated(ctx, workspaceID, agentSessionID,
|
|
"session_deleted", activitySessionDeletedEventPayload(workspaceID, agentSessionID))
|
|
}
|
|
|
|
func isFailedAgentLifecycleStatus(status string) bool {
|
|
switch strings.ToLower(strings.TrimSpace(status)) {
|
|
case "failed", "failure", "error", "errored":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func classifyRuntimeNodeErrorCode(message string) string {
|
|
normalized := strings.ToLower(strings.TrimSpace(message))
|
|
if strings.Contains(normalized, "network") ||
|
|
strings.Contains(normalized, "connection") ||
|
|
strings.Contains(normalized, "disconnected") ||
|
|
strings.Contains(normalized, "econnreset") ||
|
|
strings.Contains(normalized, "socket") {
|
|
return agentanalytics.ErrorCodeRuntimeNetworkDisconnected
|
|
}
|
|
if strings.Contains(normalized, "process") ||
|
|
strings.Contains(normalized, "exit") ||
|
|
strings.Contains(normalized, "exited") {
|
|
return agentanalytics.ErrorCodeRuntimeProcessExited
|
|
}
|
|
return agentanalytics.ErrorCodeRuntimeExecFailed
|
|
}
|