Files
tutti/packages/agent/daemon/runtime/controller_settings_test.go
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

335 lines
10 KiB
Go

package agentruntime
import (
"context"
"errors"
"testing"
)
func TestControllerUpdateSettingsMergesModelPatchWithoutRequiringPermissionMode(t *testing.T) {
t.Parallel()
adapter := &statefulInteractiveAdapter{}
controller := NewController([]Adapter{adapter}, nil)
started, err := controller.Start(context.Background(), StartInput{
RoomID: "room-1",
AgentSessionID: "agent-session-1",
Provider: ProviderCodex,
CWD: "/workspace",
Title: "Codex",
Settings: &SessionSettings{
CodexSaverMode: true,
Model: "gpt-5.2-codex",
ReasoningEffort: "high",
Speed: "standard",
PlanMode: true,
PermissionModeID: "full-access",
},
})
if err != nil {
t.Fatalf("Start: %v", err)
}
if started.Session.Settings == nil {
t.Fatal("session settings = nil, want round-tripped settings")
}
updated, err := controller.UpdateSettings(context.Background(), UpdateSettingsInput{
RoomID: "room-1",
AgentSessionID: "agent-session-1",
Settings: SessionSettingsPatch{
Model: stringPtr("gpt-5.4"),
Speed: stringPtr("fast"),
},
})
if err != nil {
t.Fatalf("UpdateSettings: %v", err)
}
if updated.Settings.PermissionModeID != "full-access" {
t.Fatalf("updated settings permission mode = %q, want %q", updated.Settings.PermissionModeID, "full-access")
}
if updated.Settings.Model != "gpt-5.4" {
t.Fatalf("updated settings model = %q, want %q", updated.Settings.Model, "gpt-5.4")
}
if updated.Settings.Speed != "fast" {
t.Fatalf("updated settings speed = %q, want fast", updated.Settings.Speed)
}
if updated.Settings.ReasoningEffort != "high" || !updated.Settings.PlanMode || !updated.Settings.CodexSaverMode {
t.Fatalf("updated settings = %#v, want non-updated fields preserved", updated.Settings)
}
}
func TestControllerUpdateSettingsAppliesLiveAdapterSettingsPatch(t *testing.T) {
t.Parallel()
adapter := &statefulInteractiveAdapter{
snapshot: SessionStateSnapshot{
Settings: &SessionSettings{
Model: "gpt-5.2-codex",
ReasoningEffort: "high",
PlanMode: false,
PermissionModeID: "full-access",
},
},
}
controller := NewController([]Adapter{adapter}, nil)
started, err := controller.Start(context.Background(), StartInput{
RoomID: "room-1",
AgentSessionID: "agent-session-1",
Provider: ProviderCodex,
CWD: "/workspace",
Title: "Codex",
Settings: &SessionSettings{
Model: "gpt-5.2-codex",
ReasoningEffort: "high",
PlanMode: false,
PermissionModeID: "full-access",
},
})
if err != nil {
t.Fatalf("Start: %v", err)
}
if _, err := controller.UpdateSettings(context.Background(), UpdateSettingsInput{
RoomID: "room-1",
AgentSessionID: started.Session.AgentSessionID,
Settings: SessionSettingsPatch{
Model: stringPtr("gpt-5.4"),
},
}); err != nil {
t.Fatalf("UpdateSettings: %v", err)
}
if len(adapter.appliedSettings) != 1 {
t.Fatalf("applied settings = %#v, want one live patch", adapter.appliedSettings)
}
if adapter.appliedSettings[0].Model == nil || *adapter.appliedSettings[0].Model != "gpt-5.4" {
t.Fatalf("applied settings model = %#v, want gpt-5.4", adapter.appliedSettings[0].Model)
}
state, err := controller.State("room-1", started.Session.AgentSessionID)
if err != nil {
t.Fatalf("State: %v", err)
}
if state.Settings == nil || state.Settings.Model != "gpt-5.4" {
t.Fatalf("state settings = %#v, want live model override updated", state.Settings)
}
}
func TestControllerUpdateSettingsRejectsSettingsThatRequireNewSession(t *testing.T) {
t.Parallel()
adapter := &statefulInteractiveAdapter{requiresNewSession: true}
controller := NewController([]Adapter{adapter}, nil)
started, err := controller.Start(context.Background(), StartInput{
RoomID: "room-1",
AgentSessionID: "agent-session-1",
Provider: ProviderCodex,
CWD: "/workspace",
Title: "Codex",
Settings: &SessionSettings{
Model: "gpt-5.2-codex",
ReasoningEffort: "high",
PermissionModeID: "full-access",
},
})
if err != nil {
t.Fatalf("Start: %v", err)
}
_, err = controller.UpdateSettings(context.Background(), UpdateSettingsInput{
RoomID: "room-1",
AgentSessionID: started.Session.AgentSessionID,
Settings: SessionSettingsPatch{
Model: stringPtr("gpt-5.3-codex-spark"),
},
})
if !errors.Is(err, ErrSessionSettingsRequireNewSession) {
t.Fatalf("UpdateSettings error = %v, want ErrSessionSettingsRequireNewSession", err)
}
if len(adapter.appliedSettings) != 0 {
t.Fatalf("applied live settings = %#v, want none for new-session-only setting", adapter.appliedSettings)
}
session, ok := controller.Session("room-1", started.Session.AgentSessionID)
if !ok {
t.Fatal("Session returned ok=false after rejected update")
}
if session.Settings == nil || session.Settings.Model != "gpt-5.2-codex" {
t.Fatalf("session settings after rejected update = %#v, want original model preserved", session.Settings)
}
}
func TestControllerUpdateSettingsDoesNotPersistFailedLivePatch(t *testing.T) {
t.Parallel()
adapter := &statefulInteractiveAdapter{
applySettingsErr: errors.New("live settings unavailable"),
}
controller := NewController([]Adapter{adapter}, nil)
started, err := controller.Start(context.Background(), StartInput{
RoomID: "room-1",
AgentSessionID: "agent-session-1",
Provider: ProviderCodex,
CWD: "/workspace",
Title: "Codex",
Settings: &SessionSettings{
Model: "gpt-5.2-codex",
ReasoningEffort: "high",
PermissionModeID: "full-access",
},
})
if err != nil {
t.Fatalf("Start: %v", err)
}
_, err = controller.UpdateSettings(context.Background(), UpdateSettingsInput{
RoomID: "room-1",
AgentSessionID: started.Session.AgentSessionID,
Settings: SessionSettingsPatch{
Model: stringPtr("gpt-5.4"),
},
})
if err == nil {
t.Fatal("UpdateSettings: expected live settings failure")
}
session, ok := controller.Session("room-1", started.Session.AgentSessionID)
if !ok {
t.Fatal("Session returned ok=false after failed update")
}
if session.Settings == nil {
t.Fatal("session settings = nil after failed update")
}
if session.Settings.Model != "gpt-5.2-codex" {
t.Fatalf("session settings model = %q, want original value after failed update", session.Settings.Model)
}
state, err := controller.State("room-1", started.Session.AgentSessionID)
if err != nil {
t.Fatalf("State: %v", err)
}
if state.Settings == nil {
t.Fatal("state settings = nil after failed update")
}
if state.Settings.Model != "gpt-5.2-codex" {
t.Fatalf("state settings model = %q, want original value after failed update", state.Settings.Model)
}
}
func TestControllerUpdateSettingsDoesNotAdvanceSessionUpdatedAt(t *testing.T) {
t.Parallel()
adapter := &statefulInteractiveAdapter{}
controller := NewController([]Adapter{adapter}, nil)
started, err := controller.Start(context.Background(), StartInput{
RoomID: "room-1",
AgentSessionID: "agent-session-1",
Provider: ProviderCodex,
CWD: "/workspace",
Title: "Codex",
Settings: &SessionSettings{
Model: "gpt-5.2-codex",
ReasoningEffort: "high",
PermissionModeID: "full-access",
},
})
if err != nil {
t.Fatalf("Start: %v", err)
}
controller.mu.Lock()
session := controller.sessions[sessionKey("room-1", started.Session.AgentSessionID)]
session.UpdatedAtUnixMS = 123
controller.sessions[sessionKey("room-1", started.Session.AgentSessionID)] = session
controller.mu.Unlock()
if _, err := controller.UpdateSettings(context.Background(), UpdateSettingsInput{
RoomID: "room-1",
AgentSessionID: started.Session.AgentSessionID,
Settings: SessionSettingsPatch{
Model: stringPtr("gpt-5.4"),
},
}); err != nil {
t.Fatalf("UpdateSettings: %v", err)
}
updated, ok := controller.Session("room-1", started.Session.AgentSessionID)
if !ok {
t.Fatal("Session returned ok=false after update")
}
if updated.UpdatedAtUnixMS != 123 {
t.Fatalf("UpdatedAtUnixMS = %d, want preserved value 123", updated.UpdatedAtUnixMS)
}
state, err := controller.State("room-1", started.Session.AgentSessionID)
if err != nil {
t.Fatalf("State: %v", err)
}
if state.UpdatedAtUnixMS != 123 {
t.Fatalf("state UpdatedAtUnixMS = %d, want preserved value 123", state.UpdatedAtUnixMS)
}
}
func TestControllerStateAppliesAdapterSettingsOverride(t *testing.T) {
t.Parallel()
adapter := &statefulInteractiveAdapter{
snapshot: SessionStateSnapshot{
PermissionModeID: "full-access",
Settings: &SessionSettings{
Model: "opus",
ReasoningEffort: "low",
PlanMode: true,
PermissionModeID: "full-access",
},
},
}
controller := NewController([]Adapter{adapter}, nil)
_, err := controller.Start(context.Background(), StartInput{
RoomID: "room-1",
AgentSessionID: "agent-session-1",
Provider: ProviderCodex,
CWD: "/workspace",
Title: "Codex",
Settings: &SessionSettings{
Model: "gpt-5.2-codex",
ReasoningEffort: "high",
PermissionModeID: "read-only",
},
})
if err != nil {
t.Fatalf("Start: %v", err)
}
state, err := controller.State("room-1", "agent-session-1")
if err != nil {
t.Fatalf("State: %v", err)
}
if state.PermissionModeID != "full-access" {
t.Fatalf("state permission mode = %q, want %q", state.PermissionModeID, "full-access")
}
if state.Settings == nil {
t.Fatal("state settings = nil, want adapter override")
}
if state.Settings.Model != "opus" || state.Settings.ReasoningEffort != "low" || !state.Settings.PlanMode {
t.Fatalf("state settings = %#v, want adapter override", state.Settings)
}
if state.Settings.PermissionModeID != "full-access" {
t.Fatalf("state settings permission mode = %q, want %q", state.Settings.PermissionModeID, "full-access")
}
if got := asString(state.RuntimeContext["model"]); got != "opus" {
t.Fatalf("runtime context model = %q, want opus", got)
}
if got := asString(state.RuntimeContext["reasoningEffort"]); got != "low" {
t.Fatalf("runtime context reasoningEffort = %q, want low", got)
}
if got, _ := state.RuntimeContext["planMode"].(bool); !got {
t.Fatalf("runtime context planMode = %#v, want true", state.RuntimeContext["planMode"])
}
}