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

859 lines
35 KiB
Go

// Package hostadapter adapts the daemon runtime contract to Agent Host.
//
// Both sides of this boundary are owned by Tutti. Product services should
// provide only the concrete runtime backend and current-user identity instead
// of maintaining their own lifecycle and error mappings.
package hostadapter
import (
"context"
"errors"
"strings"
agentruntime "github.com/tutti-os/tutti/packages/agent/daemon/runtime"
host "github.com/tutti-os/tutti/packages/agent/host"
"github.com/tutti-os/tutti/packages/agent/store-sqlite/canonical"
)
// RuntimeBackend is the daemon controller surface required by Agent Host.
// *runtime.Controller implements this interface.
type RuntimeBackend interface {
Start(context.Context, agentruntime.StartInput) (agentruntime.StartResult, error)
PublishSessionInitialization(context.Context, string, string) (agentruntime.Session, error)
Resume(context.Context, agentruntime.ResumeInput) (agentruntime.Session, error)
Session(string, string) (agentruntime.Session, bool)
State(string, string) (agentruntime.SessionStateSnapshot, error)
CanResume(agentruntime.ResumeInput) bool
Exec(context.Context, agentruntime.ExecInput) (agentruntime.ExecResult, error)
DurablyReportSubmitProvenance(context.Context, agentruntime.SubmitProvenanceInput) error
ValidatePromptContent(context.Context, agentruntime.ExecInput) error
Cancel(context.Context, agentruntime.CancelInput) (agentruntime.CancelResult, error)
SubmitInteractive(context.Context, agentruntime.SubmitInteractiveInput) (agentruntime.SubmitInteractiveResult, error)
InteractiveDisposition(string, string, string, string, string) agentruntime.InteractiveDisposition
UpdateSettings(context.Context, agentruntime.UpdateSettingsInput) (agentruntime.UpdateSettingsResult, error)
SetTitle(context.Context, string, string, string) (agentruntime.Session, error)
SetVisible(context.Context, string, string, bool) (agentruntime.Session, error)
Close(context.Context, agentruntime.CloseInput) (agentruntime.CloseResult, error)
GoalControl(context.Context, agentruntime.GoalControlInput) (agentruntime.GoalControlResult, error)
ReconcileGoal(context.Context, agentruntime.GoalReconcileInput) (agentruntime.GoalReconcileResult, error)
GoalCapabilities(context.Context, agentruntime.GoalReconcileInput) (agentruntime.GoalAdapterCapabilities, error)
}
type runtimeRetainedSettingsBackend interface {
UpdateRetainedSettings(context.Context, agentruntime.UpdateSettingsInput) (agentruntime.UpdateSettingsResult, error)
}
type runtimeHistoryBackend interface {
SupportsEffectiveHistory(context.Context, agentruntime.EffectiveHistoryInput) (bool, error)
ReadEffectiveHistory(context.Context, agentruntime.EffectiveHistoryInput) (agentruntime.EffectiveHistorySnapshot, error)
RollbackLatestTurn(context.Context, agentruntime.EffectiveHistoryInput) (agentruntime.HistoryMutationResult, error)
}
// RuntimeController implements Agent Host runtime ports with a daemon backend.
type RuntimeController struct {
Backend RuntimeBackend
CurrentUserID func() string
}
var (
_ host.RuntimeController = (*RuntimeController)(nil)
_ host.RuntimeSessionInitializationPublisher = (*RuntimeController)(nil)
_ host.RuntimeSessionRepreparer = (*RuntimeController)(nil)
_ host.RuntimeHistoryController = (*RuntimeController)(nil)
_ host.RuntimeProviderTurnAcceptanceReconciler = (*RuntimeController)(nil)
_ host.RuntimeSessionLiveness = (*RuntimeController)(nil)
_ host.RuntimeWorkspaceDisconnector = (*RuntimeController)(nil)
_ host.RuntimeWorkspaceDisconnectTargeter = (*RuntimeController)(nil)
_ host.RuntimeRetainedSettingsUpdater = (*RuntimeController)(nil)
_ host.RuntimeSubmitProvenanceReporter = (*RuntimeController)(nil)
_ host.SessionForkRuntime = (*RuntimeController)(nil)
_ host.SessionForkTurnBindingRecoveryRuntime = (*RuntimeController)(nil)
_ host.SideConversationRuntime = (*RuntimeController)(nil)
_ host.GoalRuntimeController = (*RuntimeController)(nil)
_ host.GoalRuntimeControlLifecycleRegistrar = (*RuntimeController)(nil)
_ host.GoalRuntimeReconciler = (*RuntimeController)(nil)
_ host.GoalRuntimeRecoveryPolicyResolver = (*RuntimeController)(nil)
_ host.GoalRuntimeGenerationFencer = (*RuntimeController)(nil)
)
type runtimeSessionReprepareBackend interface {
Reprepare(context.Context, agentruntime.ResumeInput) (agentruntime.Session, error)
}
func (a *RuntimeController) SupportsEffectiveHistory(
ctx context.Context,
input host.RuntimeHistoryInput,
) (bool, error) {
if err := a.requireBackend(); err != nil {
return false, err
}
backend, ok := a.Backend.(runtimeHistoryBackend)
if !ok {
return false, nil
}
return backend.SupportsEffectiveHistory(ctx, runtimeHistoryInput(input))
}
func (a *RuntimeController) ReadEffectiveHistory(
ctx context.Context,
input host.RuntimeHistoryInput,
) (host.RuntimeHistorySnapshot, error) {
if err := a.requireBackend(); err != nil {
return host.RuntimeHistorySnapshot{}, err
}
backend, ok := a.Backend.(runtimeHistoryBackend)
if !ok {
return host.RuntimeHistorySnapshot{}, host.ErrRuntimeHistoryUnsupported
}
snapshot, err := backend.ReadEffectiveHistory(ctx, runtimeHistoryInput(input))
return hostHistorySnapshot(snapshot), mapRuntimeError(err)
}
func (a *RuntimeController) RollbackLatestTurn(
ctx context.Context,
input host.RuntimeHistoryInput,
) (host.RuntimeHistoryMutationResult, error) {
if err := a.requireBackend(); err != nil {
return host.RuntimeHistoryMutationResult{
Disposition: host.RuntimeDispatchDispositionNotDispatched,
}, err
}
backend, ok := a.Backend.(runtimeHistoryBackend)
if !ok {
return host.RuntimeHistoryMutationResult{
Disposition: host.RuntimeDispatchDispositionNotDispatched,
}, host.ErrRuntimeHistoryUnsupported
}
result, err := backend.RollbackLatestTurn(ctx, runtimeHistoryInput(input))
projected := host.RuntimeHistoryMutationResult{
Disposition: host.RuntimeDispatchDisposition(result.Disposition),
}
if result.Snapshot != nil {
snapshot := hostHistorySnapshot(*result.Snapshot)
projected.Snapshot = &snapshot
}
return projected, mapRuntimeError(err)
}
func (a *RuntimeController) Start(ctx context.Context, input host.RuntimeStartInput) (host.RuntimeStartResult, error) {
if err := a.requireBackend(); err != nil {
return host.RuntimeStartResult{}, err
}
result, err := a.Backend.Start(ctx, agentruntime.StartInput{
RoomID: input.WorkspaceID,
AgentSessionID: input.AgentSessionID,
AgentTargetID: input.AgentTargetID,
Provider: input.Provider,
CWD: input.Cwd,
Env: append([]string(nil), input.Env...),
MCPServers: runtimeMCPServerBindings(input.MCPServers),
Title: input.Title,
InitialTitleEstablished: input.InitialTitleEstablished,
Visible: input.Visible,
RuntimeContext: cloneMap(input.RuntimeContext),
ProviderTargetRef: cloneMap(input.ProviderTargetRef),
PermissionModeID: input.PermissionModeID,
Settings: runtimeSettings(host.ComposerSettings{
CodexSaverMode: input.CodexSaverMode,
RTKSaverMode: input.RTKSaverMode,
Model: input.Model,
PermissionModeID: input.PermissionModeID,
PlanMode: input.PlanMode,
BrowserUse: input.BrowserUse,
ComputerUse: input.ComputerUse,
ReasoningEffort: input.ReasoningEffort,
Speed: input.Speed,
ConversationDetailMode: input.ConversationDetailMode,
}),
Provisional: input.Provisional,
CanonicalInitPending: input.CanonicalInitPending,
})
if err != nil {
return host.RuntimeStartResult{}, mapRuntimeError(err)
}
session := a.sessionWithState(result.Session)
session.Provisional = input.Provisional
return host.RuntimeStartResult{Session: session, Created: result.Created}, nil
}
func (a *RuntimeController) PublishSessionInitialization(
ctx context.Context,
input host.RuntimeSessionInitializationPublishInput,
) (host.ProviderRuntimeSession, error) {
if err := a.requireBackend(); err != nil {
return host.ProviderRuntimeSession{}, err
}
session, err := a.Backend.PublishSessionInitialization(
ctx,
input.WorkspaceID,
input.AgentSessionID,
)
if err != nil {
return host.ProviderRuntimeSession{}, mapRuntimeError(err)
}
return a.sessionWithState(session), nil
}
func (a *RuntimeController) Resume(ctx context.Context, input host.RuntimeResumeInput) (host.ProviderRuntimeSession, error) {
if err := a.requireBackend(); err != nil {
return host.ProviderRuntimeSession{}, err
}
session, err := a.Backend.Resume(ctx, runtimeResumeInput(input))
if err != nil {
return host.ProviderRuntimeSession{}, mapRuntimeError(err)
}
return a.sessionWithState(session), nil
}
func (a *RuntimeController) Reprepare(ctx context.Context, input host.RuntimeResumeInput) (host.ProviderRuntimeSession, error) {
if err := a.requireBackend(); err != nil {
return host.ProviderRuntimeSession{}, err
}
backend, ok := a.Backend.(runtimeSessionReprepareBackend)
if !ok {
return host.ProviderRuntimeSession{}, host.ErrRuntimeSessionReprepareUnavailable
}
session, err := backend.Reprepare(ctx, runtimeResumeInput(input))
if err != nil {
return host.ProviderRuntimeSession{}, mapRuntimeError(err)
}
return a.sessionWithState(session), nil
}
func (a *RuntimeController) Session(workspaceID, sessionID string) (host.ProviderRuntimeSession, bool) {
if a == nil || a.Backend == nil {
return host.ProviderRuntimeSession{}, false
}
session, found := a.Backend.Session(workspaceID, sessionID)
if !found {
return host.ProviderRuntimeSession{}, false
}
return a.sessionWithState(session), true
}
func (a *RuntimeController) RuntimeSessionLive(workspaceID, sessionID string) bool {
if a == nil || a.Backend == nil {
return false
}
liveness, ok := a.Backend.(interface {
HasLiveSession(string, string) bool
})
return ok && liveness.HasLiveSession(strings.TrimSpace(workspaceID), strings.TrimSpace(sessionID))
}
func (a *RuntimeController) CanResume(input host.RuntimeResumeInput) bool {
return a != nil && a.Backend != nil && a.Backend.CanResume(runtimeResumeInput(input))
}
func (a *RuntimeController) Exec(ctx context.Context, input host.RuntimeExecInput) (host.RuntimeExecResult, error) {
if err := a.requireBackend(); err != nil {
return host.RuntimeExecResult{}, err
}
result, err := a.Backend.Exec(ctx, runtimeExecInput(input))
projected := host.RuntimeExecResult{
AgentSessionID: result.AgentSessionID,
Status: result.Status,
TurnID: result.TurnID,
Accepted: result.Accepted,
SessionStatus: result.SessionStatus,
TurnLifecycle: hostTurnLifecycle(result.TurnLifecycle),
SubmitAvailability: host.SubmitAvailability{
State: result.SubmitAvailability.State, Reason: result.SubmitAvailability.Reason,
},
}
if result.ProviderDispatch != nil {
projected.ProviderDispatch.Disposition = host.RuntimeDispatchDisposition(
result.ProviderDispatch.Disposition,
)
if result.ProviderDispatch.Acceptance != nil {
projected.ProviderDispatch.Acceptance = &host.RuntimeProviderAcceptanceReceipt{
ProviderSessionID: result.ProviderDispatch.Acceptance.ProviderSessionID,
ProviderTurnID: result.ProviderDispatch.Acceptance.ProviderTurnID,
Source: host.RuntimeAcceptanceSource(
result.ProviderDispatch.Acceptance.Source,
),
}
}
}
return projected, mapRuntimeError(err)
}
func (a *RuntimeController) DurablyReportSubmitProvenance(ctx context.Context, input host.RuntimeSubmitProvenanceInput) error {
if err := a.requireBackend(); err != nil {
return err
}
return mapRuntimeError(a.Backend.DurablyReportSubmitProvenance(ctx, runtimeSubmitProvenanceInput(input)))
}
func (a *RuntimeController) ReconcileProviderTurnAcceptance(
ctx context.Context,
input host.RuntimeProviderTurnAcceptanceInput,
) error {
if err := a.requireBackend(); err != nil {
return err
}
reconciler, ok := a.Backend.(interface {
ReconcileProviderTurnAcceptance(
context.Context,
agentruntime.ProviderTurnAcceptanceInput,
) error
})
if !ok {
return errors.New("agent runtime cannot reconcile provider turn acceptance")
}
return mapRuntimeError(reconciler.ReconcileProviderTurnAcceptance(
ctx,
agentruntime.ProviderTurnAcceptanceInput{
RoomID: input.WorkspaceID,
AgentSessionID: input.AgentSessionID,
Provider: input.Provider,
RootTurnID: input.RootTurnID,
ExpectedProviderSessionID: input.ExpectedProviderSessionID,
ExpectedProviderTurnID: input.ExpectedProviderTurnID,
ClientUserMessageID: input.ClientUserMessageID,
},
))
}
func (a *RuntimeController) ValidatePromptContent(ctx context.Context, input host.RuntimeExecInput) error {
if err := a.requireBackend(); err != nil {
return err
}
return mapRuntimeError(a.Backend.ValidatePromptContent(ctx, runtimeExecInput(input)))
}
func (a *RuntimeController) Cancel(ctx context.Context, input host.RuntimeCancelInput) (host.RuntimeCancelResult, error) {
if err := a.requireBackend(); err != nil {
return host.RuntimeCancelResult{}, err
}
targets := make([]agentruntime.CancelTarget, 0, len(input.Targets))
for _, target := range input.Targets {
targets = append(targets, agentruntime.CancelTarget{AgentSessionID: target.AgentSessionID, TurnID: target.TurnID})
}
result, err := a.Backend.Cancel(ctx, agentruntime.CancelInput{
RoomID: input.WorkspaceID, RootAgentSessionID: input.RootAgentSessionID, Targets: targets, Reason: input.Reason,
})
confirmed := make([]host.RuntimeCancelTarget, 0, len(result.ConfirmedTargets))
for _, target := range result.ConfirmedTargets {
confirmed = append(confirmed, host.RuntimeCancelTarget{AgentSessionID: target.AgentSessionID, TurnID: target.TurnID})
}
hostResult := host.RuntimeCancelResult{
AgentSessionID: result.AgentSessionID,
Canceled: result.Canceled,
TargetAbsent: result.TargetAbsent,
ProviderStateLost: result.ProviderStateLost,
ConfirmedTargets: confirmed,
}
if errors.Is(err, agentruntime.ErrCancelTargetMismatch) {
return hostResult, host.ErrRuntimeCancelDeliveryUnconfirmed
}
return hostResult, mapRuntimeError(err)
}
func (a *RuntimeController) SubmitInteractive(ctx context.Context, input host.RuntimeSubmitInteractiveInput) (host.RuntimeSubmitInteractiveResult, error) {
if err := a.requireBackend(); err != nil {
return host.RuntimeSubmitInteractiveResult{}, err
}
result, err := a.Backend.SubmitInteractive(ctx, agentruntime.SubmitInteractiveInput{
RoomID: input.WorkspaceID, RootAgentSessionID: input.RootAgentSessionID,
AgentSessionID: input.AgentSessionID, TurnID: input.TurnID, RequestID: input.RequestID,
Action: input.Action, OptionID: input.OptionID, Payload: cloneMap(input.Payload),
})
return host.RuntimeSubmitInteractiveResult{
Disposition: host.RuntimeInteractiveDisposition(result.Disposition),
FollowUpPrompt: result.FollowUpPrompt,
}, mapRuntimeError(err)
}
func (a *RuntimeController) InteractiveDisposition(workspaceID, rootSessionID, sessionID, turnID, requestID string) host.RuntimeInteractiveDisposition {
if a == nil || a.Backend == nil {
return host.RuntimeInteractiveDispositionUnknown
}
return host.RuntimeInteractiveDisposition(a.Backend.InteractiveDisposition(workspaceID, rootSessionID, sessionID, turnID, requestID))
}
func (a *RuntimeController) UpdateSettings(ctx context.Context, input host.RuntimeUpdateSettingsInput) error {
if err := a.requireBackend(); err != nil {
return err
}
_, err := a.Backend.UpdateSettings(ctx, agentruntime.UpdateSettingsInput{
RoomID: input.WorkspaceID, AgentSessionID: input.AgentSessionID,
Settings: agentruntime.SessionSettingsPatch{
Model: input.Settings.Model, ReasoningEffort: input.Settings.ReasoningEffort, Speed: input.Settings.Speed,
PlanMode: input.Settings.PlanMode, BrowserUse: input.Settings.BrowserUse,
ComputerUse: input.Settings.ComputerUse, PermissionModeID: input.Settings.PermissionModeID,
},
})
return mapRuntimeError(err)
}
func (a *RuntimeController) UpdateRetainedSettings(ctx context.Context, input host.RuntimeUpdateSettingsInput) error {
if err := a.requireBackend(); err != nil {
return err
}
backend, ok := a.Backend.(runtimeRetainedSettingsBackend)
if !ok {
return host.ErrWorkspaceDisconnectUnavailable
}
_, err := backend.UpdateRetainedSettings(ctx, agentruntime.UpdateSettingsInput{
RoomID: input.WorkspaceID, AgentSessionID: input.AgentSessionID,
Settings: agentruntime.SessionSettingsPatch{
Model: input.Settings.Model, ReasoningEffort: input.Settings.ReasoningEffort, Speed: input.Settings.Speed,
PlanMode: input.Settings.PlanMode, BrowserUse: input.Settings.BrowserUse,
ComputerUse: input.Settings.ComputerUse, PermissionModeID: input.Settings.PermissionModeID,
},
})
return mapRuntimeError(err)
}
func (a *RuntimeController) SetTitle(ctx context.Context, input host.RuntimeSetTitleInput) (host.ProviderRuntimeSession, error) {
if err := a.requireBackend(); err != nil {
return host.ProviderRuntimeSession{}, err
}
session, err := a.Backend.SetTitle(ctx, input.WorkspaceID, input.AgentSessionID, input.Title)
if err != nil {
return host.ProviderRuntimeSession{}, mapRuntimeError(err)
}
return a.sessionWithState(session), nil
}
func (a *RuntimeController) SetVisible(ctx context.Context, input host.RuntimeSetVisibleInput) (host.ProviderRuntimeSession, error) {
if err := a.requireBackend(); err != nil {
return host.ProviderRuntimeSession{}, err
}
session, err := a.Backend.SetVisible(ctx, input.WorkspaceID, input.AgentSessionID, input.Visible)
if err != nil {
return host.ProviderRuntimeSession{}, mapRuntimeError(err)
}
return a.sessionWithState(session), nil
}
func (a *RuntimeController) Close(ctx context.Context, input host.RuntimeCloseInput) error {
if err := a.requireBackend(); err != nil {
return err
}
_, err := a.Backend.Close(ctx, agentruntime.CloseInput{
RoomID: input.WorkspaceID,
AgentSessionID: input.AgentSessionID,
PreserveCanonicalState: input.PreserveCanonicalState,
})
return mapRuntimeError(err)
}
func (a *RuntimeController) ResolveSessionFork(
ctx context.Context,
source host.ProviderRuntimeSession,
) (host.SessionForkDriverDescriptor, error) {
if err := a.requireBackend(); err != nil {
return host.SessionForkDriverDescriptor{}, err
}
backend, ok := a.Backend.(sessionForkRuntimeBackend)
if !ok {
return host.SessionForkDriverDescriptor{}, nil
}
capabilities, err := backend.ForkCapabilities(ctx, runtimeSession(source))
if err != nil {
return host.SessionForkDriverDescriptor{}, mapRuntimeError(err)
}
if !capabilities.FullSession && !capabilities.ThroughTurn {
return host.SessionForkDriverDescriptor{}, nil
}
return host.SessionForkDriverDescriptor{
Kind: firstNonEmptyString(capabilities.DriverKind, "daemon-runtime-native"),
Version: firstNonEmptyString(capabilities.DriverVersion, "v1"),
StateBindingMode: host.SessionForkStateBindingMode(firstNonEmptyString(capabilities.StateBindingMode, string(host.SessionForkStateBindingHostCopy))),
FullSession: capabilities.FullSession,
ThroughTurn: capabilities.ThroughTurn,
}, nil
}
func (a *RuntimeController) ForkSession(
ctx context.Context,
input host.RuntimeSessionForkInput,
) (host.RuntimeSessionForkResult, error) {
if err := a.requireBackend(); err != nil {
return host.RuntimeSessionForkResult{
DeliveryDisposition: host.SessionForkDeliveryNotStarted,
}, err
}
backend, ok := a.Backend.(sessionForkRuntimeBackend)
if !ok {
return host.RuntimeSessionForkResult{
DeliveryDisposition: host.SessionForkDeliveryNotStarted,
}, host.ErrSessionForkUnsupported
}
result, err := backend.Fork(ctx, agentruntime.SessionForkInput{
Source: runtimeSession(input.Source),
ProviderTurnID: input.SourceProviderTurnID,
ProviderTurnBindingJSON: append([]byte(nil), input.SourceProviderTurnBindingJSON...),
TargetTitle: input.TargetTitle,
})
mapped := host.RuntimeSessionForkResult{
ProviderSessionID: strings.TrimSpace(result.ProviderSessionID),
TargetProviderTurnBindings: make(
[]host.SessionForkProviderTurnBinding,
0,
len(result.TargetProviderTurnBindings),
),
StateBindingMode: host.SessionForkStateBindingMode(strings.TrimSpace(result.StateBindingMode)),
StateBindingReceipt: strings.TrimSpace(result.StateBindingReceipt),
DeliveryDisposition: host.SessionForkDeliveryDisposition(
result.DeliveryDisposition,
),
}
for _, binding := range result.TargetProviderTurnBindings {
mapped.TargetProviderTurnBindings = append(
mapped.TargetProviderTurnBindings,
host.SessionForkProviderTurnBinding{
ProviderTurnID: strings.TrimSpace(binding.ProviderTurnID),
ProviderTurnBindingJSON: append(
[]byte(nil),
binding.ProviderTurnBindingJSON...,
),
},
)
}
if mapped.StateBindingMode == "" {
mapped.StateBindingMode = host.SessionForkStateBindingHostCopy
}
if err != nil {
if errors.Is(err, agentruntime.ErrSessionForkUnsupported) {
return mapped, host.ErrSessionForkUnsupported
}
return mapped, mapRuntimeError(err)
}
return mapped, nil
}
func (a *RuntimeController) CanForkProviderTurn(
ctx context.Context,
input host.RuntimeProviderTurnForkabilityInput,
) (bool, error) {
if err := a.requireBackend(); err != nil {
return false, err
}
backend, ok := a.Backend.(sessionForkRuntimeBackend)
if !ok {
return false, nil
}
return backend.CanForkProviderTurn(
ctx,
agentruntime.ProviderTurnForkabilityInput{
Source: runtimeSession(input.Source),
CanonicalTurnID: strings.TrimSpace(input.CanonicalTurnID),
ProviderTurnID: strings.TrimSpace(input.ProviderTurnID),
ProviderTurnBindingJSON: append([]byte(nil), input.ProviderTurnBindingJSON...),
},
)
}
func firstNonEmptyString(values ...string) string {
for _, value := range values {
if value = strings.TrimSpace(value); value != "" {
return value
}
}
return ""
}
func (a *RuntimeController) GoalControl(ctx context.Context, input host.RuntimeGoalControlInput) (host.RuntimeGoalControlResult, error) {
if err := a.requireBackend(); err != nil {
return host.RuntimeGoalControlResult{}, err
}
result, err := a.Backend.GoalControl(ctx, agentruntime.GoalControlInput{
RoomID: input.WorkspaceID, AgentSessionID: input.AgentSessionID,
Action: agentruntime.GoalControlAction(input.Action), Objective: input.Objective,
OperationID: input.OperationID, GoalRevision: input.GoalRevision, RepairEpoch: input.RepairEpoch,
SubmissionMetadata: cloneMap(input.SubmissionMetadata), RequireLive: input.RequireLive,
})
return host.RuntimeGoalControlResult{
AgentSessionID: result.AgentSessionID, Goal: cloneMap(result.Goal), Evidence: cloneMap(result.Evidence),
ProviderPhase: result.ProviderPhase, ExecutionPending: result.ExecutionPending,
}, mapRuntimeError(err)
}
func (a *RuntimeController) ReconcileGoal(ctx context.Context, input host.RuntimeGoalControlInput) (host.RuntimeGoalReconcileResult, error) {
if err := a.requireBackend(); err != nil {
return host.RuntimeGoalReconcileResult{}, err
}
result, err := a.Backend.ReconcileGoal(ctx, agentruntime.GoalReconcileInput{
RoomID: input.WorkspaceID, AgentSessionID: input.AgentSessionID, RequireLive: input.RequireLive,
})
return host.RuntimeGoalReconcileResult{
AgentSessionID: result.AgentSessionID, Goal: cloneMap(result.Goal), Evidence: cloneMap(result.Evidence),
}, mapRuntimeError(err)
}
func (a *RuntimeController) GoalRecoveryPolicy(ctx context.Context, input host.RuntimeGoalControlInput) (host.RuntimeGoalRecoveryPolicy, error) {
if err := a.requireBackend(); err != nil {
return host.RuntimeGoalRecoveryPolicy{}, err
}
capabilities, err := a.Backend.GoalCapabilities(ctx, agentruntime.GoalReconcileInput{RoomID: input.WorkspaceID, AgentSessionID: input.AgentSessionID})
return host.RuntimeGoalRecoveryPolicy{
QuerySupported: capabilities.QuerySupported, ReplaySetAfterRestart: capabilities.ReplaySetAfterRestart,
}, mapRuntimeError(err)
}
func (a *RuntimeController) FenceGoalGeneration(ctx context.Context, input host.RuntimeGoalGenerationFenceInput) error {
if err := a.requireBackend(); err != nil {
return err
}
fencer, ok := a.Backend.(interface {
FenceGoalGeneration(context.Context, agentruntime.GoalGenerationFenceRequest) error
})
if !ok {
return host.ErrGoalGenerationFenceUnavailable
}
return mapRuntimeError(fencer.FenceGoalGeneration(ctx, agentruntime.GoalGenerationFenceRequest{
RoomID: input.WorkspaceID, AgentSessionID: input.AgentSessionID,
OperationID: input.TargetOperationID, Revision: input.TargetRevision,
RepairEpoch: input.TargetRepairEpoch, Reason: input.Reason, RequireLive: input.RequireLive,
}))
}
func (a *RuntimeController) requireBackend() error {
if a == nil || a.Backend == nil {
return errors.New("agent runtime controller is unavailable")
}
return nil
}
func mapRuntimeError(err error) error {
if err == nil {
return nil
}
if errors.Is(err, agentruntime.ErrSessionDisconnected) {
return errors.Join(host.ErrRuntimeSessionDisconnected, err)
}
if errors.Is(err, agentruntime.ErrSessionNotFound) {
return errors.Join(host.ErrSessionNotFound, err)
}
if errors.Is(err, agentruntime.ErrActiveTurnTargetRequired) {
return errors.Join(host.ErrActiveTurnTargetRequired, err)
}
if errors.Is(err, agentruntime.ErrActiveTurnTargetMismatch) {
return errors.Join(host.ErrActiveTurnTargetMismatch, err)
}
if errors.Is(err, agentruntime.ErrSessionActiveTurn) {
return errors.Join(host.ErrRuntimeSessionActive, err)
}
if errors.Is(err, agentruntime.ErrEffectiveHistoryUnsupported) {
return host.ErrRuntimeHistoryUnsupported
}
if errors.Is(err, agentruntime.ErrSideConversationUnsupported) {
return errors.Join(host.ErrSideConversationUnsupported, err)
}
if errors.Is(err, agentruntime.ErrSideConversationConflict) {
return errors.Join(host.ErrSideConversationConflict, err)
}
if errors.Is(err, agentruntime.ErrSideConversationExpired) {
return errors.Join(host.ErrSideConversationExpired, err)
}
if errors.Is(err, agentruntime.ErrInteractiveRequestNotLive) {
return errors.Join(host.ErrInteractiveRequestNotLive, err)
}
if errors.Is(err, agentruntime.ErrInteractiveAlreadyAnswered) {
return errors.Join(host.ErrInteractiveAlreadyAnswered, err)
}
if errors.Is(err, agentruntime.ErrInteractiveResponseInvalid) {
return errors.Join(host.ErrInteractiveResponseInvalid, err)
}
if errors.Is(err, agentruntime.ErrProviderStartTimeout) {
var appErr *agentruntime.AppError
if errors.As(err, &appErr) && appErr != nil {
return host.NewProviderStartTimeoutError(appErr.Message, appErr.DebugMessage, err)
}
return host.NewProviderStartTimeoutError("", "", err)
}
var appErr *agentruntime.AppError
if errors.As(err, &appErr) && appErr != nil {
if errors.Is(appErr, context.Canceled) || errors.Is(appErr, context.DeadlineExceeded) {
return err
}
return host.NewProviderError(appErr.Code, appErr.Message, appErr.DebugMessage, appErr)
}
return err
}
func (a *RuntimeController) fromSession(session agentruntime.Session) host.ProviderRuntimeSession {
var settings *host.ComposerSettings
if session.Settings != nil {
value := hostSettings(*session.Settings)
settings = &value
}
return host.ProviderRuntimeSession{
ID: session.AgentSessionID, WorkspaceID: session.RoomID, UserID: a.currentUserID(),
Scope: host.RuntimeSessionScope(session.Scope),
SourceAgentSessionID: session.SourceAgentSessionID, SideRequestID: session.SideRequestID,
AgentTargetID: session.AgentTargetID, Provider: session.Provider, ProviderSessionID: session.ProviderSessionID,
Resumable: session.Resumable,
Cwd: session.CWD, Env: append([]string(nil), session.Env...), MCPServers: hostMCPServerBindings(session.MCPServers), Settings: settings,
ProviderTargetRef: cloneMap(session.ProviderTargetRef),
RuntimeContext: cloneMap(session.RuntimeContext), Status: session.Status,
TurnLifecycle: hostTurnLifecyclePointer(session.TurnLifecycle), SubmitAvailability: hostSubmitAvailability(session.SubmitAvailability),
Visible: session.Visible, Title: session.Title, InitialTitleEstablished: session.InitialTitleEstablished,
LastError: session.LastError, CreatedAtUnixMS: session.CreatedAtUnixMS, UpdatedAtUnixMS: session.UpdatedAtUnixMS,
}
}
func runtimeSession(session host.ProviderRuntimeSession) agentruntime.Session {
var settings *agentruntime.SessionSettings
if session.Settings != nil {
settings = runtimeSettings(*session.Settings)
}
return agentruntime.Session{
RoomID: session.WorkspaceID, AgentSessionID: session.ID,
Scope: agentruntime.RuntimeSessionScope(session.Scope),
SourceAgentSessionID: session.SourceAgentSessionID, SideRequestID: session.SideRequestID,
AgentTargetID: session.AgentTargetID, Provider: session.Provider,
ProviderSessionID: session.ProviderSessionID, Resumable: session.Resumable,
CWD: session.Cwd, Env: append([]string(nil), session.Env...), MCPServers: runtimeMCPServerBindings(session.MCPServers),
Status: session.Status, TurnLifecycle: runtimeTurnLifecyclePointer(session.TurnLifecycle),
SubmitAvailability: runtimeSubmitAvailability(session.SubmitAvailability),
Title: session.Title, LastError: session.LastError, Visible: session.Visible,
RuntimeContext: cloneMap(session.RuntimeContext), ProviderTargetRef: cloneMap(session.ProviderTargetRef),
Settings: settings,
CreatedAtUnixMS: session.CreatedAtUnixMS, UpdatedAtUnixMS: session.UpdatedAtUnixMS,
InitialTitleEstablished: session.InitialTitleEstablished,
}
}
// sessionWithState preserves the daemon runtime's provider-enriched live
// observation. The base Session owns process identity and lifecycle fields;
// State overlays provider-computed settings and runtime context such as model
// catalogs, usage, rate limits, account details, and commands.
func (a *RuntimeController) sessionWithState(session agentruntime.Session) host.ProviderRuntimeSession {
result := a.fromSession(session)
if a == nil || a.Backend == nil {
return result
}
state, err := a.Backend.State(session.RoomID, session.AgentSessionID)
if err != nil {
return result
}
if state.ProviderSessionID != "" {
result.ProviderSessionID = state.ProviderSessionID
}
result.Resumable = result.Resumable || state.Resumable
if state.Status != "" {
result.Status = state.Status
}
if state.TurnLifecycle != nil {
result.TurnLifecycle = hostTurnLifecyclePointer(state.TurnLifecycle)
}
if state.SubmitAvailability != nil {
result.SubmitAvailability = hostSubmitAvailability(state.SubmitAvailability)
}
if state.Settings != nil {
settings := hostSettings(*state.Settings)
result.Settings = &settings
}
result.Capabilities = canonical.CloneCapabilitySnapshot(state.Capabilities)
result.RuntimeContext = cloneMap(state.RuntimeContext)
if state.UpdatedAtUnixMS > 0 {
result.UpdatedAtUnixMS = state.UpdatedAtUnixMS
}
return result
}
func (a *RuntimeController) currentUserID() string {
if a != nil && a.CurrentUserID != nil {
return strings.TrimSpace(a.CurrentUserID())
}
return ""
}
func runtimeResumeInput(input host.RuntimeResumeInput) agentruntime.ResumeInput {
return agentruntime.ResumeInput{
RoomID: input.WorkspaceID, AgentSessionID: input.AgentSessionID, AgentTargetID: input.AgentTargetID,
Provider: input.Provider, ProviderSessionID: input.ProviderSessionID, CWD: input.Cwd,
Resumable: input.Resumable,
Env: append([]string(nil), input.Env...), MCPServers: runtimeMCPServerBindings(input.MCPServers),
Title: input.Title, Status: input.Status, Visible: input.Visible,
RuntimeContext: cloneMap(input.RuntimeContext),
ProviderLaunchRuntimeContext: cloneMap(input.ProviderLaunchRuntimeContext),
ProviderTargetRef: cloneMap(input.ProviderTargetRef),
PermissionModeID: input.Settings.PermissionModeID, Settings: runtimeSettings(input.Settings),
CreatedAtUnixMS: input.CreatedAtUnixMS, UpdatedAtUnixMS: input.UpdatedAtUnixMS,
GoalGenerationFences: runtimeGoalGenerationFences(input.GoalGenerationFences),
RecreateIfMissing: input.RecreateIfMissing,
}
}
func runtimeExecInput(input host.RuntimeExecInput) agentruntime.ExecInput {
return agentruntime.ExecInput{
RoomID: input.WorkspaceID, AgentSessionID: input.AgentSessionID,
TurnID: input.TurnID, ClientSubmitID: input.ClientSubmitID,
CanonicalSubmitOccurredAtUnixMS: input.CanonicalSubmitOccurredAtUnixMS,
CapabilityRefs: runtimeCapabilityReferences(input.CapabilityRefs),
TuttiModeSnapshot: runtimeTuttiModeSnapshot(input.TuttiModeSnapshot),
Content: runtimePromptContent(input.Content),
DisplayPrompt: input.DisplayPrompt, InitialTitle: input.InitialTitle, InitialTitleBase: input.InitialTitleBase,
Metadata: cloneMap(input.Metadata), Guidance: input.Guidance,
HistoryReplacement: input.HistoryReplacement,
RequireProviderAcceptance: input.RequireProviderAcceptance,
ConnectorRoutingUpdate: cloneStringPointer(input.ConnectorRoutingUpdate),
}
}
func cloneStringPointer(value *string) *string {
if value == nil {
return nil
}
cloned := *value
return &cloned
}
func runtimeHistoryInput(input host.RuntimeHistoryInput) agentruntime.EffectiveHistoryInput {
return agentruntime.EffectiveHistoryInput{
RoomID: input.WorkspaceID, AgentSessionID: input.AgentSessionID,
Provider: input.Provider,
}
}
func hostHistorySnapshot(input agentruntime.EffectiveHistorySnapshot) host.RuntimeHistorySnapshot {
turns := make([]host.RuntimeHistoryTurn, 0, len(input.Turns))
for _, turn := range input.Turns {
turns = append(turns, host.RuntimeHistoryTurn{
ID: turn.ID, Status: turn.Status,
ClientUserMessageID: turn.ClientUserMessageID,
})
}
return host.RuntimeHistorySnapshot{
ProviderSessionID: input.ProviderSessionID,
Turns: turns,
}
}
func runtimeSubmitProvenanceInput(input host.RuntimeSubmitProvenanceInput) agentruntime.SubmitProvenanceInput {
return agentruntime.SubmitProvenanceInput{
RoomID: input.WorkspaceID, AgentSessionID: input.AgentSessionID,
TurnID: input.TurnID, ClientSubmitID: input.ClientSubmitID,
CanonicalSubmitOccurredAtUnixMS: input.CanonicalSubmitOccurredAtUnixMS,
Content: runtimePromptContent(input.Content), DisplayPrompt: input.DisplayPrompt,
Guidance: input.Guidance,
}
}
func runtimePromptContent(input []host.PromptContentBlock) []agentruntime.PromptContentBlock {
content := make([]agentruntime.PromptContentBlock, 0, len(input))
for _, block := range input {
content = append(content, agentruntime.PromptContentBlock{
Type: block.Type, Text: block.Text, MimeType: block.MimeType, Data: block.Data, URL: block.URL,
AttachmentID: block.AttachmentID, Name: block.Name, Path: block.Path, ConnectorKey: block.ConnectorKey,
})
}
return content
}
func runtimeCapabilityReferences(input []host.CapabilityReference) []agentruntime.CapabilityReference {
references := make([]agentruntime.CapabilityReference, 0, len(input))
for _, reference := range input {
references = append(references, agentruntime.CapabilityReference{
Capability: reference.Capability,
Source: reference.Source,
})
}
return references
}