Sync terminal selection AI preference (#1441)
Syncs the terminal selection Add to Conversation preference with AI settings so cloud sync, local backups, restores, and settings-only auto-sync detect the value. Follow-up for #1436.
This commit is contained in:
@@ -44,6 +44,7 @@ const {
|
||||
hasCloudSyncEntityData,
|
||||
hasMeaningfulCloudSyncData,
|
||||
shouldPromptCloudVaultRecovery,
|
||||
SYNCABLE_SETTING_STORAGE_KEYS,
|
||||
} = await import("./syncPayload.ts");
|
||||
const storageKeys = await import("../infrastructure/config/storageKeys.ts");
|
||||
|
||||
@@ -124,6 +125,7 @@ test("buildSyncPayload includes AI configuration settings", () => {
|
||||
localStorage.setItem(storageKeys.STORAGE_KEY_AI_AGENT_MODEL_MAP, JSON.stringify({ codex: "gpt-test" }));
|
||||
localStorage.setItem(storageKeys.STORAGE_KEY_AI_AGENT_PROVIDER_MAP, JSON.stringify({ catty: "openai-main" }));
|
||||
localStorage.setItem(storageKeys.STORAGE_KEY_AI_WEB_SEARCH, JSON.stringify(webSearch));
|
||||
localStorage.setItem(storageKeys.STORAGE_KEY_AI_SHOW_TERMINAL_SELECTION_ACTION, "false");
|
||||
|
||||
const payload = buildSyncPayload(vault([]));
|
||||
|
||||
@@ -140,9 +142,18 @@ test("buildSyncPayload includes AI configuration settings", () => {
|
||||
agentModelMap: { codex: "gpt-test" },
|
||||
agentProviderMap: { catty: "openai-main" },
|
||||
webSearchConfig: webSearch,
|
||||
showTerminalSelectionAction: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("terminal selection AI preference is syncable for auto-sync detection", () => {
|
||||
assert.ok(
|
||||
(SYNCABLE_SETTING_STORAGE_KEYS as readonly string[]).includes(
|
||||
storageKeys.STORAGE_KEY_AI_SHOW_TERMINAL_SELECTION_ACTION,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("buildSyncPayload includes host tree sidebar visibility setting", () => {
|
||||
localStorage.setItem(storageKeys.STORAGE_KEY_SHOW_HOST_TREE_SIDEBAR, "false");
|
||||
|
||||
@@ -215,6 +226,7 @@ test("applySyncPayload restores AI configuration settings", async () => {
|
||||
agentModelMap: { claude: "claude-test" },
|
||||
agentProviderMap: { catty: "anthropic-main" },
|
||||
webSearchConfig: webSearch,
|
||||
showTerminalSelectionAction: false,
|
||||
},
|
||||
},
|
||||
syncedAt: 1,
|
||||
@@ -234,6 +246,7 @@ test("applySyncPayload restores AI configuration settings", async () => {
|
||||
assert.deepEqual(JSON.parse(localStorage.getItem(storageKeys.STORAGE_KEY_AI_AGENT_MODEL_MAP)!), { claude: "claude-test" });
|
||||
assert.deepEqual(JSON.parse(localStorage.getItem(storageKeys.STORAGE_KEY_AI_AGENT_PROVIDER_MAP)!), { catty: "anthropic-main" });
|
||||
assert.deepEqual(JSON.parse(localStorage.getItem(storageKeys.STORAGE_KEY_AI_WEB_SEARCH)!), webSearch);
|
||||
assert.equal(localStorage.getItem(storageKeys.STORAGE_KEY_AI_SHOW_TERMINAL_SELECTION_ACTION), "false");
|
||||
});
|
||||
|
||||
test("applySyncPayload restores host tree sidebar visibility setting", async () => {
|
||||
|
||||
@@ -82,6 +82,7 @@ import {
|
||||
STORAGE_KEY_AI_AGENT_PROVIDER_MAP,
|
||||
STORAGE_KEY_AI_WEB_SEARCH,
|
||||
STORAGE_KEY_AI_QUICK_MESSAGES,
|
||||
STORAGE_KEY_AI_SHOW_TERMINAL_SELECTION_ACTION,
|
||||
STORAGE_KEY_PORT_FORWARDING,
|
||||
} from '../infrastructure/config/storageKeys';
|
||||
|
||||
@@ -251,6 +252,7 @@ export const SYNCABLE_SETTING_STORAGE_KEYS = [
|
||||
STORAGE_KEY_AI_AGENT_PROVIDER_MAP,
|
||||
STORAGE_KEY_AI_WEB_SEARCH,
|
||||
STORAGE_KEY_AI_QUICK_MESSAGES,
|
||||
STORAGE_KEY_AI_SHOW_TERMINAL_SELECTION_ACTION,
|
||||
] as const;
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
@@ -457,6 +459,10 @@ export function collectSyncableSettings(): SyncPayload['settings'] {
|
||||
if (webSearchConfig) ai.webSearchConfig = stripDeviceBoundApiKey(webSearchConfig);
|
||||
const quickMessages = readArraySetting(STORAGE_KEY_AI_QUICK_MESSAGES);
|
||||
if (quickMessages) ai.quickMessages = sanitizeQuickMessages(quickMessages);
|
||||
const showTerminalSelectionAction = localStorageAdapter.readBoolean(STORAGE_KEY_AI_SHOW_TERMINAL_SELECTION_ACTION);
|
||||
if (showTerminalSelectionAction != null) {
|
||||
ai.showTerminalSelectionAction = showTerminalSelectionAction;
|
||||
}
|
||||
if (Object.keys(ai).length > 0) settings.ai = ai;
|
||||
|
||||
return Object.keys(settings).length > 0 ? settings : undefined;
|
||||
@@ -594,6 +600,12 @@ function applySyncableSettings(settings: NonNullable<SyncPayload['settings']>):
|
||||
if (ai.quickMessages != null) {
|
||||
localStorageAdapter.write(STORAGE_KEY_AI_QUICK_MESSAGES, sanitizeQuickMessages(ai.quickMessages));
|
||||
}
|
||||
if (ai.showTerminalSelectionAction != null) {
|
||||
localStorageAdapter.writeBoolean(
|
||||
STORAGE_KEY_AI_SHOW_TERMINAL_SELECTION_ACTION,
|
||||
ai.showTerminalSelectionAction,
|
||||
);
|
||||
}
|
||||
// After all AI writes, reconcile per-agent bindings against the final
|
||||
// provider list. Sync payloads can land with a new `providers` set but
|
||||
// no `agentProviderMap`, or with a stale `agentProviderMap` that
|
||||
@@ -635,6 +647,9 @@ function notifyAIStateAfterSync(ai: NonNullable<SyncPayload['settings']>['ai']):
|
||||
}
|
||||
if (ai.webSearchConfig !== undefined) touched.push(STORAGE_KEY_AI_WEB_SEARCH);
|
||||
if (ai.quickMessages != null) touched.push(STORAGE_KEY_AI_QUICK_MESSAGES);
|
||||
if (ai.showTerminalSelectionAction != null) {
|
||||
touched.push(STORAGE_KEY_AI_SHOW_TERMINAL_SELECTION_ACTION);
|
||||
}
|
||||
for (const key of touched) {
|
||||
emitAIStateChanged(key);
|
||||
}
|
||||
|
||||
@@ -273,6 +273,7 @@ export interface SyncPayload {
|
||||
agentProviderMap?: Record<string, string>;
|
||||
webSearchConfig?: Record<string, unknown> | null;
|
||||
quickMessages?: Array<Record<string, unknown>>;
|
||||
showTerminalSelectionAction?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user