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

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 (58) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/docs/sdk.md +5 -5
  3. package/examples/sdk/10-settings.ts +2 -2
  4. package/package.json +5 -5
  5. package/src/capability/fs.ts +90 -0
  6. package/src/capability/index.ts +41 -227
  7. package/src/capability/types.ts +1 -11
  8. package/src/cli/args.ts +4 -0
  9. package/src/core/agent-session.ts +4 -4
  10. package/src/core/agent-storage.ts +50 -0
  11. package/src/core/auth-storage.ts +112 -4
  12. package/src/core/bash-executor.ts +1 -1
  13. package/src/core/custom-tools/loader.ts +2 -2
  14. package/src/core/extensions/loader.ts +2 -2
  15. package/src/core/extensions/types.ts +1 -1
  16. package/src/core/hooks/loader.ts +2 -2
  17. package/src/core/mcp/config.ts +2 -2
  18. package/src/core/model-registry.ts +46 -0
  19. package/src/core/sdk.ts +37 -29
  20. package/src/core/settings-manager.ts +152 -135
  21. package/src/core/skills.ts +72 -51
  22. package/src/core/slash-commands.ts +3 -3
  23. package/src/core/system-prompt.ts +10 -10
  24. package/src/core/tools/edit.ts +7 -4
  25. package/src/core/tools/find.ts +2 -2
  26. package/src/core/tools/index.test.ts +16 -0
  27. package/src/core/tools/index.ts +21 -8
  28. package/src/core/tools/lsp/index.ts +4 -1
  29. package/src/core/tools/ssh.ts +6 -6
  30. package/src/core/tools/task/commands.ts +3 -5
  31. package/src/core/tools/task/executor.ts +88 -3
  32. package/src/core/tools/task/index.ts +4 -0
  33. package/src/core/tools/task/model-resolver.ts +10 -7
  34. package/src/core/tools/task/worker-protocol.ts +48 -2
  35. package/src/core/tools/task/worker.ts +152 -7
  36. package/src/core/tools/write.ts +7 -4
  37. package/src/discovery/agents-md.ts +13 -19
  38. package/src/discovery/builtin.ts +367 -247
  39. package/src/discovery/claude.ts +181 -290
  40. package/src/discovery/cline.ts +30 -10
  41. package/src/discovery/codex.ts +185 -244
  42. package/src/discovery/cursor.ts +106 -121
  43. package/src/discovery/gemini.ts +72 -97
  44. package/src/discovery/github.ts +7 -10
  45. package/src/discovery/helpers.ts +94 -88
  46. package/src/discovery/index.ts +1 -2
  47. package/src/discovery/mcp-json.ts +15 -18
  48. package/src/discovery/ssh.ts +9 -17
  49. package/src/discovery/vscode.ts +10 -5
  50. package/src/discovery/windsurf.ts +52 -86
  51. package/src/main.ts +5 -1
  52. package/src/modes/interactive/components/extensions/extension-dashboard.ts +24 -11
  53. package/src/modes/interactive/components/extensions/state-manager.ts +19 -15
  54. package/src/modes/interactive/controllers/selector-controller.ts +6 -2
  55. package/src/modes/interactive/interactive-mode.ts +19 -15
  56. package/src/prompts/agents/plan.md +107 -30
  57. package/src/utils/shell.ts +2 -2
  58. package/src/prompts/agents/planner.md +0 -112
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, renameSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { type Settings as SettingsItem, settingsCapability } from "../capability/settings";
4
4
  import { getAgentDbPath, getAgentDir } from "../config";
5
- import { loadSync } from "../discovery";
5
+ import { loadCapability } from "../discovery";
6
6
  import type { SymbolPreset } from "../modes/interactive/theme/theme";
7
7
  import { AgentStorage } from "./agent-storage";
8
8
  import { logger } from "./logger";
