@oh-my-pi/pi-coding-agent 4.1.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +2 -1
  3. package/docs/sdk.md +0 -3
  4. package/package.json +6 -5
  5. package/src/config.ts +9 -0
  6. package/src/core/agent-session.ts +3 -3
  7. package/src/core/agent-storage.ts +450 -0
  8. package/src/core/auth-storage.ts +102 -183
  9. package/src/core/compaction/branch-summarization.ts +5 -4
  10. package/src/core/compaction/compaction.ts +7 -6
  11. package/src/core/compaction/utils.ts +6 -11
  12. package/src/core/custom-commands/bundled/review/index.ts +22 -94
  13. package/src/core/custom-share.ts +66 -0
  14. package/src/core/export-html/index.ts +1 -33
  15. package/src/core/history-storage.ts +15 -7
  16. package/src/core/prompt-templates.ts +271 -1
  17. package/src/core/sdk.ts +14 -3
  18. package/src/core/settings-manager.ts +100 -34
  19. package/src/core/slash-commands.ts +4 -1
  20. package/src/core/storage-migration.ts +215 -0
  21. package/src/core/system-prompt.ts +130 -290
  22. package/src/core/title-generator.ts +3 -2
  23. package/src/core/tools/ask.ts +2 -2
  24. package/src/core/tools/bash.ts +2 -1
  25. package/src/core/tools/calculator.ts +2 -1
  26. package/src/core/tools/complete.ts +5 -2
  27. package/src/core/tools/edit.ts +2 -1
  28. package/src/core/tools/find.ts +2 -1
  29. package/src/core/tools/gemini-image.ts +2 -1
  30. package/src/core/tools/git.ts +2 -2
  31. package/src/core/tools/grep.ts +2 -1
  32. package/src/core/tools/index.test.ts +0 -28
  33. package/src/core/tools/index.ts +0 -6
  34. package/src/core/tools/lsp/index.ts +2 -1
  35. package/src/core/tools/output.ts +2 -1
  36. package/src/core/tools/read.ts +4 -1
  37. package/src/core/tools/ssh.ts +4 -2
  38. package/src/core/tools/task/agents.ts +56 -30
  39. package/src/core/tools/task/commands.ts +5 -8
  40. package/src/core/tools/task/index.ts +7 -15
  41. package/src/core/tools/web-fetch.ts +2 -1
  42. package/src/core/tools/web-search/auth.ts +106 -16
  43. package/src/core/tools/web-search/index.ts +3 -2
  44. package/src/core/tools/web-search/providers/anthropic.ts +44 -6
  45. package/src/core/tools/write.ts +2 -1
  46. package/src/core/voice.ts +3 -1
  47. package/src/discovery/builtin.ts +9 -54
  48. package/src/discovery/claude.ts +16 -69
  49. package/src/discovery/codex.ts +11 -36
  50. package/src/discovery/helpers.ts +52 -1
  51. package/src/main.ts +1 -1
  52. package/src/migrations.ts +20 -20
  53. package/src/modes/interactive/controllers/command-controller.ts +527 -0
  54. package/src/modes/interactive/controllers/event-controller.ts +340 -0
  55. package/src/modes/interactive/controllers/extension-ui-controller.ts +600 -0
  56. package/src/modes/interactive/controllers/input-controller.ts +585 -0
  57. package/src/modes/interactive/controllers/selector-controller.ts +585 -0
  58. package/src/modes/interactive/interactive-mode.ts +363 -3139
  59. package/src/modes/interactive/theme/theme.ts +5 -5
  60. package/src/modes/interactive/types.ts +189 -0
  61. package/src/modes/interactive/utils/ui-helpers.ts +449 -0
  62. package/src/modes/interactive/utils/voice-manager.ts +96 -0
  63. package/src/prompts/{explore.md → agents/explore.md} +7 -5
  64. package/src/prompts/agents/frontmatter.md +7 -0
  65. package/src/prompts/{plan.md → agents/plan.md} +3 -3
  66. package/src/prompts/agents/planner.md +112 -0
  67. package/src/prompts/agents/task.md +15 -0
  68. package/src/prompts/review-request.md +44 -8
  69. package/src/prompts/system/custom-system-prompt.md +80 -0
  70. package/src/prompts/system/file-operations.md +12 -0
  71. package/src/prompts/system/system-prompt.md +237 -0
  72. package/src/prompts/system/title-system.md +2 -0
  73. package/src/prompts/tools/bash.md +1 -1
  74. package/src/prompts/tools/read.md +1 -1
  75. package/src/prompts/tools/task.md +34 -22
  76. package/src/core/tools/rulebook.ts +0 -132
  77. package/src/prompts/architect-plan.md +0 -10
  78. package/src/prompts/implement-with-critic.md +0 -11
  79. package/src/prompts/implement.md +0 -11
  80. package/src/prompts/system-prompt.md +0 -43
  81. package/src/prompts/task.md +0 -14
  82. package/src/prompts/title-system.md +0 -8
  83. /package/src/prompts/{init.md → agents/init.md} +0 -0
  84. /package/src/prompts/{reviewer.md → agents/reviewer.md} +0 -0
  85. /package/src/prompts/{branch-summary-preamble.md → compaction/branch-summary-preamble.md} +0 -0
  86. /package/src/prompts/{branch-summary.md → compaction/branch-summary.md} +0 -0
  87. /package/src/prompts/{compaction-summary.md → compaction/compaction-summary.md} +0 -0
  88. /package/src/prompts/{compaction-turn-prefix.md → compaction/compaction-turn-prefix.md} +0 -0
  89. /package/src/prompts/{compaction-update-summary.md → compaction/compaction-update-summary.md} +0 -0
  90. /package/src/prompts/{summarization-system.md → system/summarization-system.md} +0 -0
