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

162 lines
5.0 KiB
Go

package agenthost
import (
"context"
"strings"
"sync"
"time"
)
// commandPreconditionStage names the boundary emission for a command that
// failed before it reached any observable lifecycle step: argument validation,
// submit-claim preparation, send fences, and lock acquisition.
const commandPreconditionStage = "precondition"
// commandTerminalFailure aggregates one Host command into at most one
// TerminalFailure. LifecycleStep stays diagnostic; it only records the first
// failing stage so the command boundary can name where the command broke.
// A more specific emission inside the command (guidance target, goal control,
// durable delta) marks the aggregation as already reported.
type commandTerminalFailure struct {
flow string
mu sync.Mutex
workspaceID string
agentSessionID string
operationID string
requestID string
clientSubmitID string
turnID string
provider string
providerAcceptanceDiagnostics *RuntimeProviderAcceptanceDiagnostics
stage string
startedAt time.Time
emitted bool
}
type commandTerminalFailureInput struct {
flow string
workspaceID string
agentSessionID string
operationID string
requestID string
clientSubmitID string
turnID string
}
type commandTerminalFailureKey struct{}
// beginCommand scopes a terminal-failure aggregation to one Host command. A
// nested command scopes its own aggregation for its callees only.
func (h *Host) beginCommand(
ctx context.Context,
input commandTerminalFailureInput,
) (context.Context, *commandTerminalFailure) {
command := &commandTerminalFailure{
flow: strings.TrimSpace(input.flow),
workspaceID: strings.TrimSpace(input.workspaceID),
agentSessionID: strings.TrimSpace(input.agentSessionID),
operationID: strings.TrimSpace(input.operationID),
requestID: strings.TrimSpace(input.requestID),
clientSubmitID: strings.TrimSpace(input.clientSubmitID),
turnID: strings.TrimSpace(input.turnID),
}
if h != nil {
command.startedAt = h.now()
}
if h == nil {
return ctx, command
}
return context.WithValue(ctx, commandTerminalFailureKey{}, command), command
}
func commandTerminalFailureFrom(ctx context.Context) *commandTerminalFailure {
command, _ := ctx.Value(commandTerminalFailureKey{}).(*commandTerminalFailure)
return command
}
// recordCommandFailureStage keeps the first failing stage of the running
// command. Steps from a different flow (notably session_create_cleanup, which
// runs only after a primary failure) never claim the stage.
func recordCommandFailureStage(ctx context.Context, flow, workspaceID, agentSessionID, provider, stage string, err error) {
command := commandTerminalFailureFrom(ctx)
if command == nil || err == nil || command.flow != flow {
return
}
command.mu.Lock()
defer command.mu.Unlock()
if command.stage != "" {
return
}
command.stage = stage
if value := strings.TrimSpace(workspaceID); value != "" {
command.workspaceID = value
}
if value := strings.TrimSpace(agentSessionID); value != "" {
command.agentSessionID = value
}
if value := strings.TrimSpace(provider); value != "" {
command.provider = value
}
}
func markCommandTerminalFailureEmitted(ctx context.Context) {
command := commandTerminalFailureFrom(ctx)
if command == nil {
return
}
command.mu.Lock()
command.emitted = true
command.mu.Unlock()
}
func recordProviderAcceptanceDiagnostics(
ctx context.Context,
dispatch RuntimeProviderDispatchResult,
) {
command := commandTerminalFailureFrom(ctx)
if command == nil || dispatch.AcceptanceDiagnostics == nil {
return
}
diagnostics := *dispatch.AcceptanceDiagnostics
command.mu.Lock()
command.providerAcceptanceDiagnostics = &diagnostics
command.mu.Unlock()
}
// finish emits the single aggregated failure for a command that returned err.
func (c *commandTerminalFailure) finish(ctx context.Context, h *Host, err error) {
if c == nil || err == nil {
return
}
c.mu.Lock()
emitted, stage := c.emitted, c.stage
failure := TerminalFailure{
Flow: c.flow,
WorkspaceID: c.workspaceID,
AgentSessionID: c.agentSessionID,
OperationID: c.operationID,
RequestID: c.requestID,
ClientSubmitID: c.clientSubmitID,
TurnID: c.turnID,
Provider: c.provider,
ProviderAcceptanceDiagnostics: c.providerAcceptanceDiagnostics,
}
startedAt := c.startedAt
c.mu.Unlock()
if emitted {
return
}
if stage == "" {
stage = commandPreconditionStage
}
failure.FailureStage = stage
failure.ErrorCode = terminalFailureCode(err)
failure.ErrorMessage = err.Error()
failure.Retryable = isRetryableRuntimeOperationError(err)
if duration := h.now().Sub(startedAt).Milliseconds(); duration > 0 {
failure.DurationMS = duration
}
h.observeTerminalFailure(ctx, failure)
}