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
223 lines
8.8 KiB
Go
223 lines
8.8 KiB
Go
package workspace
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
preferencesbiz "github.com/tutti-os/tutti/services/tuttid/biz/preferences"
|
|
)
|
|
|
|
const desktopPreferencesInsertSQL = `
|
|
INSERT INTO desktop_preferences (
|
|
id,
|
|
agent_cli_update_check_enabled,
|
|
default_agent_provider,
|
|
agent_conversation_detail_mode,
|
|
agent_dock_layout,
|
|
dock_icon_style,
|
|
dock_placement,
|
|
deleted_agent_conversation_retention_days,
|
|
locale,
|
|
theme_source,
|
|
sleep_prevention_mode,
|
|
update_channel,
|
|
update_policy,
|
|
agent_composer_defaults_by_provider_json,
|
|
agent_composer_defaults_by_agent_target_json,
|
|
agent_gui_conversation_rail_collapsed_by_provider_json,
|
|
agent_session_launch_modes_by_workspace_json,
|
|
file_default_openers_by_extension_json,
|
|
app_catalog_channel,
|
|
browser_use_connection_mode,
|
|
minimize_animation,
|
|
show_app_developer_sources,
|
|
workbench_window_snapping_enabled,
|
|
workbench_window_snapping_shortcut_preset,
|
|
feature_flags_json,
|
|
workbench_shortcuts_json,
|
|
updated_at_unix_ms
|
|
)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`
|
|
|
|
const desktopPreferencesReplaceOnConflictSQL = `
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
agent_cli_update_check_enabled = excluded.agent_cli_update_check_enabled,
|
|
default_agent_provider = excluded.default_agent_provider,
|
|
agent_conversation_detail_mode = excluded.agent_conversation_detail_mode,
|
|
agent_dock_layout = excluded.agent_dock_layout,
|
|
dock_icon_style = excluded.dock_icon_style,
|
|
dock_placement = excluded.dock_placement,
|
|
deleted_agent_conversation_retention_days = excluded.deleted_agent_conversation_retention_days,
|
|
locale = excluded.locale,
|
|
theme_source = excluded.theme_source,
|
|
sleep_prevention_mode = excluded.sleep_prevention_mode,
|
|
update_channel = excluded.update_channel,
|
|
update_policy = excluded.update_policy,
|
|
agent_composer_defaults_by_provider_json = excluded.agent_composer_defaults_by_provider_json,
|
|
agent_gui_conversation_rail_collapsed_by_provider_json = excluded.agent_gui_conversation_rail_collapsed_by_provider_json,
|
|
file_default_openers_by_extension_json = excluded.file_default_openers_by_extension_json,
|
|
app_catalog_channel = excluded.app_catalog_channel,
|
|
browser_use_connection_mode = excluded.browser_use_connection_mode,
|
|
minimize_animation = excluded.minimize_animation,
|
|
show_app_developer_sources = excluded.show_app_developer_sources,
|
|
workbench_window_snapping_enabled = excluded.workbench_window_snapping_enabled,
|
|
workbench_window_snapping_shortcut_preset = excluded.workbench_window_snapping_shortcut_preset,
|
|
feature_flags_json = excluded.feature_flags_json,
|
|
workbench_shortcuts_json = excluded.workbench_shortcuts_json,
|
|
updated_at_unix_ms = excluded.updated_at_unix_ms
|
|
`
|
|
|
|
type desktopPreferencesExecer interface {
|
|
ExecContext(context.Context, string, ...any) (sql.Result, error)
|
|
}
|
|
|
|
type encodedDesktopPreferences struct {
|
|
agentComposerDefaultsJSON string
|
|
agentComposerDefaultsByAgentTargetJSON string
|
|
agentGUIConversationRailCollapsedJSON string
|
|
agentSessionLaunchModesJSON string
|
|
featureFlagsJSON string
|
|
fileDefaultOpenersJSON string
|
|
workbenchShortcutsJSON string
|
|
}
|
|
|
|
func (s *SQLiteStore) PutDesktopPreferences(ctx context.Context, preferences preferencesbiz.DesktopPreferences) (preferencesbiz.DesktopPreferences, error) {
|
|
if s == nil || s.writeDB == nil {
|
|
return preferencesbiz.DesktopPreferences{}, errors.New("workspace database is not initialized")
|
|
}
|
|
|
|
if _, err := writeDesktopPreferences(ctx, s.writeDB, preferences, desktopPreferencesReplaceOnConflictSQL); err != nil {
|
|
return preferencesbiz.DesktopPreferences{}, fmt.Errorf("put desktop preferences: %w", err)
|
|
}
|
|
|
|
// Re-read after the write because the dedicated target-defaults or launch-mode
|
|
// patch may have committed between a full preferences caller's read and this
|
|
// update. The conflict clause deliberately preserves those columns, so
|
|
// returning the input object here would publish a stale snapshot.
|
|
return s.GetDesktopPreferences(ctx)
|
|
}
|
|
|
|
func (s *SQLiteStore) InitializeDesktopPreferences(
|
|
ctx context.Context,
|
|
preferences preferencesbiz.DesktopPreferences,
|
|
) (preferencesbiz.DesktopPreferences, bool, error) {
|
|
if s == nil || s.writeDB == nil {
|
|
return preferencesbiz.DesktopPreferences{}, false, errors.New("workspace database is not initialized")
|
|
}
|
|
|
|
tx, err := s.writeDB.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return preferencesbiz.DesktopPreferences{}, false, fmt.Errorf("begin desktop preferences initialization: %w", err)
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
created, err := insertDesktopPreferencesIfAbsent(ctx, tx, preferences)
|
|
if err != nil {
|
|
return preferencesbiz.DesktopPreferences{}, false, fmt.Errorf("initialize desktop preferences: %w", err)
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return preferencesbiz.DesktopPreferences{}, false, fmt.Errorf("commit desktop preferences initialization: %w", err)
|
|
}
|
|
stored, err := s.GetDesktopPreferences(ctx)
|
|
if err != nil {
|
|
return preferencesbiz.DesktopPreferences{}, false, fmt.Errorf("read initialized desktop preferences: %w", err)
|
|
}
|
|
return stored, created, nil
|
|
}
|
|
|
|
func insertDesktopPreferencesIfAbsent(
|
|
ctx context.Context,
|
|
execer desktopPreferencesExecer,
|
|
preferences preferencesbiz.DesktopPreferences,
|
|
) (bool, error) {
|
|
result, err := writeDesktopPreferences(ctx, execer, preferences, "ON CONFLICT(id) DO NOTHING")
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
rows, err := result.RowsAffected()
|
|
if err != nil {
|
|
return false, fmt.Errorf("count initialized desktop preferences rows: %w", err)
|
|
}
|
|
return rows == 1, nil
|
|
}
|
|
|
|
func writeDesktopPreferences(
|
|
ctx context.Context,
|
|
execer desktopPreferencesExecer,
|
|
preferences preferencesbiz.DesktopPreferences,
|
|
conflictSQL string,
|
|
) (sql.Result, error) {
|
|
encoded, err := encodeDesktopPreferences(preferences)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return execer.ExecContext(
|
|
ctx,
|
|
desktopPreferencesInsertSQL+conflictSQL,
|
|
desktopPreferencesRowID,
|
|
preferences.AgentCLIUpdateCheckEnabled,
|
|
preferences.DefaultAgentProvider,
|
|
preferencesbiz.NormalizeDesktopAgentConversationDetailMode(preferences.AgentConversationDetailMode),
|
|
preferencesbiz.NormalizeDesktopAgentDockLayout(preferences.AgentDockLayout),
|
|
preferences.DockIconStyle,
|
|
preferences.DockPlacement,
|
|
preferencesbiz.NormalizeDeletedAgentConversationRetentionDays(preferences.DeletedAgentConversationRetentionDays),
|
|
preferences.Locale,
|
|
preferences.ThemeSource,
|
|
preferences.SleepPreventionMode,
|
|
preferences.UpdateChannel,
|
|
preferences.UpdatePolicy,
|
|
encoded.agentComposerDefaultsJSON,
|
|
encoded.agentComposerDefaultsByAgentTargetJSON,
|
|
encoded.agentGUIConversationRailCollapsedJSON,
|
|
encoded.agentSessionLaunchModesJSON,
|
|
encoded.fileDefaultOpenersJSON,
|
|
preferences.AppCatalogChannel,
|
|
preferences.BrowserUseConnectionMode,
|
|
preferences.MinimizeAnimation,
|
|
preferences.ShowAppDeveloperSources,
|
|
preferences.WindowSnappingEnabled,
|
|
preferences.WindowSnappingShortcutPreset,
|
|
encoded.featureFlagsJSON,
|
|
encoded.workbenchShortcutsJSON,
|
|
unixMs(time.Now().UTC()),
|
|
)
|
|
}
|
|
|
|
func encodeDesktopPreferences(preferences preferencesbiz.DesktopPreferences) (encodedDesktopPreferences, error) {
|
|
var encoded encodedDesktopPreferences
|
|
var err error
|
|
encoded.agentComposerDefaultsJSON, err = encodeAgentComposerDefaultsByProvider(preferences.AgentComposerDefaultsByProvider)
|
|
if err != nil {
|
|
return encoded, fmt.Errorf("encode desktop preferences agent composer defaults: %w", err)
|
|
}
|
|
encoded.agentComposerDefaultsByAgentTargetJSON, err = encodeAgentComposerDefaultsByProvider(preferences.AgentComposerDefaultsByAgentTarget)
|
|
if err != nil {
|
|
return encoded, fmt.Errorf("encode desktop preferences agent composer defaults by agent target: %w", err)
|
|
}
|
|
encoded.agentGUIConversationRailCollapsedJSON, err = encodeAgentGUIConversationRailCollapsedByProvider(preferences.AgentGUIConversationRailCollapsedByProvider)
|
|
if err != nil {
|
|
return encoded, fmt.Errorf("encode desktop preferences agent gui conversation rail: %w", err)
|
|
}
|
|
encoded.agentSessionLaunchModesJSON, err = encodeAgentSessionLaunchModesByWorkspace(preferences.AgentSessionLaunchModesByWorkspace)
|
|
if err != nil {
|
|
return encoded, fmt.Errorf("encode desktop preferences Agent Session launch modes: %w", err)
|
|
}
|
|
encoded.fileDefaultOpenersJSON, err = encodeFileDefaultOpenersByExtension(preferences.FileDefaultOpenersByExtension)
|
|
if err != nil {
|
|
return encoded, fmt.Errorf("encode desktop preferences file default openers: %w", err)
|
|
}
|
|
encoded.featureFlagsJSON, err = encodeFeatureFlags(preferences.FeatureFlags)
|
|
if err != nil {
|
|
return encoded, fmt.Errorf("encode desktop preferences feature flags: %w", err)
|
|
}
|
|
encoded.workbenchShortcutsJSON, err = encodeWorkbenchShortcuts(preferences.WorkbenchShortcuts)
|
|
if err != nil {
|
|
return encoded, fmt.Errorf("encode desktop preferences workbench shortcuts: %w", err)
|
|
}
|
|
return encoded, nil
|
|
}
|