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

266 lines
11 KiB
Go

package agenthost
import (
"context"
"strings"
"sync"
"time"
)
type Config struct {
CanonicalStore CanonicalStore
RuntimeRailPlacement RuntimeSessionRailPlacementResolver
InteractionTrees CanonicalInteractionTreeStore
TurnSubmissions TurnSubmissionStore
EffectiveHistory EffectiveHistoryStore
SessionManagement SessionManagementStore
SessionBatchManagement SessionBatchManagementStore
SessionDeletionGuard SessionDeletionGuard
SessionPurge SessionPurgeStore
DeletedSessions DeletedSessionStore
HistoricalState HistoricalSessionStateStore
SessionForks SessionForkStore
SessionForkRecovery SessionForkRecoveryStore
SessionForkRuntime SessionForkRuntime
SessionForkContext SessionForkContextPolicy
SessionForkState SessionForkProviderStateBinder
SessionForkAttachments SessionForkAttachmentStager
Runtime RuntimeController
HistoryRuntime RuntimeHistoryController
RuntimePreparation RuntimePreparationPort
SettingsPolicy SettingsPolicy
Attachments AttachmentMaterializer
Clock Clock
SessionLocker SessionLocker
RuntimeStartGate RuntimeStartGate
LifecycleObserver LifecycleObserver
TerminalFailureObserver TerminalFailureObserver
CommitObserver CommitObserver
RuntimeOperations RuntimeOperationStore
OperationEvents RuntimeOperationEventPublisher
OperationOwner string
Scheduler Scheduler
StaleTurnSettler StaleTurnSettler
GoalStore GoalStateStore
GoalFences GoalGenerationFenceStore
GoalRuntime GoalRuntimeController
GoalInbox GoalReconcileInboxStore
GoalOwner string
GoalClock Clock
GoalAttemptTimeout time.Duration
GoalRecoveryBudget time.Duration
GoalMaxAttempts int
GoalDispatchDeadline time.Duration
GoalActor *SessionActor
SessionMutationActor *SessionActor
// EditRetryDisabled neutralizes the durable edit-and-retry feature (PR
// #1681). When set, new edit-retry operations are refused and any operation
// left over from before it was disabled is quarantined during recovery
// instead of engaging the saga. The zero value keeps the feature enabled so
// its unit/conformance tests still exercise it; production wiring sets this
// true. Remove once the saga's resend/recovery gap is fixed.
EditRetryDisabled bool
SideConversationRuntime SideConversationRuntime
}
type Host struct {
store CanonicalStore
runtimeRailPlacement RuntimeSessionRailPlacementResolver
interactionTrees CanonicalInteractionTreeStore
turnSubmissions TurnSubmissionStore
effectiveHistory EffectiveHistoryStore
sessionManagement SessionManagementStore
sessionBatchManagement SessionBatchManagementStore
sessionDeletionGuard SessionDeletionGuard
sessionPurge SessionPurgeStore
deletedSessions DeletedSessionStore
historicalState HistoricalSessionStateStore
sessionForks SessionForkStore
sessionForkRecovery SessionForkRecoveryStore
sessionForkRuntime SessionForkRuntime
sideRuntime SideConversationRuntime
sessionForkContext SessionForkContextPolicy
sessionForkState SessionForkProviderStateBinder
sessionForkAttachments SessionForkAttachmentStager
runtime RuntimeController
historyRuntime RuntimeHistoryController
preparation RuntimePreparationPort
settingsPolicy SettingsPolicy
attachments AttachmentMaterializer
clock Clock
locker SessionLocker
startupGate RuntimeStartGate
observer LifecycleObserver
terminalFailure TerminalFailureObserver
commitObserver CommitObserver
operations RuntimeOperationStore
events RuntimeOperationEventPublisher
owner string
scheduler Scheduler
staleTurns StaleTurnSettler
goals GoalStateStore
goalFences GoalGenerationFenceStore
goalRuntime GoalRuntimeController
goalInbox GoalReconcileInboxStore
goalOwner string
goalClock Clock
goalAttemptTimeout time.Duration
goalRecoveryBudget time.Duration
goalMaxAttempts int
goalDispatchDeadline time.Duration
goalActor *SessionActor
sessionMutationActor *SessionActor
workspaceRuntimeAdmission *workspaceRuntimeAdmission
editRetryDisabled bool
goalFencesRestored sync.Map
sideMu sync.Mutex
sideConversations map[string]sideConversationRegistration
}
func New(config Config) *Host {
goalActor := config.GoalActor
if goalActor == nil {
goalActor = NewSessionActor()
}
sessionMutationActor := config.SessionMutationActor
if sessionMutationActor == nil {
sessionMutationActor = NewSessionActor()
}
host := &Host{
store: config.CanonicalStore, runtimeRailPlacement: config.RuntimeRailPlacement,
interactionTrees: config.InteractionTrees,
turnSubmissions: config.TurnSubmissions, effectiveHistory: config.EffectiveHistory,
sessionManagement: config.SessionManagement, sessionBatchManagement: config.SessionBatchManagement, sessionDeletionGuard: config.SessionDeletionGuard, sessionPurge: config.SessionPurge,
deletedSessions: config.DeletedSessions,
sessionForks: config.SessionForks, sessionForkRuntime: config.SessionForkRuntime,
historicalState: config.HistoricalState,
sideRuntime: config.SideConversationRuntime,
sessionForkContext: config.SessionForkContext, sessionForkState: config.SessionForkState,
sessionForkAttachments: config.SessionForkAttachments,
runtime: config.Runtime,
historyRuntime: config.HistoryRuntime,
sessionForkRecovery: config.SessionForkRecovery,
preparation: config.RuntimePreparation, settingsPolicy: config.SettingsPolicy, attachments: config.Attachments,
clock: config.Clock, locker: config.SessionLocker, startupGate: config.RuntimeStartGate,
observer: config.LifecycleObserver, terminalFailure: config.TerminalFailureObserver,
commitObserver: config.CommitObserver,
operations: config.RuntimeOperations, events: config.OperationEvents,
owner: config.OperationOwner, scheduler: config.Scheduler, staleTurns: config.StaleTurnSettler,
goals: config.GoalStore, goalFences: config.GoalFences, goalRuntime: config.GoalRuntime, goalInbox: config.GoalInbox,
goalOwner: config.GoalOwner, goalClock: config.GoalClock,
goalAttemptTimeout: config.GoalAttemptTimeout, goalRecoveryBudget: config.GoalRecoveryBudget,
goalMaxAttempts: config.GoalMaxAttempts, goalDispatchDeadline: config.GoalDispatchDeadline,
goalActor: goalActor, sessionMutationActor: sessionMutationActor,
workspaceRuntimeAdmission: newWorkspaceRuntimeAdmission(),
editRetryDisabled: config.EditRetryDisabled,
sideConversations: make(map[string]sideConversationRegistration),
}
if host.runtimeRailPlacement == nil {
host.runtimeRailPlacement, _ = host.store.(RuntimeSessionRailPlacementResolver)
}
if host.interactionTrees == nil {
host.interactionTrees, _ = host.store.(CanonicalInteractionTreeStore)
}
if host.deletedSessions == nil {
host.deletedSessions, _ = config.SessionBatchManagement.(DeletedSessionStore)
}
if host.sessionForkRecovery == nil {
host.sessionForkRecovery, _ = host.sessionForks.(SessionForkRecoveryStore)
}
// Durable runtime and goal failures reach TerminalFailureObserver through
// the same wrappers, so an adapter that wires only failure analytics still
// observes them.
observesCommits := host.commitObserver != nil || host.terminalFailure != nil
if host.operations != nil && observesCommits {
host.operations = &observedRuntimeOperationStore{RuntimeOperationStore: host.operations, host: host}
}
if host.effectiveHistory != nil && observesCommits {
host.effectiveHistory = &observedEffectiveHistoryStore{
EffectiveHistoryStore: host.effectiveHistory,
host: host,
}
}
if host.goals != nil && observesCommits {
host.goals = &observedGoalStateStore{GoalStateStore: host.goals, host: host}
}
if registrar, ok := config.GoalRuntime.(GoalRuntimeControlLifecycleRegistrar); ok {
registrar.SetGoalControlAppliedSink(host.ObserveRuntimeGoalControlApplied)
}
return host
}
// observeStep reports a diagnostic lifecycle step. A failed step never emits a
// TerminalFailure on its own; it only names the stage that the enclosing
// command reports once at its boundary.
func (h *Host) observeStep(ctx context.Context, flow, name, workspaceID, sessionID, provider string, startedAt time.Time, err error) {
if h != nil && h.observer != nil {
h.observer.ObserveLifecycleStep(ctx, LifecycleStep{
Flow: flow, Name: name, AgentSessionID: sessionID, Provider: provider, StartedAt: startedAt, Err: err,
})
}
recordCommandFailureStage(ctx, flow, workspaceID, sessionID, provider, name, err)
}
func (h *Host) observeGuidanceTargetFailure(
ctx context.Context,
ref SessionRef,
provider, turnID, clientSubmitID string,
startedAt time.Time,
err error,
) {
if err == nil {
return
}
if h != nil && h.observer != nil {
h.observer.ObserveLifecycleStep(ctx, LifecycleStep{
Flow: "guidance", Name: "guidance_target", AgentSessionID: ref.AgentSessionID,
Provider: provider, StartedAt: startedAt, Err: err,
})
}
h.observeTerminalFailure(ctx, TerminalFailure{
Flow: "guidance",
FailureStage: "guidance_target",
WorkspaceID: ref.WorkspaceID,
AgentSessionID: ref.AgentSessionID,
TurnID: strings.TrimSpace(turnID),
ClientSubmitID: strings.TrimSpace(clientSubmitID),
Provider: provider,
ErrorCode: guidanceTargetFailureCode(err),
ErrorMessage: err.Error(),
Retryable: false,
})
}
func (h *Host) observeTerminalFailure(ctx context.Context, failure TerminalFailure) {
if h == nil || h.terminalFailure == nil {
return
}
if h.store != nil && strings.TrimSpace(failure.WorkspaceID) != "" && strings.TrimSpace(failure.AgentSessionID) != "" {
if session, found, err := h.store.GetSession(ctx, failure.WorkspaceID, failure.AgentSessionID); err == nil && found {
if strings.TrimSpace(failure.Provider) == "" {
failure.Provider = strings.TrimSpace(session.Provider)
}
failure.IsChildSession = failure.IsChildSession || canonicalSessionIsChild(session)
}
}
if failure.ErrorMessage == "" && failure.ErrorCode == "" {
return
}
// A specific emission owns the incident; the enclosing command boundary
// must not report the same failure a second time.
markCommandTerminalFailureEmitted(ctx)
h.terminalFailure.ObserveTerminalFailure(ctx, failure)
}
type systemClock struct{}
func (systemClock) Now() time.Time { return time.Now() }
func (h *Host) now() time.Time {
if h != nil && h.clock != nil {
return h.clock.Now()
}
return systemClock{}.Now()
}