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

268 lines
9.9 KiB
Go

package agentruntime
import (
"context"
"strings"
activityshared "github.com/tutti-os/tutti/packages/agent/daemon/activity/events"
"github.com/tutti-os/tutti/packages/agent/store-sqlite/canonical"
)
func (c *Controller) UpdateSettings(ctx context.Context, input UpdateSettingsInput) (UpdateSettingsResult, error) {
session, adapter, err := c.sessionAndAdapter(input.RoomID, input.AgentSessionID)
if err != nil {
return UpdateSettingsResult{}, err
}
if validator, ok := adapter.(SessionSettingsValidationAdapter); ok {
if err := validator.ValidateSessionSettings(session, input.Settings); err != nil {
return UpdateSettingsResult{}, err
}
}
nextSession := session
settings := normalizeSessionSettings(nextSession.Settings, nextSession.Provider, nextSession.PermissionModeID)
if input.Settings.Model != nil {
settings.Model = strings.TrimSpace(*input.Settings.Model)
}
if input.Settings.ReasoningEffort != nil {
settings.ReasoningEffort = strings.TrimSpace(*input.Settings.ReasoningEffort)
}
if input.Settings.Speed != nil {
settings.Speed = strings.TrimSpace(*input.Settings.Speed)
}
if input.Settings.PlanMode != nil {
settings.PlanMode = *input.Settings.PlanMode
}
if input.Settings.BrowserUse != nil {
value := *input.Settings.BrowserUse
settings.BrowserUse = &value
}
if input.Settings.ComputerUse != nil {
value := *input.Settings.ComputerUse
settings.ComputerUse = &value
}
permissionChanged := false
if input.Settings.PermissionModeID != nil {
normalized := normalizePermissionModeIDWithFallback(
nextSession.Provider,
strings.TrimSpace(*input.Settings.PermissionModeID),
nextSession.PermissionModeID,
)
permissionChanged = normalized != nextSession.PermissionModeID
settings.PermissionModeID = normalized
nextSession.PermissionModeID = normalized
}
nextSession.Settings = cloneSessionSettings(settings)
if newSessionAdapter, ok := adapter.(NewSessionSettingsAdapter); ok && newSessionAdapter.RequiresNewSessionForSettings(session, input.Settings) {
return UpdateSettingsResult{}, ErrSessionSettingsRequireNewSession
}
if permissionChanged {
if permissionAdapter, ok := adapter.(PermissionModeAdapter); ok {
if err := permissionAdapter.ApplyPermissionMode(ctx, nextSession); err != nil {
return UpdateSettingsResult{}, err
}
}
}
if liveSettingsAdapter, ok := adapter.(LiveSettingsAdapter); ok {
if err := liveSettingsAdapter.ApplySessionSettings(ctx, nextSession, input.Settings); err != nil {
return UpdateSettingsResult{}, err
}
}
c.store(nextSession)
return UpdateSettingsResult{
AgentSessionID: nextSession.AgentSessionID,
Settings: settings,
}, nil
}
// UpdateRetainedSettings changes only the Controller Session snapshot. It is
// used while the provider connection is absent so the next just-in-time Resume
// receives current canonical settings without reconnecting early.
func (c *Controller) UpdateRetainedSettings(_ context.Context, input UpdateSettingsInput) (UpdateSettingsResult, error) {
session, ok := c.get(strings.TrimSpace(input.RoomID), strings.TrimSpace(input.AgentSessionID))
if !ok {
return UpdateSettingsResult{}, ErrSessionNotFound
}
settings := normalizeSessionSettings(session.Settings, session.Provider, session.PermissionModeID)
if input.Settings.Model != nil {
settings.Model = strings.TrimSpace(*input.Settings.Model)
}
if input.Settings.ReasoningEffort != nil {
settings.ReasoningEffort = strings.TrimSpace(*input.Settings.ReasoningEffort)
}
if input.Settings.Speed != nil {
settings.Speed = strings.TrimSpace(*input.Settings.Speed)
}
if input.Settings.PlanMode != nil {
settings.PlanMode = *input.Settings.PlanMode
}
if input.Settings.BrowserUse != nil {
value := *input.Settings.BrowserUse
settings.BrowserUse = &value
}
if input.Settings.ComputerUse != nil {
value := *input.Settings.ComputerUse
settings.ComputerUse = &value
}
if input.Settings.PermissionModeID != nil {
settings.PermissionModeID = normalizePermissionModeIDWithFallback(
session.Provider, strings.TrimSpace(*input.Settings.PermissionModeID), session.PermissionModeID,
)
session.PermissionModeID = settings.PermissionModeID
}
session.Settings = cloneSessionSettings(settings)
c.store(session)
return UpdateSettingsResult{AgentSessionID: session.AgentSessionID, Settings: settings}, nil
}
func shouldAdvanceSessionUpdatedAtFromEvents(events []activityshared.Event) bool {
for _, event := range events {
switch event.Type {
case activityshared.EventTurnStarted,
activityshared.EventTurnCompleted,
activityshared.EventTurnFailed:
return true
case activityshared.EventTurnUpdated:
switch strings.TrimSpace(event.Payload.TurnPhase) {
case string(activityshared.TurnPhaseWaitingApproval),
string(activityshared.TurnPhaseWaitingInput),
string(activityshared.SessionStatusWaiting):
return true
}
}
}
return false
}
func (c *Controller) State(roomID, agentSessionID string) (SessionStateSnapshot, error) {
session, adapter, err := c.sessionAndAdapter(roomID, agentSessionID)
if err != nil {
return SessionStateSnapshot{}, err
}
snapshot := SessionStateSnapshot{
RoomID: session.RoomID,
AgentSessionID: session.AgentSessionID,
AgentTargetID: session.AgentTargetID,
Provider: session.Provider,
ProviderSessionID: session.ProviderSessionID,
Resumable: session.Resumable,
Status: session.Status,
TurnLifecycle: cloneRuntimeTurnLifecycle(session.TurnLifecycle),
SubmitAvailability: cloneRuntimeSubmitAvailability(session.SubmitAvailability),
PermissionModeID: session.PermissionModeID,
Settings: normalizeOptionalSessionSettings(session.Settings, session.Provider, session.PermissionModeID),
RuntimeContext: sessionRuntimeContextSnapshot(session),
UpdatedAtUnixMS: session.UpdatedAtUnixMS,
}
if snapshot.Settings != nil {
snapshot.RuntimeContext["model"] = snapshot.Settings.Model
snapshot.RuntimeContext["reasoningEffort"] = snapshot.Settings.ReasoningEffort
snapshot.RuntimeContext["speed"] = snapshot.Settings.Speed
snapshot.RuntimeContext["planMode"] = snapshot.Settings.PlanMode
}
if stateAdapter, ok := adapter.(StateAdapter); ok {
override := stateAdapter.SessionState(session)
if override.RoomID != "" {
snapshot.RoomID = override.RoomID
}
if override.AgentSessionID != "" {
snapshot.AgentSessionID = override.AgentSessionID
}
if override.AgentTargetID != "" {
snapshot.AgentTargetID = override.AgentTargetID
}
if override.Provider != "" {
snapshot.Provider = override.Provider
}
if override.ProviderSessionID != "" {
snapshot.ProviderSessionID = override.ProviderSessionID
}
if override.Status != "" {
snapshot.Status = override.Status
}
if override.TurnLifecycle != nil {
snapshot.TurnLifecycle = cloneRuntimeTurnLifecycle(override.TurnLifecycle)
}
if override.SubmitAvailability != nil {
snapshot.SubmitAvailability = cloneRuntimeSubmitAvailability(override.SubmitAvailability)
}
if override.PermissionModeID != "" {
snapshot.PermissionModeID = normalizePermissionModeIDWithFallback(
session.Provider,
override.PermissionModeID,
snapshot.PermissionModeID,
)
}
if override.Settings != nil {
snapshot.Settings = normalizeOptionalSessionSettings(override.Settings, session.Provider, snapshot.PermissionModeID)
}
if override.Capabilities != nil {
snapshot.Capabilities = canonical.CloneCapabilitySnapshot(override.Capabilities)
}
if override.AuthState != "" {
snapshot.AuthState = override.AuthState
}
if override.RuntimeContext != nil {
snapshot.RuntimeContext = mergeRuntimeContextPatch(snapshot.RuntimeContext, override.RuntimeContext)
}
if override.PendingInteractive != nil {
snapshot.PendingInteractive = override.PendingInteractive
}
if override.UpdatedAtUnixMS > 0 {
snapshot.UpdatedAtUnixMS = override.UpdatedAtUnixMS
}
}
if snapshot.RuntimeContext == nil {
snapshot.RuntimeContext = map[string]any{}
}
snapshot.RuntimeContext["permissionModeId"] = snapshot.PermissionModeID
snapshot.RuntimeContext["visible"] = session.Visible
if snapshot.Settings != nil {
snapshot.RuntimeContext["model"] = snapshot.Settings.Model
snapshot.RuntimeContext["reasoningEffort"] = snapshot.Settings.ReasoningEffort
snapshot.RuntimeContext["speed"] = snapshot.Settings.Speed
snapshot.RuntimeContext["planMode"] = snapshot.Settings.PlanMode
}
return snapshot, nil
}
func (c *Controller) sessionStateSnapshot(session Session) SessionStateSnapshot {
snapshot, err := c.State(session.RoomID, session.AgentSessionID)
if err == nil {
return snapshot
}
return SessionStateSnapshot{
RoomID: session.RoomID,
AgentSessionID: session.AgentSessionID,
AgentTargetID: session.AgentTargetID,
Provider: session.Provider,
ProviderSessionID: session.ProviderSessionID,
Resumable: session.Resumable,
Status: session.Status,
TurnLifecycle: cloneRuntimeTurnLifecycle(session.TurnLifecycle),
SubmitAvailability: cloneRuntimeSubmitAvailability(
session.SubmitAvailability,
),
PermissionModeID: session.PermissionModeID,
Settings: normalizeOptionalSessionSettings(session.Settings, session.Provider, session.PermissionModeID),
RuntimeContext: sessionRuntimeContextSnapshot(session),
UpdatedAtUnixMS: session.UpdatedAtUnixMS,
}
}
func sessionRuntimeContextSnapshot(session Session) map[string]any {
runtimeContext := clonePayload(session.RuntimeContext)
if runtimeContext == nil {
runtimeContext = map[string]any{}
}
// Session capabilities have a typed snapshot contract. Strip legacy
// runtime-context copies so partial provider state cannot become an
// accidental second source of truth.
delete(runtimeContext, "capabilities")
delete(runtimeContext, "capabilitiesReported")
runtimeContext["cwd"] = session.CWD
runtimeContext["title"] = session.Title
runtimeContext["permissionModeId"] = session.PermissionModeID
runtimeContext["visible"] = session.Visible
return runtimeContext
}