@@ -419,14 +419,21 @@ export class SettingsManager {
419
419
  * @param cwd - Current working directory for project settings discovery
420
420
  * @param initialSettings - Initial global settings to use
421
421
  * @param persist - Whether to persist settings changes to storage
422
+ * @param projectSettings - Pre-loaded project settings (to avoid async in constructor)
422
423
  */
423
- private constructor(storage: AgentStorage | null, cwd: string | null, initialSettings: Settings, persist: boolean) {
424
+ private constructor(
425
+ storage: AgentStorage | null,
426
+ cwd: string | null,
427
+ initialSettings: Settings,
428
+ persist: boolean,
429
+ projectSettings: Settings,
430
+ ) {
424
431
  this.storage = storage;
425
432
  this.cwd = cwd;
426
433
  this.persist = persist;
427
434
  this.globalSettings = initialSettings;
428
435
  this.overrides = {};
429
- this.rebuildSettings();
436
+ this.rebuildSettings(projectSettings);
430
437
 
431
438
  // Apply environment variables from settings
432
439
  this.applyEnvironmentVariables();
@@ -458,12 +465,12 @@ export class SettingsManager {
458
465
  * @param agentDir - Agent directory containing agent.db
459
466
  * @returns Configured SettingsManager with merged global and user settings
460
467
  */
461
- static create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): SettingsManager {
468
+ static async create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): Promise<SettingsManager> {
462
469
  const storage = AgentStorage.open(getAgentDbPath(agentDir));
463
470
  SettingsManager.migrateLegacySettingsFile(storage, agentDir);
464
471
 
465
472
  // Use capability API to load user-level settings from all providers
466
- const result = loadSync(settingsCapability.id, { cwd });
473
+ const result = await loadCapability(settingsCapability.id, { cwd });
467
474
 
468
475
  // Merge all user-level settings
469
476
  let globalSettings: Settings = {};
@@ -477,7 +484,10 @@ export class SettingsManager {
477
484
  const storedSettings = SettingsManager.loadFromStorage(storage);
478
485
  globalSettings = deepMergeSettings(globalSettings, storedSettings);
479
486
 
480
- return new SettingsManager(storage, cwd, globalSettings, true);
487
+ // Load project settings before construction (constructor is sync)
488
+ const projectSettings = await SettingsManager.loadProjectSettingsStatic(cwd);
489
+
490
+ return new SettingsManager(storage, cwd, globalSettings, true, projectSettings);
481
491
  }
482
492
 
483
493
  /**
@@ -486,7 +496,7 @@ export class SettingsManager {
486
496
  * @returns SettingsManager that won't persist changes to disk
487
497
  */
488
498
  static inMemory(settings: Partial<Settings> = {}): SettingsManager {
489
- return new SettingsManager(null, null, settings, false);
499
+ return new SettingsManager(null, null, settings, false, {});
490
500
  }
491
501
 
492
502
  /**
@@ -538,11 +548,14 @@ export class SettingsManager {
538
548
  return settings as Settings;
539
549
  }
540
550
 
541
- private loadProjectSettings(): Settings {
542
- if (!this.cwd) return {};
551
+ /**
552
+ * Static helper to load project settings (used by create() before construction).
553
+ */
554
+ private static async loadProjectSettingsStatic(cwd: string | null): Promise<Settings> {
555
+ if (!cwd) return {};
543
556
 
544
557
  // Use capability API to discover settings from all providers
545
- const result = loadSync(settingsCapability.id, { cwd: this.cwd });
558
+ const result = await loadCapability(settingsCapability.id, { cwd });
546
559
 
547
560
  // Merge only project-level settings (user-level settings are handled separately via globalSettings)
548
561
  let merged: Settings = {};
@@ -555,24 +568,28 @@ export class SettingsManager {
555
568
  return SettingsManager.migrateSettings(merged as Record<string, unknown>);
556
569
  }
557
570
 
558
- private rebuildSettings(projectSettings?: Settings): void {
559
- const resolvedProjectSettings = projectSettings ?? this.loadProjectSettings();
571
+ private async loadProjectSettings(): Promise<Settings> {
572
+ return SettingsManager.loadProjectSettingsStatic(this.cwd);
573
+ }
574
+
575
+ private rebuildSettings(projectSettings: Settings): void {
560
576
  this.settings = normalizeSettings(
561
- deepMergeSettings(deepMergeSettings(this.globalSettings, resolvedProjectSettings), this.overrides),
577
+ deepMergeSettings(deepMergeSettings(this.globalSettings, projectSettings), this.overrides),
562
578
  );
563
579
  }
564
580
 
565
581
  /** Apply additional overrides on top of current settings */
566
- applyOverrides(overrides: Partial<Settings>): void {
582
+ async applyOverrides(overrides: Partial<Settings>): Promise<void> {
567
583
  this.overrides = deepMergeSettings(this.overrides, overrides);
568
- this.rebuildSettings();
584
+ const projectSettings = await this.loadProjectSettings();
585
+ this.rebuildSettings(projectSettings);
569
586
  }
570
587
 
571
588
  /**
572
589
  * Persist current global settings to SQLite storage and rebuild merged settings.
573
590
  * Merges with any concurrent changes in storage before saving.
574
591
  */
575
- private save(): void {
592
+ private async save(): Promise<void> {
576
593
  if (this.persist && this.storage) {
577
594
  try {
578
595
  const currentSettings = this.storage.getSettings() ?? {};
@@ -585,7 +602,7 @@ export class SettingsManager {
585
602
  }
586
603
 
587
604
  // Always re-merge to update active settings (needed for both file and inMemory modes)
588
- const projectSettings = this.loadProjectSettings();
605
+ const projectSettings = await this.loadProjectSettings();
589
606
  this.rebuildSettings(projectSettings);
590
607
  }
591
608
 
@@ -593,9 +610,9 @@ export class SettingsManager {
593
610
  return this.settings.lastChangelogVersion;
594
611
  }
595
612
 
596
- setLastChangelogVersion(version: string): void {
613
+ async setLastChangelogVersion(version: string): Promise<void> {
597
614
  this.globalSettings.lastChangelogVersion = version;
598
- this.save();
615
+ await this.save();
599
616
  }
600
617
 
601
618
  /**
@@ -608,7 +625,7 @@ export class SettingsManager {
608
625
  /**
609
626
  * Set model for a role. Model should be "provider/modelId" format.
610
627
  */
611
- setModelRole(role: string, model: string): void {
628
+ async setModelRole(role: string, model: string): Promise<void> {
612
629
  if (!this.globalSettings.modelRoles) {
613
630
  this.globalSettings.modelRoles = {};
614
631
  }
@@ -618,7 +635,7 @@ export class SettingsManager {
618
635
  this.overrides.modelRoles[role] = model;
619
636
  }
620
637
 
621
- this.save();
638
+ await this.save();
622
639
  }
623
640
 
624
641
  /**
@@ -632,66 +649,66 @@ export class SettingsManager {
632
649
  return this.settings.steeringMode || "one-at-a-time";
633
650
  }
634
651
 
635
- setSteeringMode(mode: "all" | "one-at-a-time"): void {
652
+ async setSteeringMode(mode: "all" | "one-at-a-time"): Promise<void> {
636
653
  this.globalSettings.steeringMode = mode;
637
- this.save();
654
+ await this.save();
638
655
  }
639
656
 
640
657
  getFollowUpMode(): "all" | "one-at-a-time" {
641
658
  return this.settings.followUpMode || "one-at-a-time";
642
659
  }
643
660
 
644
- setFollowUpMode(mode: "all" | "one-at-a-time"): void {
661
+ async setFollowUpMode(mode: "all" | "one-at-a-time"): Promise<void> {
645
662
  this.globalSettings.followUpMode = mode;
646
- this.save();
663
+ await this.save();
647
664
  }
648
665
 
649
666
  getInterruptMode(): "immediate" | "wait" {
650
667
  return this.settings.interruptMode || "immediate";
651
668
  }
652
669
 
653
- setInterruptMode(mode: "immediate" | "wait"): void {
670
+ async setInterruptMode(mode: "immediate" | "wait"): Promise<void> {
654
671
  this.globalSettings.interruptMode = mode;
655
- this.save();
672
+ await this.save();
656
673
  }
657
674
 
658
675
  getTheme(): string | undefined {
659
676
  return this.settings.theme;
660
677
  }
661
678
 
662
- setTheme(theme: string): void {
679
+ async setTheme(theme: string): Promise<void> {
663
680
  this.globalSettings.theme = theme;
664
- this.save();
681
+ await this.save();
665
682
  }
666
683
 
667
684
  getSymbolPreset(): SymbolPreset | undefined {
668
685
  return this.settings.symbolPreset;
669
686
  }
670
687
 
671
- setSymbolPreset(preset: SymbolPreset): void {
688
+ async setSymbolPreset(preset: SymbolPreset): Promise<void> {
672
689
  this.globalSettings.symbolPreset = preset;
673
- this.save();
690
+ await this.save();
674
691
  }
675
692
 
676
693
  getDefaultThinkingLevel(): "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | undefined {
677
694
  return this.settings.defaultThinkingLevel;
678
695
  }
679
696
 
680
- setDefaultThinkingLevel(level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"): void {
697
+ async setDefaultThinkingLevel(level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"): Promise<void> {
681
698
  this.globalSettings.defaultThinkingLevel = level;
682
- this.save();
699
+ await this.save();
683
700
  }
684
701
 
685
702
  getCompactionEnabled(): boolean {
686
703
  return this.settings.compaction?.enabled ?? true;
687
704
  }
688
705
 
689
- setCompactionEnabled(enabled: boolean): void {
706
+ async setCompactionEnabled(enabled: boolean): Promise<void> {
690
707
  if (!this.globalSettings.compaction) {
691
708
  this.globalSettings.compaction = {};
692
709
  }
693
710
  this.globalSettings.compaction.enabled = enabled;
694
- this.save();
711
+ await this.save();
695
712
  }
696
713
 
697
714
  getCompactionReserveTokens(): number {
@@ -714,12 +731,12 @@ export class SettingsManager {
714
731
  return this.settings.branchSummary?.enabled ?? false;
715
732
  }
716
733
 
717
- setBranchSummaryEnabled(enabled: boolean): void {
734
+ async setBranchSummaryEnabled(enabled: boolean): Promise<void> {
718
735
  if (!this.globalSettings.branchSummary) {
719
736
  this.globalSettings.branchSummary = {};
720
737
  }
721
738
  this.globalSettings.branchSummary.enabled = enabled;
722
- this.save();
739
+ await this.save();
723
740
  }
724
741
 
725
742
  getBranchSummarySettings(): { enabled: boolean; reserveTokens: number } {
@@ -733,12 +750,12 @@ export class SettingsManager {
733
750
  return this.settings.retry?.enabled ?? true;
734
751
  }
735
752
 
736
- setRetryEnabled(enabled: boolean): void {
753
+ async setRetryEnabled(enabled: boolean): Promise<void> {
737
754
  if (!this.globalSettings.retry) {
738
755
  this.globalSettings.retry = {};
739
756
  }
740
757
  this.globalSettings.retry.enabled = enabled;
741
- this.save();
758
+ await this.save();
742
759
  }
743
760
 
744
761
  getRetrySettings(): { enabled: boolean; maxRetries: number; baseDelayMs: number } {
@@ -757,48 +774,48 @@ export class SettingsManager {
757
774
  return this.settings.hideThinkingBlock ?? false;
758
775
  }
759
776
 
760
- setHideThinkingBlock(hide: boolean): void {
777
+ async setHideThinkingBlock(hide: boolean): Promise<void> {
761
778
  this.globalSettings.hideThinkingBlock = hide;
762
- this.save();
779
+ await this.save();
763
780
  }
764
781
 
765
782
  getShellPath(): string | undefined {
766
783
  return this.settings.shellPath;
767
784
  }
768
785
 
769
- setShellPath(path: string | undefined): void {
786
+ async setShellPath(path: string | undefined): Promise<void> {
770
787
  this.globalSettings.shellPath = path;
771
- this.save();
788
+ await this.save();
772
789
  }
773
790
 
774
791
  getCollapseChangelog(): boolean {
775
792
  return this.settings.collapseChangelog ?? false;
776
793
  }
777
794
 
778
- setCollapseChangelog(collapse: boolean): void {
795
+ async setCollapseChangelog(collapse: boolean): Promise<void> {
779
796
  this.globalSettings.collapseChangelog = collapse;
780
- this.save();
797
+ await this.save();
781
798
  }
782
799
 
783
800
  getExtensionPaths(): string[] {
784
801
  return [...(this.settings.extensions ?? [])];
785
802
  }
786
803
 
787
- setExtensionPaths(paths: string[]): void {
804
+ async setExtensionPaths(paths: string[]): Promise<void> {
788
805
  this.globalSettings.extensions = paths;
789
- this.save();
806
+ await this.save();
790
807
  }
791
808
 
792
809
  getSkillsEnabled(): boolean {
793
810
  return this.settings.skills?.enabled ?? true;
794
811
  }
795
812
 
796
- setSkillsEnabled(enabled: boolean): void {
813
+ async setSkillsEnabled(enabled: boolean): Promise<void> {
797
814
  if (!this.globalSettings.skills) {
798
815
  this.globalSettings.skills = {};
799
816
  }
800
817
  this.globalSettings.skills.enabled = enabled;
801
- this.save();
818
+ await this.save();
802
819
  }
803
820
 
804
821
  getSkillsSettings(): Required<SkillsSettings> {
@@ -826,48 +843,48 @@ export class SettingsManager {
826
843
  return this.settings.terminal?.showImages ?? true;
827
844
  }
828
845
 
829
- setShowImages(show: boolean): void {
846
+ async setShowImages(show: boolean): Promise<void> {
830
847
  if (!this.globalSettings.terminal) {
831
848
  this.globalSettings.terminal = {};
832
849
  }
833
850
  this.globalSettings.terminal.showImages = show;
834
- this.save();
851
+ await this.save();
835
852
  }
836
853
 
837
854
  getNotificationOnComplete(): NotificationMethod {
838
855
  return this.settings.notifications?.onComplete ?? "auto";
839
856
  }
840
857
 
841
- setNotificationOnComplete(method: NotificationMethod): void {
858
+ async setNotificationOnComplete(method: NotificationMethod): Promise<void> {
842
859
  if (!this.globalSettings.notifications) {
843
860
  this.globalSettings.notifications = {};
844
861
  }
845
862
  this.globalSettings.notifications.onComplete = method;
846
- this.save();
863
+ await this.save();
847
864
  }
848
865
 
849
866
  getImageAutoResize(): boolean {
850
867
  return this.settings.images?.autoResize ?? true;
851
868
  }
852
869
 
853
- setImageAutoResize(enabled: boolean): void {
870
+ async setImageAutoResize(enabled: boolean): Promise<void> {
854
871
  if (!this.globalSettings.images) {
855
872
  this.globalSettings.images = {};
856
873
  }
857
874
  this.globalSettings.images.autoResize = enabled;
858
- this.save();
875
+ await this.save();
859
876
  }
860
877
 
861
878
  getBlockImages(): boolean {
862
879
  return this.settings.images?.blockImages ?? false;
863
880
  }
864
881
 
865
- setBlockImages(blocked: boolean): void {
882
+ async setBlockImages(blocked: boolean): Promise<void> {
866
883
  if (!this.globalSettings.images) {
867
884
  this.globalSettings.images = {};
868
885
  }
869
886
  this.globalSettings.images.blockImages = blocked;
870
- this.save();
887
+ await this.save();
871
888
  }
872
889
 
873
890
  getEnabledModels(): string[] | undefined {
@@ -885,52 +902,52 @@ export class SettingsManager {
885
902
  };
886
903
  }
887
904
 
888
- setExaEnabled(enabled: boolean): void {
905
+ async setExaEnabled(enabled: boolean): Promise<void> {
889
906
  if (!this.globalSettings.exa) {
890
907
  this.globalSettings.exa = {};
891
908
  }
892
909
  this.globalSettings.exa.enabled = enabled;
893
- this.save();
910
+ await this.save();
894
911
  }
895
912
 
896
- setExaSearchEnabled(enabled: boolean): void {
913
+ async setExaSearchEnabled(enabled: boolean): Promise<void> {
897
914
  if (!this.globalSettings.exa) {
898
915
  this.globalSettings.exa = {};
899
916
  }
900
917
  this.globalSettings.exa.enableSearch = enabled;
901
- this.save();
918
+ await this.save();
902
919
  }
903
920
 
904
- setExaLinkedinEnabled(enabled: boolean): void {
921
+ async setExaLinkedinEnabled(enabled: boolean): Promise<void> {
905
922
  if (!this.globalSettings.exa) {
906
923
  this.globalSettings.exa = {};
907
924
  }
908
925
  this.globalSettings.exa.enableLinkedin = enabled;
909
- this.save();
926
+ await this.save();
910
927
  }
911
928
 
912
- setExaCompanyEnabled(enabled: boolean): void {
929
+ async setExaCompanyEnabled(enabled: boolean): Promise<void> {
913
930
  if (!this.globalSettings.exa) {
914
931
  this.globalSettings.exa = {};
915
932
  }
916
933
  this.globalSettings.exa.enableCompany = enabled;
917
- this.save();
934
+ await this.save();
918
935
  }
919
936
 
920
- setExaResearcherEnabled(enabled: boolean): void {
937
+ async setExaResearcherEnabled(enabled: boolean): Promise<void> {
921
938
  if (!this.globalSettings.exa) {
922
939
  this.globalSettings.exa = {};
923
940
  }
924
941
  this.globalSettings.exa.enableResearcher = enabled;
925
- this.save();
942
+ await this.save();
926
943
  }
927
944
 
928
- setExaWebsetsEnabled(enabled: boolean): void {
945
+ async setExaWebsetsEnabled(enabled: boolean): Promise<void> {
929
946
  if (!this.globalSettings.exa) {
930
947
  this.globalSettings.exa = {};
931
948
  }
932
949
  this.globalSettings.exa.enableWebsets = enabled;
933
- this.save();
950
+ await this.save();
934
951
  }
935
952
 
936
953
  // Provider settings
@@ -938,24 +955,24 @@ export class SettingsManager {
938
955
  return this.settings.providers?.webSearch ?? "auto";
939
956
  }
940
957
 
941
- setWebSearchProvider(provider: WebSearchProviderOption): void {
958
+ async setWebSearchProvider(provider: WebSearchProviderOption): Promise<void> {
942
959
  if (!this.globalSettings.providers) {
943
960
  this.globalSettings.providers = {};
944
961
  }
945
962
  this.globalSettings.providers.webSearch = provider;
946
- this.save();
963
+ await this.save();
947
964
  }
948
965
 
949
966
  getImageProvider(): ImageProviderOption {
950
967
  return this.settings.providers?.image ?? "auto";
951
968
  }
952
969
 
953
- setImageProvider(provider: ImageProviderOption): void {
970
+ async setImageProvider(provider: ImageProviderOption): Promise<void> {
954
971
  if (!this.globalSettings.providers) {
955
972
  this.globalSettings.providers = {};
956
973
  }
957
974
  this.globalSettings.providers.image = provider;
958
- this.save();
975
+ await this.save();
959
976
  }
960
977
 
961
978
  getBashInterceptorEnabled(): boolean {
@@ -970,124 +987,124 @@ export class SettingsManager {
970
987
  return [...(this.settings.bashInterceptor?.patterns ?? DEFAULT_BASH_INTERCEPTOR_RULES)];
971
988
  }
972
989
 
973
- setBashInterceptorEnabled(enabled: boolean): void {
990
+ async setBashInterceptorEnabled(enabled: boolean): Promise<void> {
974
991
  if (!this.globalSettings.bashInterceptor) {
975
992
  this.globalSettings.bashInterceptor = {};
976
993
  }
977
994
  this.globalSettings.bashInterceptor.enabled = enabled;
978
- this.save();
995
+ await this.save();
979
996
  }
980
997
 
981
998
  getGitToolEnabled(): boolean {
982
999
  return this.settings.git?.enabled ?? false;
983
1000
  }
984
1001
 
985
- setGitToolEnabled(enabled: boolean): void {
1002
+ async setGitToolEnabled(enabled: boolean): Promise<void> {
986
1003
  if (!this.globalSettings.git) {
987
1004
  this.globalSettings.git = {};
988
1005
  }
989
1006
  this.globalSettings.git.enabled = enabled;
990
- this.save();
1007
+ await this.save();
991
1008
  }
992
1009
 
993
1010
  getMCPProjectConfigEnabled(): boolean {
994
1011
  return this.settings.mcp?.enableProjectConfig ?? true;
995
1012
  }
996
1013
 
997
- setMCPProjectConfigEnabled(enabled: boolean): void {
1014
+ async setMCPProjectConfigEnabled(enabled: boolean): Promise<void> {
998
1015
  if (!this.globalSettings.mcp) {
999
1016
  this.globalSettings.mcp = {};
1000
1017
  }
1001
1018
  this.globalSettings.mcp.enableProjectConfig = enabled;
1002
- this.save();
1019
+ await this.save();
1003
1020
  }
1004
1021
 
1005
1022
  getLspFormatOnWrite(): boolean {
1006
1023
  return this.settings.lsp?.formatOnWrite ?? false;
1007
1024
  }
1008
1025
 
1009
- setLspFormatOnWrite(enabled: boolean): void {
1026
+ async setLspFormatOnWrite(enabled: boolean): Promise<void> {
1010
1027
  if (!this.globalSettings.lsp) {
1011
1028
  this.globalSettings.lsp = {};
1012
1029
  }
1013
1030
  this.globalSettings.lsp.formatOnWrite = enabled;
1014
- this.save();
1031
+ await this.save();
1015
1032
  }
1016
1033
 
1017
1034
  getLspDiagnosticsOnWrite(): boolean {
1018
1035
  return this.settings.lsp?.diagnosticsOnWrite ?? true;
1019
1036
  }
1020
1037
 
1021
- setLspDiagnosticsOnWrite(enabled: boolean): void {
1038
+ async setLspDiagnosticsOnWrite(enabled: boolean): Promise<void> {
1022
1039
  if (!this.globalSettings.lsp) {
1023
1040
  this.globalSettings.lsp = {};
1024
1041
  }
1025
1042
  this.globalSettings.lsp.diagnosticsOnWrite = enabled;
1026
- this.save();
1043
+ await this.save();
1027
1044
  }
1028
1045
 
1029
1046
  getLspDiagnosticsOnEdit(): boolean {
1030
1047
  return this.settings.lsp?.diagnosticsOnEdit ?? false;
1031
1048
  }
1032
1049
 
1033
- setLspDiagnosticsOnEdit(enabled: boolean): void {
1050
+ async setLspDiagnosticsOnEdit(enabled: boolean): Promise<void> {
1034
1051
  if (!this.globalSettings.lsp) {
1035
1052
  this.globalSettings.lsp = {};
1036
1053
  }
1037
1054
  this.globalSettings.lsp.diagnosticsOnEdit = enabled;
1038
- this.save();
1055
+ await this.save();
1039
1056
  }
1040
1057
 
1041
1058
  getEditFuzzyMatch(): boolean {
1042
1059
  return this.settings.edit?.fuzzyMatch ?? true;
1043
1060
  }
1044
1061
 
1045
- setEditFuzzyMatch(enabled: boolean): void {
1062
+ async setEditFuzzyMatch(enabled: boolean): Promise<void> {
1046
1063
  if (!this.globalSettings.edit) {
1047
1064
  this.globalSettings.edit = {};
1048
1065
  }
1049
1066
  this.globalSettings.edit.fuzzyMatch = enabled;
1050
- this.save();
1067
+ await this.save();
1051
1068
  }
1052
1069
 
1053
1070
  getDisabledProviders(): string[] {
1054
1071
  return [...(this.settings.disabledProviders ?? [])];
1055
1072
  }
1056
1073
 
1057
- setDisabledProviders(providerIds: string[]): void {
1074
+ async setDisabledProviders(providerIds: string[]): Promise<void> {
1058
1075
  this.globalSettings.disabledProviders = providerIds;
1059
- this.save();
1076
+ await this.save();
1060
1077
  }
1061
1078
 
1062
1079
  getDisabledExtensions(): string[] {
1063
1080
  return [...(this.settings.disabledExtensions ?? [])];
1064
1081
  }
1065
1082
 
1066
- setDisabledExtensions(extensionIds: string[]): void {
1083
+ async setDisabledExtensions(extensionIds: string[]): Promise<void> {
1067
1084
  this.globalSettings.disabledExtensions = extensionIds;
1068
- this.save();
1085
+ await this.save();
1069
1086
  }
1070
1087
 
1071
1088
  isExtensionEnabled(extensionId: string): boolean {
1072
1089
  return !(this.settings.disabledExtensions ?? []).includes(extensionId);
1073
1090
  }
1074
1091
 
1075
- enableExtension(extensionId: string): void {
1092
+ async enableExtension(extensionId: string): Promise<void> {
1076
1093
  const disabled = this.globalSettings.disabledExtensions ?? [];
1077
1094
  const index = disabled.indexOf(extensionId);
1078
1095
  if (index !== -1) {
1079
1096
  disabled.splice(index, 1);
1080
1097
  this.globalSettings.disabledExtensions = disabled;
1081
- this.save();
1098
+ await this.save();
1082
1099
  }
1083
1100
  }
1084
1101
 
1085
- disableExtension(extensionId: string): void {
1102
+ async disableExtension(extensionId: string): Promise<void> {
1086
1103
  const disabled = this.globalSettings.disabledExtensions ?? [];
1087
1104
  if (!disabled.includes(extensionId)) {
1088
1105
  disabled.push(extensionId);
1089
1106
  this.globalSettings.disabledExtensions = disabled;
1090
- this.save();
1107
+ await this.save();
1091
1108
  }
1092
1109
  }
1093
1110
 
@@ -1095,57 +1112,57 @@ export class SettingsManager {
1095
1112
  return this.settings.ttsr ?? {};
1096
1113
  }
1097
1114
 
1098
- setTtsrSettings(settings: TtsrSettings): void {
1115
+ async setTtsrSettings(settings: TtsrSettings): Promise<void> {
1099
1116
  this.globalSettings.ttsr = { ...this.globalSettings.ttsr, ...settings };
1100
- this.save();
1117
+ await this.save();
1101
1118
  }
1102
1119
 
1103
1120
  getTtsrEnabled(): boolean {
1104
1121
  return this.settings.ttsr?.enabled ?? true;
1105
1122
  }
1106
1123
 
1107
- setTtsrEnabled(enabled: boolean): void {
1124
+ async setTtsrEnabled(enabled: boolean): Promise<void> {
1108
1125
  if (!this.globalSettings.ttsr) {
1109
1126
  this.globalSettings.ttsr = {};
1110
1127
  }
1111
1128
  this.globalSettings.ttsr.enabled = enabled;
1112
- this.save();
1129
+ await this.save();
1113
1130
  }
1114
1131
 
1115
1132
  getTtsrContextMode(): "keep" | "discard" {
1116
1133
  return this.settings.ttsr?.contextMode ?? "discard";
1117
1134
  }
1118
1135
 
1119
- setTtsrContextMode(mode: "keep" | "discard"): void {
1136
+ async setTtsrContextMode(mode: "keep" | "discard"): Promise<void> {
1120
1137
  if (!this.globalSettings.ttsr) {
1121
1138
  this.globalSettings.ttsr = {};
1122
1139
  }
1123
1140
  this.globalSettings.ttsr.contextMode = mode;
1124
- this.save();
1141
+ await this.save();
1125
1142
  }
1126
1143
 
1127
1144
  getTtsrRepeatMode(): "once" | "after-gap" {
1128
1145
  return this.settings.ttsr?.repeatMode ?? "once";
1129
1146
  }
1130
1147
 
1131
- setTtsrRepeatMode(mode: "once" | "after-gap"): void {
1148
+ async setTtsrRepeatMode(mode: "once" | "after-gap"): Promise<void> {
1132
1149
  if (!this.globalSettings.ttsr) {
1133
1150
  this.globalSettings.ttsr = {};
1134
1151
  }
1135
1152
  this.globalSettings.ttsr.repeatMode = mode;
1136
- this.save();
1153
+ await this.save();
1137
1154
  }
1138
1155
 
1139
1156
  getTtsrRepeatGap(): number {
1140
1157
  return this.settings.ttsr?.repeatGap ?? 10;
1141
1158
  }
1142
1159
 
1143
- setTtsrRepeatGap(gap: number): void {
1160
+ async setTtsrRepeatGap(gap: number): Promise<void> {
1144
1161
  if (!this.globalSettings.ttsr) {
1145
1162
  this.globalSettings.ttsr = {};
1146
1163
  }
1147
1164
  this.globalSettings.ttsr.repeatGap = gap;
1148
- this.save();
1165
+ await this.save();
1149
1166
  }
1150
1167
 
1151
1168
  getVoiceSettings(): Required<VoiceSettings> {
@@ -1159,21 +1176,21 @@ export class SettingsManager {
1159
1176
  };
1160
1177
  }
1161
1178
 
1162
- setVoiceSettings(settings: VoiceSettings): void {
1179
+ async setVoiceSettings(settings: VoiceSettings): Promise<void> {
1163
1180
  this.globalSettings.voice = { ...this.globalSettings.voice, ...settings };
1164
- this.save();
1181
+ await this.save();
1165
1182
  }
1166
1183
 
1167
1184
  getVoiceEnabled(): boolean {
1168
1185
  return this.settings.voice?.enabled ?? false;
1169
1186
  }
1170
1187
 
1171
- setVoiceEnabled(enabled: boolean): void {
1188
+ async setVoiceEnabled(enabled: boolean): Promise<void> {
1172
1189
  if (!this.globalSettings.voice) {
1173
1190
  this.globalSettings.voice = {};
1174
1191
  }
1175
1192
  this.globalSettings.voice.enabled = enabled;
1176
- this.save();
1193
+ await this.save();
1177
1194
  }
1178
1195
 
1179
1196
  // ═══════════════════════════════════════════════════════════════════════════
@@ -1188,7 +1205,7 @@ export class SettingsManager {
1188
1205
  return this.settings.statusLine?.preset ?? "default";
1189
1206
  }
1190
1207
 
1191
- setStatusLinePreset(preset: StatusLinePreset): void {
1208
+ async setStatusLinePreset(preset: StatusLinePreset): Promise<void> {
1192
1209
  if (!this.globalSettings.statusLine) {
1193
1210
  this.globalSettings.statusLine = {};
1194
1211
  }
@@ -1198,26 +1215,26 @@ export class SettingsManager {
1198
1215
  delete this.globalSettings.statusLine.segmentOptions;
1199
1216
  }
1200
1217
  this.globalSettings.statusLine.preset = preset;
1201
- this.save();
1218
+ await this.save();
1202
1219
  }
1203
1220
 
1204
1221
  getStatusLineSeparator(): StatusLineSeparatorStyle {
1205
1222
  return this.settings.statusLine?.separator ?? "powerline-thin";
1206
1223
  }
1207
1224
 
1208
- setStatusLineSeparator(separator: StatusLineSeparatorStyle): void {
1225
+ async setStatusLineSeparator(separator: StatusLineSeparatorStyle): Promise<void> {
1209
1226
  if (!this.globalSettings.statusLine) {
1210
1227
  this.globalSettings.statusLine = {};
1211
1228
  }
1212
1229
  this.globalSettings.statusLine.separator = separator;
1213
- this.save();
1230
+ await this.save();
1214
1231
  }
1215
1232
 
1216
1233
  getStatusLineLeftSegments(): StatusLineSegmentId[] {
1217
1234
  return [...(this.settings.statusLine?.leftSegments ?? [])];
1218
1235
  }
1219
1236
 
1220
- setStatusLineLeftSegments(segments: StatusLineSegmentId[]): void {
1237
+ async setStatusLineLeftSegments(segments: StatusLineSegmentId[]): Promise<void> {
1221
1238
  if (!this.globalSettings.statusLine) {
1222
1239
  this.globalSettings.statusLine = {};
1223
1240
  }
@@ -1226,14 +1243,14 @@ export class SettingsManager {
1226
1243
  if (this.globalSettings.statusLine.preset !== "custom") {
1227
1244
  this.globalSettings.statusLine.preset = "custom";
1228
1245
  }
1229
- this.save();
1246
+ await this.save();
1230
1247
  }
1231
1248
 
1232
1249
  getStatusLineRightSegments(): StatusLineSegmentId[] {
1233
1250
  return [...(this.settings.statusLine?.rightSegments ?? [])];
1234
1251
  }
1235
1252
 
1236
- setStatusLineRightSegments(segments: StatusLineSegmentId[]): void {
1253
+ async setStatusLineRightSegments(segments: StatusLineSegmentId[]): Promise<void> {
1237
1254
  if (!this.globalSettings.statusLine) {
1238
1255
  this.globalSettings.statusLine = {};
1239
1256
  }
@@ -1242,18 +1259,18 @@ export class SettingsManager {
1242
1259
  if (this.globalSettings.statusLine.preset !== "custom") {
1243
1260
  this.globalSettings.statusLine.preset = "custom";
1244
1261
  }
1245
- this.save();
1262
+ await this.save();
1246
1263
  }
1247
1264
 
1248
1265
  getStatusLineSegmentOptions(): StatusLineSegmentOptions {
1249
1266
  return { ...this.settings.statusLine?.segmentOptions };
1250
1267
  }
1251
1268
 
1252
- setStatusLineSegmentOption<K extends keyof StatusLineSegmentOptions>(
1269
+ async setStatusLineSegmentOption<K extends keyof StatusLineSegmentOptions>(
1253
1270
  segment: K,
1254
1271
  option: keyof NonNullable<StatusLineSegmentOptions[K]>,
1255
1272
  value: boolean | number | string,
1256
- ): void {
1273
+ ): Promise<void> {
1257
1274
  if (!this.globalSettings.statusLine) {
1258
1275
  this.globalSettings.statusLine = {};
1259
1276
  }
@@ -1264,13 +1281,13 @@ export class SettingsManager {
1264
1281
  this.globalSettings.statusLine.segmentOptions[segment] = {} as NonNullable<StatusLineSegmentOptions[K]>;
1265
1282
  }
1266
1283
  (this.globalSettings.statusLine.segmentOptions[segment] as Record<string, unknown>)[option as string] = value;
1267
- this.save();
1284
+ await this.save();
1268
1285
  }
1269
1286
 
1270
- clearStatusLineSegmentOption<K extends keyof StatusLineSegmentOptions>(
1287
+ async clearStatusLineSegmentOption<K extends keyof StatusLineSegmentOptions>(
1271
1288
  segment: K,
1272
1289
  option: keyof NonNullable<StatusLineSegmentOptions[K]>,
1273
- ): void {
1290
+ ): Promise<void> {
1274
1291
  const segmentOptions = this.globalSettings.statusLine?.segmentOptions;
1275
1292
  if (!segmentOptions || !segmentOptions[segment]) {
1276
1293
  return;
@@ -1282,28 +1299,28 @@ export class SettingsManager {
1282
1299
  if (Object.keys(segmentOptions).length === 0) {
1283
1300
  delete this.globalSettings.statusLine?.segmentOptions;
1284
1301
  }
1285
- this.save();
1302
+ await this.save();
1286
1303
  }
1287
1304
 
1288
1305
  getStatusLineShowHookStatus(): boolean {
1289
1306
  return this.settings.statusLine?.showHookStatus ?? true;
1290
1307
  }
1291
1308
 
1292
- setStatusLineShowHookStatus(show: boolean): void {
1309
+ async setStatusLineShowHookStatus(show: boolean): Promise<void> {
1293
1310
  if (!this.globalSettings.statusLine) {
1294
1311
  this.globalSettings.statusLine = {};
1295
1312
  }
1296
1313
  this.globalSettings.statusLine.showHookStatus = show;
1297
- this.save();
1314
+ await this.save();
1298
1315
  }
1299
1316
 
1300
1317
  getDoubleEscapeAction(): "branch" | "tree" {
1301
1318
  return this.settings.doubleEscapeAction ?? "tree";
1302
1319
  }
1303
1320
 
1304
- setDoubleEscapeAction(action: "branch" | "tree"): void {
1321
+ async setDoubleEscapeAction(action: "branch" | "tree"): Promise<void> {
1305
1322
  this.globalSettings.doubleEscapeAction = action;
1306
- this.save();
1323
+ await this.save();
1307
1324
  }
1308
1325
 
1309
1326
  /**
@@ -1317,37 +1334,37 @@ export class SettingsManager {
1317
1334
  * Set environment variables in settings (not process.env)
1318
1335
  * This will be applied on next startup or reload
1319
1336
  */
1320
- setEnvironmentVariables(envVars: Record<string, string>): void {
1337
+ async setEnvironmentVariables(envVars: Record<string, string>): Promise<void> {
1321
1338
  this.globalSettings.env = { ...envVars };
1322
- this.save();
1339
+ await this.save();
1323
1340
  }
1324
1341
 
1325
1342
  /**
1326
1343
  * Clear all environment variables from settings
1327
1344
  */
1328
- clearEnvironmentVariables(): void {
1345
+ async clearEnvironmentVariables(): Promise<void> {
1329
1346
  delete this.globalSettings.env;
1330
- this.save();
1347
+ await this.save();
1331
1348
  }
1332
1349
 
1333
1350
  /**
1334
1351
  * Set a single environment variable in settings
1335
1352
  */
1336
- setEnvironmentVariable(key: string, value: string): void {
1353
+ async setEnvironmentVariable(key: string, value: string): Promise<void> {
1337
1354
  if (!this.globalSettings.env) {
1338
1355
  this.globalSettings.env = {};
1339
1356
  }
1340
1357
  this.globalSettings.env[key] = value;
1341
- this.save();
1358
+ await this.save();
1342
1359
  }
1343
1360
 
1344
1361
  /**
1345
1362
  * Remove a single environment variable from settings
1346
1363
  */
1347
- removeEnvironmentVariable(key: string): void {
1364
+ async removeEnvironmentVariable(key: string): Promise<void> {
1348
1365
  if (this.globalSettings.env) {
1349
1366
  delete this.globalSettings.env[key];
1350
- this.save();
1367
+ await this.save();
1351
1368
  }
1352
1369
  }
1353
1370
  }