@@ -1,9 +1,11 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
1
+ import { existsSync, readFileSync, renameSync } from "node:fs";
2
+ import { join } from "node:path";
3
3
  import { type Settings as SettingsItem, settingsCapability } from "../capability/settings";
4
- import { getAgentDir } from "../config";
4
+ import { getAgentDbPath, getAgentDir } from "../config";
5
5
  import { loadSync } from "../discovery";
6
6
  import type { SymbolPreset } from "../modes/interactive/theme/theme";
7
+ import { AgentStorage } from "./agent-storage";
8
+ import { logger } from "./logger";
7
9
 
8
10
  export interface CompactionSettings {
9
11
  enabled?: boolean; // default: true
@@ -337,10 +339,36 @@ function normalizeBashInterceptorSettings(
337
339
  return { enabled, simpleLs, patterns };
338
340
  }
339
341
 
342
+ let cachedNerdFonts: boolean | null = null;
343
+
344
+ function hasNerdFonts(): boolean {
345
+ if (cachedNerdFonts !== null) {
346
+ return cachedNerdFonts;
347
+ }
348
+
349
+ const envOverride = process.env.NERD_FONTS;
350
+ if (envOverride === "1") {
351
+ cachedNerdFonts = true;
352
+ return true;
353
+ }
354
+ if (envOverride === "0") {
355
+ cachedNerdFonts = false;
356
+ return false;
357
+ }
358
+
359
+ const termProgram = (process.env.TERM_PROGRAM || "").toLowerCase();
360
+ const term = (process.env.TERM || "").toLowerCase();
361
+ const nerdTerms = ["iterm", "wezterm", "kitty", "ghostty", "alacritty"];
362
+ cachedNerdFonts = nerdTerms.some((candidate) => termProgram.includes(candidate) || term.includes(candidate));
363
+ return cachedNerdFonts;
364
+ }
365
+
340
366
  function normalizeSettings(settings: Settings): Settings {
341
367
  const merged = deepMergeSettings(DEFAULT_SETTINGS, settings);
368
+ const symbolPreset = merged.symbolPreset ?? (hasNerdFonts() ? "nerd" : "unicode");
342
369
  return {
343
370
  ...merged,
371
+ symbolPreset,
344
372
  bashInterceptor: normalizeBashInterceptorSettings(merged.bashInterceptor),
345
373
  };
346
374
  }
@@ -377,15 +405,23 @@ function deepMergeSettings(base: Settings, overrides: Settings): Settings {
377
405
  }
378
406
 
379
407
  export class SettingsManager {
380
- private settingsPath: string | null;
408
+ /** SQLite storage for persisted settings (null for in-memory mode) */
409
+ private storage: AgentStorage | null;
381
410
  private cwd: string | null;
382
411
  private globalSettings: Settings;
383
412
  private overrides: Settings;
384
413
  private settings!: Settings;
385
414
  private persist: boolean;
386
415
 
387
- private constructor(settingsPath: string | null, cwd: string | null, initialSettings: Settings, persist: boolean) {
388
- this.settingsPath = settingsPath;
416
+ /**
417
+ * Private constructor - use static factory methods instead.
418
+ * @param storage - SQLite storage instance for persistence, or null for in-memory mode
419
+ * @param cwd - Current working directory for project settings discovery
420
+ * @param initialSettings - Initial global settings to use
421
+ * @param persist - Whether to persist settings changes to storage
422
+ */
423
+ private constructor(storage: AgentStorage | null, cwd: string | null, initialSettings: Settings, persist: boolean) {
424
+ this.storage = storage;
389
425
  this.cwd = cwd;
390
426
  this.persist = persist;
391
427
  this.globalSettings = initialSettings;
@@ -416,9 +452,15 @@ export class SettingsManager {
416
452
  }
417
453
  }
418
454
 
419
- /** Create a SettingsManager that loads from files */
455
+ /**
456
+ * Create a SettingsManager that loads from persistent SQLite storage.
457
+ * @param cwd - Current working directory for project settings discovery
458
+ * @param agentDir - Agent directory containing agent.db
459
+ * @returns Configured SettingsManager with merged global and user settings
460
+ */
420
461
  static create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): SettingsManager {
421
- const settingsPath = join(agentDir, "settings.json");
462
+ const storage = AgentStorage.open(getAgentDbPath(agentDir));
463
+ SettingsManager.migrateLegacySettingsFile(storage, agentDir);
422
464
 
423
465
  // Use capability API to load user-level settings from all providers
424
466
  const result = loadSync(settingsCapability.id, { cwd });
@@ -431,29 +473,58 @@ export class SettingsManager {
431
473
  }
432
474
  }
433
475
 
434
- // Also load from agentDir for backward compatibility (if not covered by providers)
435
- const legacySettings = SettingsManager.loadFromFile(settingsPath);
436
- globalSettings = deepMergeSettings(globalSettings, legacySettings);
476
+ // Load persisted settings from agent.db (legacy settings.json is migrated separately)
477
+ const storedSettings = SettingsManager.loadFromStorage(storage);
478
+ globalSettings = deepMergeSettings(globalSettings, storedSettings);
437
479
 
438
- return new SettingsManager(settingsPath, cwd, globalSettings, true);
480
+ return new SettingsManager(storage, cwd, globalSettings, true);
439
481
  }
440
482
 
441
- /** Create an in-memory SettingsManager (no file I/O) */
483
+ /**
484
+ * Create an in-memory SettingsManager without persistence.
485
+ * @param settings - Initial settings to use
486
+ * @returns SettingsManager that won't persist changes to disk
487
+ */
442
488
  static inMemory(settings: Partial<Settings> = {}): SettingsManager {
443
489
  return new SettingsManager(null, null, settings, false);
444
490
  }
445
491
 
446
- private static loadFromFile(path: string): Settings {
447
- if (!existsSync(path)) {
492
+ /**
493
+ * Load settings from SQLite storage, applying any schema migrations.
494
+ * @param storage - AgentStorage instance, or null for in-memory mode
495
+ * @returns Parsed and migrated settings, or empty object if storage is null/empty
496
+ */
497
+ private static loadFromStorage(storage: AgentStorage | null): Settings {
498
+ if (!storage) {
448
499
  return {};
449
500
  }
501
+ const settings = storage.getSettings();
502
+ if (!settings) {
503
+ return {};
504
+ }
505
+ return SettingsManager.migrateSettings(settings as Record<string, unknown>);
506
+ }
507
+
508
+ private static migrateLegacySettingsFile(storage: AgentStorage, agentDir: string): void {
509
+ const settingsPath = join(agentDir, "settings.json");
510
+ if (!existsSync(settingsPath)) return;
511
+ if (storage.getSettings() !== null) return;
512
+
450
513
  try {
451
- const content = readFileSync(path, "utf-8");
452
- const settings = JSON.parse(content);
453
- return SettingsManager.migrateSettings(settings as Record<string, unknown>);
514
+ const content = readFileSync(settingsPath, "utf-8");
515
+ const parsed = JSON.parse(content);
516
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
517
+ return;
518
+ }
519
+ const migrated = SettingsManager.migrateSettings(parsed as Record<string, unknown>);
520
+ storage.saveSettings(migrated);
521
+ try {
522
+ renameSync(settingsPath, `${settingsPath}.bak`);
523
+ } catch (error) {
524
+ logger.warn("SettingsManager failed to backup settings.json", { error: String(error) });
525
+ }
454
526
  } catch (error) {
455
- console.error(`Warning: Could not read settings file ${path}: ${error}`);
456
- return {};
527
+ logger.warn("SettingsManager failed to migrate settings.json", { error: String(error) });
457
528
  }
458
529
  }
459
530
 
@@ -497,24 +568,19 @@ export class SettingsManager {
497
568
  this.rebuildSettings();
498
569
  }
499
570
 
571
+ /**
572
+ * Persist current global settings to SQLite storage and rebuild merged settings.
573
+ * Merges with any concurrent changes in storage before saving.
574
+ */
500
575
  private save(): void {
501
- if (this.persist && this.settingsPath) {
576
+ if (this.persist && this.storage) {
502
577
  try {
503
- const dir = dirname(this.settingsPath);
504
- if (!existsSync(dir)) {
505
- mkdirSync(dir, { recursive: true });
506
- }
507
-
508
- // Re-read current file to preserve any settings added externally while running
509
- const currentFileSettings = SettingsManager.loadFromFile(this.settingsPath);
510
- // Merge: file settings as base, globalSettings (in-memory changes) as overrides
511
- const mergedSettings = deepMergeSettings(currentFileSettings, this.globalSettings);
578
+ const currentSettings = this.storage.getSettings() ?? {};
579
+ const mergedSettings = deepMergeSettings(currentSettings, this.globalSettings);
512
580
  this.globalSettings = mergedSettings;
513
-
514
- // Save merged settings (project settings are read-only)
515
- writeFileSync(this.settingsPath, JSON.stringify(this.globalSettings, null, 2), "utf-8");
581
+ this.storage.saveSettings(this.globalSettings);
516
582
  } catch (error) {
517
- console.error(`Warning: Could not save settings file: ${error}`);
583
+ logger.warn("SettingsManager save failed", { error: String(error) });
518
584
  }
519
585
  }
520
586
 
@@ -2,6 +2,7 @@ import { slashCommandCapability } from "../capability/slash-command";
2
2
  import type { SlashCommand } from "../discovery";
3
3
  import { loadSync } from "../discovery";
4
4
  import { parseFrontmatter } from "../discovery/helpers";
5
+ import { renderPromptTemplate } from "./prompt-templates";
5
6
  import { EMBEDDED_COMMAND_TEMPLATES } from "./tools/task/commands";
6
7
 
7
8
  /**
@@ -158,7 +159,9 @@ export function expandSlashCommand(text: string, fileCommands: FileSlashCommand[
158
159
  const fileCommand = fileCommands.find((cmd) => cmd.name === commandName);
159
160
  if (fileCommand) {
160
161
  const args = parseCommandArgs(argsString);
161
- return substituteArgs(fileCommand.content, args);
162
+ const argsText = args.join(" ");
163
+ const substituted = substituteArgs(fileCommand.content, args);
164
+ return renderPromptTemplate(substituted, { args, ARGUMENTS: argsText, arguments: argsText });
162
165
  }
163
166
 
164
167
  return text;
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Migrates legacy JSON storage (settings.json, auth.json) to SQLite-based agent.db.
3
+ * Settings migrate only when the DB has no settings; auth merges per-provider when missing.
4
+ * Original JSON files are backed up to .bak and removed after successful migration.
5
+ */
6
+
7
+ import { getAgentDbPath } from "../config";
8
+ import { AgentStorage } from "./agent-storage";
9
+ import type { AuthCredential, AuthCredentialEntry, AuthStorageData } from "./auth-storage";
10
+ import { logger } from "./logger";
11
+ import type { Settings } from "./settings-manager";
12
+
13
+ /** Paths configuration for the storage migration process. */
14
+ type MigrationPaths = {
15
+ /** Directory containing agent.db */
16
+ agentDir: string;
17
+ /** Path to legacy settings.json file */
18
+ settingsPath: string;
19
+ /** Candidate paths to search for auth.json (checked in order) */
20
+ authPaths: string[];
21
+ };
22
+
23
+ /** Result of the JSON-to-SQLite storage migration. */
24
+ export interface StorageMigrationResult {
25
+ /** Whether settings.json was migrated to agent.db */
26
+ migratedSettings: boolean;
27
+ /** Whether auth.json was migrated to agent.db */
28
+ migratedAuth: boolean;
29
+ /** Non-fatal issues encountered during migration */
30
+ warnings: string[];
31
+ }
32
+
33
+ /**
34
+ * Type guard for plain objects.
35
+ * @param value - Value to check
36
+ * @returns True if value is a non-null, non-array object
37
+ */
38
+ function isRecord(value: unknown): value is Record<string, unknown> {
39
+ return !!value && typeof value === "object" && !Array.isArray(value);
40
+ }
41
+
42
+ /**
43
+ * Transforms legacy settings to current schema (e.g., queueMode -> steeringMode).
44
+ * @param settings - Settings object potentially containing deprecated keys
45
+ * @returns Settings with deprecated keys renamed to current equivalents
46
+ */
47
+ function migrateLegacySettings(settings: Settings): Settings {
48
+ const migrated = { ...settings } as Record<string, unknown>;
49
+ if ("queueMode" in migrated && !("steeringMode" in migrated)) {
50
+ migrated.steeringMode = migrated.queueMode;
51
+ delete migrated.queueMode;
52
+ }
53
+ return migrated as Settings;
54
+ }
55
+
56
+ /**
57
+ * Normalizes credential entries to array format (legacy stored single credentials).
58
+ * @param entry - Single credential or array of credentials
59
+ * @returns Array of credentials (empty if entry is undefined)
60
+ */
61
+ function normalizeCredentialEntry(entry: AuthCredentialEntry | undefined): AuthCredential[] {
62
+ if (!entry) return [];
63
+ return Array.isArray(entry) ? entry : [entry];
64
+ }
65
+
66
+ /**
67
+ * Reads and parses a JSON file.
68
+ * @param path - Path to the JSON file
69
+ * @returns Parsed JSON content, or null if file doesn't exist or parsing fails
70
+ */
71
+ async function readJsonFile<T>(path: string): Promise<T | null> {
72
+ try {
73
+ const file = Bun.file(path);
74
+ if (!(await file.exists())) return null;
75
+ const content = await file.text();
76
+ return JSON.parse(content) as T;
77
+ } catch (error) {
78
+ logger.warn("Storage migration failed to read JSON", { path, error: String(error) });
79
+ return null;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Backs up a JSON file to .bak and removes the original.
85
+ * Prevents re-migration on subsequent runs.
86
+ * @param path - Path to the JSON file to backup
87
+ */
88
+ async function backupJson(path: string): Promise<void> {
89
+ const file = Bun.file(path);
90
+ if (!(await file.exists())) return;
91
+
92
+ const backupPath = `${path}.bak`;
93
+ try {
94
+ const content = await file.arrayBuffer();
95
+ await Bun.write(backupPath, content);
96
+ await file.unlink();
97
+ } catch (error) {
98
+ logger.warn("Storage migration failed to backup JSON", { path, error: String(error) });
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Migrates settings.json to SQLite storage if DB is empty.
104
+ * @param storage - AgentStorage instance to migrate into
105
+ * @param settingsPath - Path to legacy settings.json
106
+ * @param warnings - Array to collect non-fatal warnings
107
+ * @returns True if migration was performed
108
+ */
109
+ async function migrateSettings(storage: AgentStorage, settingsPath: string, warnings: string[]): Promise<boolean> {
110
+ const settingsFile = Bun.file(settingsPath);
111
+ const settingsExists = await settingsFile.exists();
112
+ const hasDbSettings = storage.getSettings() !== null;
113
+
114
+ if (!settingsExists) return false;
115
+ if (hasDbSettings) {
116
+ warnings.push(`settings.json exists but agent.db is authoritative: ${settingsPath}`);
117
+ return false;
118
+ }
119
+
120
+ const settingsJson = await readJsonFile<Settings>(settingsPath);
121
+ if (!settingsJson) return false;
122
+
123
+ storage.saveSettings(migrateLegacySettings(settingsJson));
124
+ await backupJson(settingsPath);
125
+ return true;
126
+ }
127
+
128
+ /**
129
+ * Finds the first valid auth.json from candidate paths (checked in priority order).
130
+ * @param authPaths - Candidate paths to search (e.g., project-local before global)
131
+ * @returns First valid auth file with its path and parsed data, or null if none found
132
+ */
133
+ async function findFirstAuthJson(authPaths: string[]): Promise<{ path: string; data: AuthStorageData } | null> {
134
+ for (const authPath of authPaths) {
135
+ const data = await readJsonFile<AuthStorageData>(authPath);
136
+ if (data && isRecord(data)) {
137
+ return { path: authPath, data };
138
+ }
139
+ }
140
+ return null;
141
+ }
142
+
143
+ /**
144
+ * Validates that a credential has a recognized type.
145
+ * @param entry - Credential to validate
146
+ * @returns True if credential type is api_key or oauth
147
+ */
148
+ function isValidCredential(entry: AuthCredential): boolean {
149
+ return entry.type === "api_key" || entry.type === "oauth";
150
+ }
151
+
152
+ /**
153
+ * Migrates auth.json to SQLite storage for providers missing in agent.db.
154
+ * @param storage - AgentStorage instance to migrate into
155
+ * @param authPaths - Candidate paths to search for auth.json
156
+ * @param warnings - Array to collect non-fatal warnings
157
+ * @returns True if migration was performed
158
+ */
159
+ async function migrateAuth(storage: AgentStorage, authPaths: string[], warnings: string[]): Promise<boolean> {
160
+ const authJson = await findFirstAuthJson(authPaths);
161
+ if (!authJson) return false;
162
+
163
+ let sawValid = false;
164
+ let migratedAny = false;
165
+
166
+ for (const [provider, entry] of Object.entries(authJson.data)) {
167
+ const credentials = normalizeCredentialEntry(entry)
168
+ .filter(isValidCredential)
169
+ .map((credential) => credential);
170
+
171
+ if (credentials.length === 0) continue;
172
+ sawValid = true;
173
+
174
+ if (storage.listAuthCredentials(provider).length > 0) {
175
+ continue;
176
+ }
177
+
178
+ storage.replaceAuthCredentialsForProvider(provider, credentials);
179
+ migratedAny = true;
180
+ }
181
+
182
+ if (sawValid) {
183
+ await backupJson(authJson.path);
184
+ }
185
+
186
+ if (!migratedAny && sawValid) {
187
+ warnings.push(`auth.json entries already present in agent.db: ${authJson.path}`);
188
+ }
189
+
190
+ return migratedAny;
191
+ }
192
+
193
+ /**
194
+ * Migrates legacy JSON files (settings.json, auth.json) to SQLite-based agent.db.
195
+ * Settings migrate only when the DB has no settings; auth merges per-provider when missing.
196
+ * @param paths - Configuration specifying locations of legacy files and target DB
197
+ * @returns Result indicating what was migrated and any warnings encountered
198
+ */
199
+ export async function migrateJsonStorage(paths: MigrationPaths): Promise<StorageMigrationResult> {
200
+ const storage = AgentStorage.open(getAgentDbPath(paths.agentDir));
201
+ const warnings: string[] = [];
202
+
203
+ const [migratedSettings, migratedAuth] = await Promise.all([
204
+ migrateSettings(storage, paths.settingsPath, warnings),
205
+ migrateAuth(storage, paths.authPaths, warnings),
206
+ ]);
207
+
208
+ if (warnings.length > 0) {
209
+ for (const warning of warnings) {
210
+ logger.warn("Storage migration warning", { warning });
211
+ }
212
+ }
213
+
214
+ return { migratedSettings, migratedAuth, warnings };
215
+ }