Files
tutti/services/tuttid/data/workspace/desktop_preferences_migrations.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

833 lines
27 KiB
Go

package workspace
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
preferencesbiz "github.com/tutti-os/tutti/services/tuttid/biz/preferences"
)
func (s *SQLiteStore) applyDesktopPreferencesMigrations(ctx context.Context) error {
migrations := []func(context.Context) error{
s.applyDesktopPreferencesV1,
s.applyDesktopPreferencesAgentDockLayoutV1,
s.applyDesktopPreferencesSleepPreventionModeV1,
s.applyDesktopPreferencesDockPlacementV1,
s.applyDesktopPreferencesDockIconStyleV1,
s.applyDesktopPreferencesDefaultAgentProviderV1,
s.applyDesktopPreferencesAgentComposerDefaultsV1,
s.applyDesktopPreferencesAgentComposerDefaultsByAgentTargetV1,
s.applyDesktopPreferencesAgentGUIConversationRailV1,
s.applyDesktopPreferencesAgentSessionLaunchModesV1,
s.applyDesktopPreferencesBrowserUseConnectionModeV1,
s.applyDesktopPreferencesUpdateSettingsV1,
s.applyDesktopPreferencesFileDefaultOpenersV1,
s.applyDesktopPreferencesAppCatalogChannelV1,
s.applyDesktopPreferencesMinimizeAnimationV1,
s.applyDesktopPreferencesWindowSnappingV1,
s.applyDesktopPreferencesShowAppDeveloperSourcesV1,
s.applyDesktopPreferencesAgentConversationDetailModeV1,
s.applyDesktopPreferencesFeatureFlagsV1,
s.applyDesktopPreferencesTuttiModeDefaultOffV1,
s.applyDesktopPreferencesDeletedAgentRetentionV1,
s.applyDesktopPreferencesAgentCLIUpdateCheckV1,
}
for _, apply := range migrations {
if err := apply(ctx); err != nil {
return err
}
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
_, err = s.writeDB.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS desktop_preferences (
id TEXT PRIMARY KEY,
agent_cli_update_check_enabled INTEGER NOT NULL DEFAULT 1,
agent_dock_layout TEXT NOT NULL DEFAULT 'unified',
dock_icon_style TEXT NOT NULL DEFAULT 'flat',
dock_placement TEXT NOT NULL DEFAULT 'bottom',
deleted_agent_conversation_retention_days INTEGER NOT NULL DEFAULT 30,
default_agent_provider TEXT NOT NULL DEFAULT 'tutti-agent',
agent_conversation_detail_mode TEXT NOT NULL DEFAULT 'coding',
agent_composer_defaults_by_provider_json TEXT NOT NULL DEFAULT '{}',
agent_gui_conversation_rail_collapsed_by_provider_json TEXT NOT NULL DEFAULT '{}',
file_default_openers_by_extension_json TEXT NOT NULL DEFAULT '{"htm":"appBrowser","html":"appBrowser","shtml":"appBrowser","xhtml":"appBrowser"}',
locale TEXT NOT NULL,
minimize_animation TEXT NOT NULL DEFAULT 'scale',
theme_source TEXT NOT NULL,
sleep_prevention_mode TEXT NOT NULL DEFAULT 'never',
browser_use_connection_mode TEXT NOT NULL DEFAULT 'isolated',
app_catalog_channel TEXT NOT NULL DEFAULT 'production',
update_channel TEXT NOT NULL DEFAULT 'stable',
update_policy TEXT NOT NULL DEFAULT 'prompt',
show_app_developer_sources INTEGER NOT NULL DEFAULT 0,
workbench_window_snapping_enabled INTEGER NOT NULL DEFAULT 0,
workbench_window_snapping_shortcut_preset TEXT NOT NULL DEFAULT 'commandArrows',
feature_flags_json TEXT,
workbench_shortcuts_json TEXT,
updated_at_unix_ms INTEGER NOT NULL
);
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesV1, now)
if err != nil {
return fmt.Errorf("migrate workspace database for desktop preferences: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesDeletedAgentRetentionV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesDeletedAgentRetentionV1)
if err != nil {
return err
}
if applied {
return nil
}
hasColumn, err := s.hasColumn(ctx, "desktop_preferences", "deleted_agent_conversation_retention_days")
if err != nil {
return err
}
if !hasColumn {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN deleted_agent_conversation_retention_days INTEGER NOT NULL DEFAULT 30;
`); err != nil {
return fmt.Errorf("migrate desktop deleted agent conversation retention: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesDeletedAgentRetentionV1, unixMs(time.Now().UTC()))
if err != nil {
return fmt.Errorf("record desktop deleted agent conversation retention migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesAgentCLIUpdateCheckV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesAgentCLIUpdateCheckV1)
if err != nil {
return err
}
if applied {
return nil
}
hasColumn, err := s.hasColumn(ctx, "desktop_preferences", "agent_cli_update_check_enabled")
if err != nil {
return err
}
if !hasColumn {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN agent_cli_update_check_enabled INTEGER NOT NULL DEFAULT 1;
`); err != nil {
return fmt.Errorf("migrate desktop agent CLI update check preference: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesAgentCLIUpdateCheckV1, unixMs(time.Now().UTC()))
if err != nil {
return fmt.Errorf("record desktop agent CLI update check preference migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesAgentDockLayoutV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesAgentDockLayoutV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasAgentDockLayout, err := s.hasColumn(ctx, "desktop_preferences", "agent_dock_layout")
if err != nil {
return err
}
if !hasAgentDockLayout {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN agent_dock_layout TEXT NOT NULL DEFAULT 'unified';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop agent dock layout: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesAgentDockLayoutV1, now)
if err != nil {
return fmt.Errorf("record desktop agent dock layout migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesAgentConversationDetailModeV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesAgentConversationDetailModeV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasAgentConversationDetailMode, err := s.hasColumn(ctx, "desktop_preferences", "agent_conversation_detail_mode")
if err != nil {
return err
}
if !hasAgentConversationDetailMode {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN agent_conversation_detail_mode TEXT NOT NULL DEFAULT 'coding';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop agent conversation detail mode: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesAgentConversationDetailModeV1, now)
if err != nil {
return fmt.Errorf("record desktop agent conversation detail mode migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesFeatureFlagsV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesFeatureFlagsV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
columns := []struct {
name string
definition string
}{
{"feature_flags_json", "TEXT"},
{"workbench_shortcuts_json", "TEXT"},
}
for _, column := range columns {
hasColumn, err := s.hasColumn(ctx, "desktop_preferences", column.name)
if err != nil {
return err
}
if hasColumn {
continue
}
if _, err := s.writeDB.ExecContext(ctx, fmt.Sprintf(`
ALTER TABLE desktop_preferences
ADD COLUMN %s %s;`, column.name, column.definition)); err != nil {
return fmt.Errorf("migrate workspace database for desktop feature flags preferences %s: %w", column.name, err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesFeatureFlagsV1, now)
if err != nil {
return fmt.Errorf("record desktop feature flags preferences migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesShowAppDeveloperSourcesV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesShowAppDeveloperSourcesV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasShowAppDeveloperSources, err := s.hasColumn(ctx, "desktop_preferences", "show_app_developer_sources")
if err != nil {
return err
}
if !hasShowAppDeveloperSources {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN show_app_developer_sources INTEGER NOT NULL DEFAULT 0;`); err != nil {
return fmt.Errorf("migrate workspace database for desktop app developer sources: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesShowAppDeveloperSourcesV1, now)
if err != nil {
return fmt.Errorf("record desktop app developer sources migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesFileDefaultOpenersV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesFileDefaultOpenersV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasFileDefaultOpeners, err := s.hasColumn(ctx, "desktop_preferences", "file_default_openers_by_extension_json")
if err != nil {
return err
}
if !hasFileDefaultOpeners {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN file_default_openers_by_extension_json TEXT NOT NULL DEFAULT '{"htm":"appBrowser","html":"appBrowser","shtml":"appBrowser","xhtml":"appBrowser"}';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop file default openers: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesFileDefaultOpenersV1, now)
if err != nil {
return fmt.Errorf("migrate workspace database for desktop file default openers: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesAppCatalogChannelV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesAppCatalogChannelV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasAppCatalogChannel, err := s.hasColumn(ctx, "desktop_preferences", "app_catalog_channel")
if err != nil {
return err
}
if !hasAppCatalogChannel {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN app_catalog_channel TEXT NOT NULL DEFAULT 'production';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop app catalog channel: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesAppCatalogChannelV1, now)
if err != nil {
return fmt.Errorf("record desktop app catalog channel migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesMinimizeAnimationV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesMinimizeAnimationV1)
if err != nil {
return err
}
if applied {
return nil
}
hasMinimizeAnimation, err := s.hasColumn(ctx, "desktop_preferences", "minimize_animation")
if err != nil {
return err
}
now := unixMs(time.Now().UTC())
if !hasMinimizeAnimation {
_, err = s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences ADD COLUMN minimize_animation TEXT NOT NULL DEFAULT 'scale';
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesMinimizeAnimationV1, now)
if err != nil {
return fmt.Errorf("migrate workspace database for desktop preferences minimize animation: %w", err)
}
return nil
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesMinimizeAnimationV1, now)
if err != nil {
return fmt.Errorf("mark desktop preferences minimize animation migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesAgentGUIConversationRailV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesAgentGUIConversationRailV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasAgentGUIConversationRail, err := s.hasColumn(ctx, "desktop_preferences", "agent_gui_conversation_rail_collapsed_by_provider_json")
if err != nil {
return err
}
if !hasAgentGUIConversationRail {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN agent_gui_conversation_rail_collapsed_by_provider_json TEXT NOT NULL DEFAULT '{}';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop agent gui conversation rail: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesAgentGUIConversationRailV1, now)
if err != nil {
return fmt.Errorf("migrate workspace database for desktop agent gui conversation rail: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesAgentSessionLaunchModesV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesAgentSessionLaunchModesV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasColumn, err := s.hasColumn(ctx, "desktop_preferences", "agent_session_launch_modes_by_workspace_json")
if err != nil {
return err
}
if !hasColumn {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN agent_session_launch_modes_by_workspace_json TEXT NOT NULL DEFAULT '{}';`); err != nil {
return fmt.Errorf("migrate workspace database for Agent Session launch modes: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesAgentSessionLaunchModesV1, now)
if err != nil {
return fmt.Errorf("mark Agent Session launch modes migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesWindowSnappingV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesWindowSnappingV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasWindowSnappingEnabled, err := s.hasColumn(ctx, "desktop_preferences", "workbench_window_snapping_enabled")
if err != nil {
return err
}
if !hasWindowSnappingEnabled {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN workbench_window_snapping_enabled INTEGER NOT NULL DEFAULT 0;`); err != nil {
return fmt.Errorf("migrate workspace database for desktop workbench window snapping enabled: %w", err)
}
}
hasWindowSnappingShortcutPreset, err := s.hasColumn(ctx, "desktop_preferences", "workbench_window_snapping_shortcut_preset")
if err != nil {
return err
}
if !hasWindowSnappingShortcutPreset {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN workbench_window_snapping_shortcut_preset TEXT NOT NULL DEFAULT 'commandArrows';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop workbench window snapping shortcut preset: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesWindowSnappingV1, now)
if err != nil {
return fmt.Errorf("record desktop workbench window snapping migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesUpdateSettingsV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesUpdateSettingsV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasUpdateChannel, err := s.hasColumn(ctx, "desktop_preferences", "update_channel")
if err != nil {
return err
}
if !hasUpdateChannel {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN update_channel TEXT NOT NULL DEFAULT 'stable';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop update channel: %w", err)
}
}
hasUpdatePolicy, err := s.hasColumn(ctx, "desktop_preferences", "update_policy")
if err != nil {
return err
}
if !hasUpdatePolicy {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN update_policy TEXT NOT NULL DEFAULT 'prompt';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop update policy: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesUpdateSettingsV1, now)
if err != nil {
return fmt.Errorf("record desktop update settings migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesSleepPreventionModeV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesSleepPreventionModeV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasSleepPreventionMode, err := s.hasColumn(ctx, "desktop_preferences", "sleep_prevention_mode")
if err != nil {
return err
}
hasLegacyPreventSleepEnabled, err := s.hasColumn(ctx, "desktop_preferences", "prevent_sleep_while_agent_running_enabled")
if err != nil {
return err
}
if !hasSleepPreventionMode {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN sleep_prevention_mode TEXT NOT NULL DEFAULT 'never';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop sleep prevention mode: %w", err)
}
if hasLegacyPreventSleepEnabled {
if _, err := s.writeDB.ExecContext(ctx, `
UPDATE desktop_preferences
SET sleep_prevention_mode = 'whileAgentRunning'
WHERE prevent_sleep_while_agent_running_enabled = 1;`); err != nil {
return fmt.Errorf("migrate legacy desktop sleep prevention mode values: %w", err)
}
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesSleepPreventionModeV1, now)
if err != nil {
return fmt.Errorf("migrate workspace database for desktop sleep prevention mode: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesDockPlacementV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesDockPlacementV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasDockPlacement, err := s.hasColumn(ctx, "desktop_preferences", "dock_placement")
if err != nil {
return err
}
if !hasDockPlacement {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN dock_placement TEXT NOT NULL DEFAULT 'bottom';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop dock placement: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesDockPlacementV1, now)
if err != nil {
return fmt.Errorf("record desktop dock placement migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesDockIconStyleV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesDockIconStyleV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasDockIconStyle, err := s.hasColumn(ctx, "desktop_preferences", "dock_icon_style")
if err != nil {
return err
}
if !hasDockIconStyle {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN dock_icon_style TEXT NOT NULL DEFAULT 'flat';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop dock icon style: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesDockIconStyleV1, now)
if err != nil {
return fmt.Errorf("record desktop dock icon style migration: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesDefaultAgentProviderV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesDefaultAgentProviderV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasDefaultAgentProvider, err := s.hasColumn(ctx, "desktop_preferences", "default_agent_provider")
if err != nil {
return err
}
if !hasDefaultAgentProvider {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN default_agent_provider TEXT NOT NULL DEFAULT 'tutti-agent';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop default agent provider: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesDefaultAgentProviderV1, now)
if err != nil {
return fmt.Errorf("migrate workspace database for desktop default agent provider: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesAgentComposerDefaultsV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesAgentComposerDefaultsV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasAgentComposerDefaults, err := s.hasColumn(ctx, "desktop_preferences", "agent_composer_defaults_by_provider_json")
if err != nil {
return err
}
if !hasAgentComposerDefaults {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN agent_composer_defaults_by_provider_json TEXT NOT NULL DEFAULT '{}';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop agent composer defaults: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesAgentComposerDefaultsV1, now)
if err != nil {
return fmt.Errorf("migrate workspace database for desktop agent composer defaults: %w", err)
}
return nil
}
func (s *SQLiteStore) applyDesktopPreferencesAgentComposerDefaultsByAgentTargetV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesAgentComposerDefaultsByAgentTargetV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasColumn, err := s.hasColumn(ctx, "desktop_preferences", "agent_composer_defaults_by_agent_target_json")
if err != nil {
return err
}
if !hasColumn {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN agent_composer_defaults_by_agent_target_json TEXT NOT NULL DEFAULT '{}';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop agent composer defaults by agent target: %w", err)
}
}
if err := s.backfillAgentComposerDefaultsByAgentTarget(ctx); err != nil {
return fmt.Errorf("migrate workspace database for desktop agent composer defaults by agent target: %w", err)
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesAgentComposerDefaultsByAgentTargetV1, now)
if err != nil {
return fmt.Errorf("migrate workspace database for desktop agent composer defaults by agent target: %w", err)
}
return nil
}
// backfillAgentComposerDefaultsByAgentTarget copies the legacy provider-keyed
// composer defaults onto their local agent target ids exactly once. After
// this data migration nothing reads the legacy column anymore, so remembered
// defaults (including explicit clears) are fully owned by the new column.
func (s *SQLiteStore) backfillAgentComposerDefaultsByAgentTarget(ctx context.Context) error {
row := s.writeDB.QueryRowContext(ctx, `
SELECT agent_composer_defaults_by_provider_json, agent_composer_defaults_by_agent_target_json
FROM desktop_preferences
WHERE id = ?
`, desktopPreferencesRowID)
var legacyJSON string
var byAgentTargetJSON string
if err := row.Scan(&legacyJSON, &byAgentTargetJSON); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil
}
return err
}
byAgentTarget, err := decodeAgentComposerDefaultsByProvider(byAgentTargetJSON)
if err != nil {
return err
}
if len(byAgentTarget) > 0 {
return nil
}
legacy, err := decodeAgentComposerDefaultsByProvider(legacyJSON)
if err != nil {
return err
}
migrated := map[string]preferencesbiz.AgentComposerDefaults{}
for provider, defaults := range legacy {
agentTargetID := preferencesbiz.LocalAgentTargetIDForProvider(provider)
if agentTargetID == "" || defaults.IsZero() {
continue
}
migrated[agentTargetID] = defaults
}
if len(migrated) == 0 {
return nil
}
migratedJSON, err := encodeAgentComposerDefaultsByProvider(migrated)
if err != nil {
return err
}
_, err = s.writeDB.ExecContext(ctx, `
UPDATE desktop_preferences
SET agent_composer_defaults_by_agent_target_json = ?
WHERE id = ?
`, migratedJSON, desktopPreferencesRowID)
return err
}
func (s *SQLiteStore) applyDesktopPreferencesBrowserUseConnectionModeV1(ctx context.Context) error {
applied, err := s.hasMigration(ctx, schemaMigrationDesktopPreferencesBrowserUseConnectionModeV1)
if err != nil {
return err
}
if applied {
return nil
}
now := unixMs(time.Now().UTC())
hasBrowserUseConnectionMode, err := s.hasColumn(ctx, "desktop_preferences", "browser_use_connection_mode")
if err != nil {
return err
}
if !hasBrowserUseConnectionMode {
if _, err := s.writeDB.ExecContext(ctx, `
ALTER TABLE desktop_preferences
ADD COLUMN browser_use_connection_mode TEXT NOT NULL DEFAULT 'isolated';`); err != nil {
return fmt.Errorf("migrate workspace database for desktop browser use connection mode: %w", err)
}
}
_, err = s.writeDB.ExecContext(ctx, `
INSERT INTO tuttid_schema_migrations (id, applied_at_unix_ms)
VALUES (?, ?);
`, schemaMigrationDesktopPreferencesBrowserUseConnectionModeV1, now)
if err != nil {
return fmt.Errorf("migrate workspace database for desktop browser use connection mode: %w", err)
}
return nil
}