@oh-my-pi/pi-coding-agent 8.13.0 → 9.0.0
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.
- package/CHANGELOG.md +6 -0
- package/package.json +7 -7
- package/src/config/settings-manager.ts +121 -2
- package/src/session/agent-session.ts +13 -10
- package/src/tools/path-utils.ts +33 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [9.0.0] - 2026-01-29
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- External edits to `config.yml` are now preserved when omp reloads or saves unrelated settings. Previously, editing config.yml directly (e.g., removing a package from `packages` array) would be silently reverted on next omp startup when automatic setters like `setLastChangelogVersion()` triggered a save. ([#1046](https://github.com/badlogic/pi-mono/pull/1046) by [@nicobailonMD](https://github.com/nicobailonMD))
|
|
10
|
+
|
|
5
11
|
## [8.13.0] - 2026-01-29
|
|
6
12
|
|
|
7
13
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -79,12 +79,12 @@
|
|
|
79
79
|
"test": "bun test"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@oh-my-pi/omp-stats": "
|
|
83
|
-
"@oh-my-pi/pi-agent-core": "
|
|
84
|
-
"@oh-my-pi/pi-ai": "
|
|
85
|
-
"@oh-my-pi/pi-natives": "
|
|
86
|
-
"@oh-my-pi/pi-tui": "
|
|
87
|
-
"@oh-my-pi/pi-utils": "
|
|
82
|
+
"@oh-my-pi/omp-stats": "9.0.0",
|
|
83
|
+
"@oh-my-pi/pi-agent-core": "9.0.0",
|
|
84
|
+
"@oh-my-pi/pi-ai": "9.0.0",
|
|
85
|
+
"@oh-my-pi/pi-natives": "9.0.0",
|
|
86
|
+
"@oh-my-pi/pi-tui": "9.0.0",
|
|
87
|
+
"@oh-my-pi/pi-utils": "9.0.0",
|
|
88
88
|
"@openai/agents": "^0.4.4",
|
|
89
89
|
"@sinclair/typebox": "^0.34.48",
|
|
90
90
|
"ajv": "^8.17.1",
|
|
@@ -460,6 +460,8 @@ export class SettingsManager {
|
|
|
460
460
|
private overrides: Settings;
|
|
461
461
|
private settings!: Settings;
|
|
462
462
|
private persist: boolean;
|
|
463
|
+
private modifiedFields = new Set<keyof Settings>();
|
|
464
|
+
private modifiedNestedFields = new Map<keyof Settings, Set<string>>();
|
|
463
465
|
|
|
464
466
|
static #lastInstance: SettingsManager | null = null;
|
|
465
467
|
|
|
@@ -714,6 +716,17 @@ export class SettingsManager {
|
|
|
714
716
|
this.rebuildSettings(projectSettings);
|
|
715
717
|
}
|
|
716
718
|
|
|
719
|
+
/** Mark a field as modified during this session */
|
|
720
|
+
private markModified(field: keyof Settings, nestedKey?: string): void {
|
|
721
|
+
this.modifiedFields.add(field);
|
|
722
|
+
if (nestedKey) {
|
|
723
|
+
if (!this.modifiedNestedFields.has(field)) {
|
|
724
|
+
this.modifiedNestedFields.set(field, new Set());
|
|
725
|
+
}
|
|
726
|
+
this.modifiedNestedFields.get(field)!.add(nestedKey);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
717
730
|
/**
|
|
718
731
|
* Persist current global settings to config.yml and rebuild merged settings.
|
|
719
732
|
* Uses file locking to prevent concurrent write races.
|
|
@@ -723,8 +736,32 @@ export class SettingsManager {
|
|
|
723
736
|
const configPath = this.configPath;
|
|
724
737
|
try {
|
|
725
738
|
await withFileLock(configPath, async () => {
|
|
726
|
-
|
|
727
|
-
const
|
|
739
|
+
// Re-read current file to get latest external changes
|
|
740
|
+
const currentFileSettings = await SettingsManager.loadFromYaml(configPath);
|
|
741
|
+
|
|
742
|
+
// Start with file settings as base - preserves external edits
|
|
743
|
+
const mergedSettings: Settings = { ...currentFileSettings };
|
|
744
|
+
|
|
745
|
+
// Only override with in-memory values for fields that were explicitly modified during this session
|
|
746
|
+
for (const field of this.modifiedFields) {
|
|
747
|
+
const value = this.globalSettings[field];
|
|
748
|
+
|
|
749
|
+
// Handle nested objects specially - merge at nested level to preserve unmodified nested keys
|
|
750
|
+
if (this.modifiedNestedFields.has(field) && typeof value === "object" && value !== null) {
|
|
751
|
+
const nestedModified = this.modifiedNestedFields.get(field)!;
|
|
752
|
+
const baseNested = (currentFileSettings[field] as Record<string, unknown>) ?? {};
|
|
753
|
+
const inMemoryNested = value as Record<string, unknown>;
|
|
754
|
+
const mergedNested = { ...baseNested };
|
|
755
|
+
for (const nestedKey of nestedModified) {
|
|
756
|
+
mergedNested[nestedKey] = inMemoryNested[nestedKey];
|
|
757
|
+
}
|
|
758
|
+
(mergedSettings as Record<string, unknown>)[field] = mergedNested;
|
|
759
|
+
} else {
|
|
760
|
+
// For top-level primitives and arrays, use the modified value directly
|
|
761
|
+
(mergedSettings as Record<string, unknown>)[field] = value;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
728
765
|
this.globalSettings = mergedSettings;
|
|
729
766
|
await Bun.write(configPath, YAML.stringify(this.globalSettings, null, 2));
|
|
730
767
|
});
|
|
@@ -743,6 +780,7 @@ export class SettingsManager {
|
|
|
743
780
|
|
|
744
781
|
async setLastChangelogVersion(version: string): Promise<void> {
|
|
745
782
|
this.globalSettings.lastChangelogVersion = version;
|
|
783
|
+
this.markModified("lastChangelogVersion");
|
|
746
784
|
await this.save();
|
|
747
785
|
}
|
|
748
786
|
|
|
@@ -761,6 +799,7 @@ export class SettingsManager {
|
|
|
761
799
|
this.globalSettings.modelRoles = {};
|
|
762
800
|
}
|
|
763
801
|
this.globalSettings.modelRoles[role] = model;
|
|
802
|
+
this.markModified("modelRoles", role);
|
|
764
803
|
|
|
765
804
|
if (this.overrides.modelRoles && this.overrides.modelRoles[role] !== undefined) {
|
|
766
805
|
this.overrides.modelRoles[role] = model;
|
|
@@ -782,6 +821,7 @@ export class SettingsManager {
|
|
|
782
821
|
|
|
783
822
|
async setSteeringMode(mode: "all" | "one-at-a-time"): Promise<void> {
|
|
784
823
|
this.globalSettings.steeringMode = mode;
|
|
824
|
+
this.markModified("steeringMode");
|
|
785
825
|
await this.save();
|
|
786
826
|
}
|
|
787
827
|
|
|
@@ -791,6 +831,7 @@ export class SettingsManager {
|
|
|
791
831
|
|
|
792
832
|
async setFollowUpMode(mode: "all" | "one-at-a-time"): Promise<void> {
|
|
793
833
|
this.globalSettings.followUpMode = mode;
|
|
834
|
+
this.markModified("followUpMode");
|
|
794
835
|
await this.save();
|
|
795
836
|
}
|
|
796
837
|
|
|
@@ -800,6 +841,7 @@ export class SettingsManager {
|
|
|
800
841
|
|
|
801
842
|
async setInterruptMode(mode: "immediate" | "wait"): Promise<void> {
|
|
802
843
|
this.globalSettings.interruptMode = mode;
|
|
844
|
+
this.markModified("interruptMode");
|
|
803
845
|
await this.save();
|
|
804
846
|
}
|
|
805
847
|
|
|
@@ -809,6 +851,7 @@ export class SettingsManager {
|
|
|
809
851
|
|
|
810
852
|
async setTheme(theme: string): Promise<void> {
|
|
811
853
|
this.globalSettings.theme = theme;
|
|
854
|
+
this.markModified("theme");
|
|
812
855
|
await this.save();
|
|
813
856
|
}
|
|
814
857
|
|
|
@@ -818,6 +861,7 @@ export class SettingsManager {
|
|
|
818
861
|
|
|
819
862
|
async setSymbolPreset(preset: SymbolPreset): Promise<void> {
|
|
820
863
|
this.globalSettings.symbolPreset = preset;
|
|
864
|
+
this.markModified("symbolPreset");
|
|
821
865
|
await this.save();
|
|
822
866
|
}
|
|
823
867
|
|
|
@@ -827,6 +871,7 @@ export class SettingsManager {
|
|
|
827
871
|
|
|
828
872
|
async setColorBlindMode(enabled: boolean): Promise<void> {
|
|
829
873
|
this.globalSettings.colorBlindMode = enabled;
|
|
874
|
+
this.markModified("colorBlindMode");
|
|
830
875
|
await this.save();
|
|
831
876
|
}
|
|
832
877
|
|
|
@@ -836,6 +881,7 @@ export class SettingsManager {
|
|
|
836
881
|
|
|
837
882
|
async setDefaultThinkingLevel(level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"): Promise<void> {
|
|
838
883
|
this.globalSettings.defaultThinkingLevel = level;
|
|
884
|
+
this.markModified("defaultThinkingLevel");
|
|
839
885
|
await this.save();
|
|
840
886
|
}
|
|
841
887
|
|
|
@@ -848,6 +894,7 @@ export class SettingsManager {
|
|
|
848
894
|
this.globalSettings.compaction = {};
|
|
849
895
|
}
|
|
850
896
|
this.globalSettings.compaction.enabled = enabled;
|
|
897
|
+
this.markModified("compaction", "enabled");
|
|
851
898
|
await this.save();
|
|
852
899
|
}
|
|
853
900
|
|
|
@@ -892,6 +939,7 @@ export class SettingsManager {
|
|
|
892
939
|
this.globalSettings.branchSummary = {};
|
|
893
940
|
}
|
|
894
941
|
this.globalSettings.branchSummary.enabled = enabled;
|
|
942
|
+
this.markModified("branchSummary", "enabled");
|
|
895
943
|
await this.save();
|
|
896
944
|
}
|
|
897
945
|
|
|
@@ -911,6 +959,7 @@ export class SettingsManager {
|
|
|
911
959
|
this.globalSettings.retry = {};
|
|
912
960
|
}
|
|
913
961
|
this.globalSettings.retry.enabled = enabled;
|
|
962
|
+
this.markModified("retry", "enabled");
|
|
914
963
|
await this.save();
|
|
915
964
|
}
|
|
916
965
|
|
|
@@ -942,6 +991,7 @@ export class SettingsManager {
|
|
|
942
991
|
this.globalSettings.retry = {};
|
|
943
992
|
}
|
|
944
993
|
this.globalSettings.retry.maxRetries = maxRetries;
|
|
994
|
+
this.markModified("retry", "maxRetries");
|
|
945
995
|
await this.save();
|
|
946
996
|
}
|
|
947
997
|
|
|
@@ -954,6 +1004,7 @@ export class SettingsManager {
|
|
|
954
1004
|
this.globalSettings.retry = {};
|
|
955
1005
|
}
|
|
956
1006
|
this.globalSettings.retry.baseDelayMs = baseDelayMs;
|
|
1007
|
+
this.markModified("retry", "baseDelayMs");
|
|
957
1008
|
await this.save();
|
|
958
1009
|
}
|
|
959
1010
|
|
|
@@ -973,6 +1024,7 @@ export class SettingsManager {
|
|
|
973
1024
|
this.globalSettings.todoCompletion = {};
|
|
974
1025
|
}
|
|
975
1026
|
this.globalSettings.todoCompletion.enabled = enabled;
|
|
1027
|
+
this.markModified("todoCompletion", "enabled");
|
|
976
1028
|
await this.save();
|
|
977
1029
|
}
|
|
978
1030
|
|
|
@@ -985,6 +1037,7 @@ export class SettingsManager {
|
|
|
985
1037
|
this.globalSettings.todoCompletion = {};
|
|
986
1038
|
}
|
|
987
1039
|
this.globalSettings.todoCompletion.maxReminders = maxReminders;
|
|
1040
|
+
this.markModified("todoCompletion", "maxReminders");
|
|
988
1041
|
await this.save();
|
|
989
1042
|
}
|
|
990
1043
|
|
|
@@ -998,6 +1051,7 @@ export class SettingsManager {
|
|
|
998
1051
|
|
|
999
1052
|
async setHideThinkingBlock(hide: boolean): Promise<void> {
|
|
1000
1053
|
this.globalSettings.hideThinkingBlock = hide;
|
|
1054
|
+
this.markModified("hideThinkingBlock");
|
|
1001
1055
|
await this.save();
|
|
1002
1056
|
}
|
|
1003
1057
|
|
|
@@ -1007,6 +1061,7 @@ export class SettingsManager {
|
|
|
1007
1061
|
|
|
1008
1062
|
async setShellPath(path: string | undefined): Promise<void> {
|
|
1009
1063
|
this.globalSettings.shellPath = path;
|
|
1064
|
+
this.markModified("shellPath");
|
|
1010
1065
|
await this.save();
|
|
1011
1066
|
}
|
|
1012
1067
|
|
|
@@ -1016,6 +1071,7 @@ export class SettingsManager {
|
|
|
1016
1071
|
|
|
1017
1072
|
async setCollapseChangelog(collapse: boolean): Promise<void> {
|
|
1018
1073
|
this.globalSettings.collapseChangelog = collapse;
|
|
1074
|
+
this.markModified("collapseChangelog");
|
|
1019
1075
|
await this.save();
|
|
1020
1076
|
}
|
|
1021
1077
|
|
|
@@ -1028,6 +1084,7 @@ export class SettingsManager {
|
|
|
1028
1084
|
this.globalSettings.startup = {};
|
|
1029
1085
|
}
|
|
1030
1086
|
this.globalSettings.startup.quiet = quiet;
|
|
1087
|
+
this.markModified("startup", "quiet");
|
|
1031
1088
|
await this.save();
|
|
1032
1089
|
}
|
|
1033
1090
|
|
|
@@ -1037,6 +1094,7 @@ export class SettingsManager {
|
|
|
1037
1094
|
|
|
1038
1095
|
async setExtensionPaths(paths: string[]): Promise<void> {
|
|
1039
1096
|
this.globalSettings.extensions = paths;
|
|
1097
|
+
this.markModified("extensions");
|
|
1040
1098
|
await this.save();
|
|
1041
1099
|
}
|
|
1042
1100
|
|
|
@@ -1049,6 +1107,7 @@ export class SettingsManager {
|
|
|
1049
1107
|
this.globalSettings.skills = {};
|
|
1050
1108
|
}
|
|
1051
1109
|
this.globalSettings.skills.enabled = enabled;
|
|
1110
|
+
this.markModified("skills", "enabled");
|
|
1052
1111
|
await this.save();
|
|
1053
1112
|
}
|
|
1054
1113
|
|
|
@@ -1076,6 +1135,7 @@ export class SettingsManager {
|
|
|
1076
1135
|
this.globalSettings.skills = {};
|
|
1077
1136
|
}
|
|
1078
1137
|
this.globalSettings.skills.enableSkillCommands = enabled;
|
|
1138
|
+
this.markModified("skills", "enableSkillCommands");
|
|
1079
1139
|
await this.save();
|
|
1080
1140
|
}
|
|
1081
1141
|
|
|
@@ -1095,6 +1155,7 @@ export class SettingsManager {
|
|
|
1095
1155
|
this.globalSettings.commands = {};
|
|
1096
1156
|
}
|
|
1097
1157
|
this.globalSettings.commands.enableClaudeUser = enabled;
|
|
1158
|
+
this.markModified("commands", "enableClaudeUser");
|
|
1098
1159
|
await this.save();
|
|
1099
1160
|
}
|
|
1100
1161
|
|
|
@@ -1107,6 +1168,7 @@ export class SettingsManager {
|
|
|
1107
1168
|
this.globalSettings.commands = {};
|
|
1108
1169
|
}
|
|
1109
1170
|
this.globalSettings.commands.enableClaudeProject = enabled;
|
|
1171
|
+
this.markModified("commands", "enableClaudeProject");
|
|
1110
1172
|
await this.save();
|
|
1111
1173
|
}
|
|
1112
1174
|
|
|
@@ -1119,6 +1181,7 @@ export class SettingsManager {
|
|
|
1119
1181
|
this.globalSettings.terminal = {};
|
|
1120
1182
|
}
|
|
1121
1183
|
this.globalSettings.terminal.showImages = show;
|
|
1184
|
+
this.markModified("terminal", "showImages");
|
|
1122
1185
|
await this.save();
|
|
1123
1186
|
}
|
|
1124
1187
|
|
|
@@ -1131,6 +1194,7 @@ export class SettingsManager {
|
|
|
1131
1194
|
this.globalSettings.notifications = {};
|
|
1132
1195
|
}
|
|
1133
1196
|
this.globalSettings.notifications.onComplete = method;
|
|
1197
|
+
this.markModified("notifications", "onComplete");
|
|
1134
1198
|
await this.save();
|
|
1135
1199
|
}
|
|
1136
1200
|
|
|
@@ -1146,6 +1210,7 @@ export class SettingsManager {
|
|
|
1146
1210
|
this.globalSettings.ask = {};
|
|
1147
1211
|
}
|
|
1148
1212
|
this.globalSettings.ask.timeout = seconds;
|
|
1213
|
+
this.markModified("ask", "timeout");
|
|
1149
1214
|
await this.save();
|
|
1150
1215
|
}
|
|
1151
1216
|
|
|
@@ -1158,6 +1223,7 @@ export class SettingsManager {
|
|
|
1158
1223
|
this.globalSettings.ask = {};
|
|
1159
1224
|
}
|
|
1160
1225
|
this.globalSettings.ask.notification = method;
|
|
1226
|
+
this.markModified("ask", "notification");
|
|
1161
1227
|
await this.save();
|
|
1162
1228
|
}
|
|
1163
1229
|
|
|
@@ -1170,6 +1236,7 @@ export class SettingsManager {
|
|
|
1170
1236
|
this.globalSettings.images = {};
|
|
1171
1237
|
}
|
|
1172
1238
|
this.globalSettings.images.autoResize = enabled;
|
|
1239
|
+
this.markModified("images", "autoResize");
|
|
1173
1240
|
await this.save();
|
|
1174
1241
|
}
|
|
1175
1242
|
|
|
@@ -1182,6 +1249,7 @@ export class SettingsManager {
|
|
|
1182
1249
|
this.globalSettings.images = {};
|
|
1183
1250
|
}
|
|
1184
1251
|
this.globalSettings.images.blockImages = blocked;
|
|
1252
|
+
this.markModified("images", "blockImages");
|
|
1185
1253
|
await this.save();
|
|
1186
1254
|
}
|
|
1187
1255
|
|
|
@@ -1205,6 +1273,7 @@ export class SettingsManager {
|
|
|
1205
1273
|
this.globalSettings.exa = {};
|
|
1206
1274
|
}
|
|
1207
1275
|
this.globalSettings.exa.enabled = enabled;
|
|
1276
|
+
this.markModified("exa", "enabled");
|
|
1208
1277
|
await this.save();
|
|
1209
1278
|
}
|
|
1210
1279
|
|
|
@@ -1213,6 +1282,7 @@ export class SettingsManager {
|
|
|
1213
1282
|
this.globalSettings.exa = {};
|
|
1214
1283
|
}
|
|
1215
1284
|
this.globalSettings.exa.enableSearch = enabled;
|
|
1285
|
+
this.markModified("exa", "enableSearch");
|
|
1216
1286
|
await this.save();
|
|
1217
1287
|
}
|
|
1218
1288
|
|
|
@@ -1221,6 +1291,7 @@ export class SettingsManager {
|
|
|
1221
1291
|
this.globalSettings.exa = {};
|
|
1222
1292
|
}
|
|
1223
1293
|
this.globalSettings.exa.enableLinkedin = enabled;
|
|
1294
|
+
this.markModified("exa", "enableLinkedin");
|
|
1224
1295
|
await this.save();
|
|
1225
1296
|
}
|
|
1226
1297
|
|
|
@@ -1229,6 +1300,7 @@ export class SettingsManager {
|
|
|
1229
1300
|
this.globalSettings.exa = {};
|
|
1230
1301
|
}
|
|
1231
1302
|
this.globalSettings.exa.enableCompany = enabled;
|
|
1303
|
+
this.markModified("exa", "enableCompany");
|
|
1232
1304
|
await this.save();
|
|
1233
1305
|
}
|
|
1234
1306
|
|
|
@@ -1237,6 +1309,7 @@ export class SettingsManager {
|
|
|
1237
1309
|
this.globalSettings.exa = {};
|
|
1238
1310
|
}
|
|
1239
1311
|
this.globalSettings.exa.enableResearcher = enabled;
|
|
1312
|
+
this.markModified("exa", "enableResearcher");
|
|
1240
1313
|
await this.save();
|
|
1241
1314
|
}
|
|
1242
1315
|
|
|
@@ -1245,6 +1318,7 @@ export class SettingsManager {
|
|
|
1245
1318
|
this.globalSettings.exa = {};
|
|
1246
1319
|
}
|
|
1247
1320
|
this.globalSettings.exa.enableWebsets = enabled;
|
|
1321
|
+
this.markModified("exa", "enableWebsets");
|
|
1248
1322
|
await this.save();
|
|
1249
1323
|
}
|
|
1250
1324
|
|
|
@@ -1258,6 +1332,7 @@ export class SettingsManager {
|
|
|
1258
1332
|
this.globalSettings.providers = {};
|
|
1259
1333
|
}
|
|
1260
1334
|
this.globalSettings.providers.webSearch = provider;
|
|
1335
|
+
this.markModified("providers", "webSearch");
|
|
1261
1336
|
await this.save();
|
|
1262
1337
|
}
|
|
1263
1338
|
|
|
@@ -1270,6 +1345,7 @@ export class SettingsManager {
|
|
|
1270
1345
|
this.globalSettings.providers = {};
|
|
1271
1346
|
}
|
|
1272
1347
|
this.globalSettings.providers.image = provider;
|
|
1348
|
+
this.markModified("providers", "image");
|
|
1273
1349
|
await this.save();
|
|
1274
1350
|
}
|
|
1275
1351
|
|
|
@@ -1290,6 +1366,7 @@ export class SettingsManager {
|
|
|
1290
1366
|
this.globalSettings.bashInterceptor = {};
|
|
1291
1367
|
}
|
|
1292
1368
|
this.globalSettings.bashInterceptor.enabled = enabled;
|
|
1369
|
+
this.markModified("bashInterceptor", "enabled");
|
|
1293
1370
|
await this.save();
|
|
1294
1371
|
}
|
|
1295
1372
|
|
|
@@ -1298,6 +1375,7 @@ export class SettingsManager {
|
|
|
1298
1375
|
this.globalSettings.bashInterceptor = {};
|
|
1299
1376
|
}
|
|
1300
1377
|
this.globalSettings.bashInterceptor.simpleLs = enabled;
|
|
1378
|
+
this.markModified("bashInterceptor", "simpleLs");
|
|
1301
1379
|
await this.save();
|
|
1302
1380
|
}
|
|
1303
1381
|
|
|
@@ -1310,6 +1388,7 @@ export class SettingsManager {
|
|
|
1310
1388
|
this.globalSettings.python = {};
|
|
1311
1389
|
}
|
|
1312
1390
|
this.globalSettings.python.toolMode = mode;
|
|
1391
|
+
this.markModified("python", "toolMode");
|
|
1313
1392
|
await this.save();
|
|
1314
1393
|
}
|
|
1315
1394
|
|
|
@@ -1322,6 +1401,7 @@ export class SettingsManager {
|
|
|
1322
1401
|
this.globalSettings.python = {};
|
|
1323
1402
|
}
|
|
1324
1403
|
this.globalSettings.python.kernelMode = mode;
|
|
1404
|
+
this.markModified("python", "kernelMode");
|
|
1325
1405
|
await this.save();
|
|
1326
1406
|
}
|
|
1327
1407
|
|
|
@@ -1334,6 +1414,7 @@ export class SettingsManager {
|
|
|
1334
1414
|
this.globalSettings.python = {};
|
|
1335
1415
|
}
|
|
1336
1416
|
this.globalSettings.python.sharedGateway = enabled;
|
|
1417
|
+
this.markModified("python", "sharedGateway");
|
|
1337
1418
|
await this.save();
|
|
1338
1419
|
}
|
|
1339
1420
|
|
|
@@ -1346,6 +1427,7 @@ export class SettingsManager {
|
|
|
1346
1427
|
this.globalSettings.mcp = {};
|
|
1347
1428
|
}
|
|
1348
1429
|
this.globalSettings.mcp.enableProjectConfig = enabled;
|
|
1430
|
+
this.markModified("mcp", "enableProjectConfig");
|
|
1349
1431
|
await this.save();
|
|
1350
1432
|
}
|
|
1351
1433
|
|
|
@@ -1358,6 +1440,7 @@ export class SettingsManager {
|
|
|
1358
1440
|
this.globalSettings.lsp = {};
|
|
1359
1441
|
}
|
|
1360
1442
|
this.globalSettings.lsp.formatOnWrite = enabled;
|
|
1443
|
+
this.markModified("lsp", "formatOnWrite");
|
|
1361
1444
|
await this.save();
|
|
1362
1445
|
}
|
|
1363
1446
|
|
|
@@ -1370,6 +1453,7 @@ export class SettingsManager {
|
|
|
1370
1453
|
this.globalSettings.lsp = {};
|
|
1371
1454
|
}
|
|
1372
1455
|
this.globalSettings.lsp.diagnosticsOnWrite = enabled;
|
|
1456
|
+
this.markModified("lsp", "diagnosticsOnWrite");
|
|
1373
1457
|
await this.save();
|
|
1374
1458
|
}
|
|
1375
1459
|
|
|
@@ -1382,6 +1466,7 @@ export class SettingsManager {
|
|
|
1382
1466
|
this.globalSettings.lsp = {};
|
|
1383
1467
|
}
|
|
1384
1468
|
this.globalSettings.lsp.diagnosticsOnEdit = enabled;
|
|
1469
|
+
this.markModified("lsp", "diagnosticsOnEdit");
|
|
1385
1470
|
await this.save();
|
|
1386
1471
|
}
|
|
1387
1472
|
|
|
@@ -1394,6 +1479,7 @@ export class SettingsManager {
|
|
|
1394
1479
|
this.globalSettings.edit = {};
|
|
1395
1480
|
}
|
|
1396
1481
|
this.globalSettings.edit.fuzzyMatch = enabled;
|
|
1482
|
+
this.markModified("edit", "fuzzyMatch");
|
|
1397
1483
|
await this.save();
|
|
1398
1484
|
}
|
|
1399
1485
|
|
|
@@ -1406,6 +1492,7 @@ export class SettingsManager {
|
|
|
1406
1492
|
this.globalSettings.edit = {};
|
|
1407
1493
|
}
|
|
1408
1494
|
this.globalSettings.edit.fuzzyThreshold = value;
|
|
1495
|
+
this.markModified("edit", "fuzzyThreshold");
|
|
1409
1496
|
await this.save();
|
|
1410
1497
|
}
|
|
1411
1498
|
|
|
@@ -1418,6 +1505,7 @@ export class SettingsManager {
|
|
|
1418
1505
|
this.globalSettings.edit = {};
|
|
1419
1506
|
}
|
|
1420
1507
|
this.globalSettings.edit.patchMode = enabled;
|
|
1508
|
+
this.markModified("edit", "patchMode");
|
|
1421
1509
|
await this.save();
|
|
1422
1510
|
}
|
|
1423
1511
|
|
|
@@ -1430,6 +1518,7 @@ export class SettingsManager {
|
|
|
1430
1518
|
this.globalSettings.edit = {};
|
|
1431
1519
|
}
|
|
1432
1520
|
this.globalSettings.edit.streamingAbort = enabled;
|
|
1521
|
+
this.markModified("edit", "streamingAbort");
|
|
1433
1522
|
await this.save();
|
|
1434
1523
|
}
|
|
1435
1524
|
|
|
@@ -1481,6 +1570,7 @@ export class SettingsManager {
|
|
|
1481
1570
|
} else {
|
|
1482
1571
|
this.globalSettings.edit.modelVariants[pattern] = variant;
|
|
1483
1572
|
}
|
|
1573
|
+
this.markModified("edit", "modelVariants");
|
|
1484
1574
|
await this.save();
|
|
1485
1575
|
}
|
|
1486
1576
|
|
|
@@ -1490,6 +1580,7 @@ export class SettingsManager {
|
|
|
1490
1580
|
|
|
1491
1581
|
async setNormativeRewrite(enabled: boolean): Promise<void> {
|
|
1492
1582
|
this.globalSettings.normativeRewrite = enabled;
|
|
1583
|
+
this.markModified("normativeRewrite");
|
|
1493
1584
|
await this.save();
|
|
1494
1585
|
}
|
|
1495
1586
|
|
|
@@ -1499,6 +1590,7 @@ export class SettingsManager {
|
|
|
1499
1590
|
|
|
1500
1591
|
async setReadLineNumbers(enabled: boolean): Promise<void> {
|
|
1501
1592
|
this.globalSettings.readLineNumbers = enabled;
|
|
1593
|
+
this.markModified("readLineNumbers");
|
|
1502
1594
|
await this.save();
|
|
1503
1595
|
}
|
|
1504
1596
|
|
|
@@ -1508,6 +1600,7 @@ export class SettingsManager {
|
|
|
1508
1600
|
|
|
1509
1601
|
async setDisabledProviders(providerIds: string[]): Promise<void> {
|
|
1510
1602
|
this.globalSettings.disabledProviders = providerIds;
|
|
1603
|
+
this.markModified("disabledProviders");
|
|
1511
1604
|
await this.save();
|
|
1512
1605
|
}
|
|
1513
1606
|
|
|
@@ -1517,6 +1610,7 @@ export class SettingsManager {
|
|
|
1517
1610
|
|
|
1518
1611
|
async setDisabledExtensions(extensionIds: string[]): Promise<void> {
|
|
1519
1612
|
this.globalSettings.disabledExtensions = extensionIds;
|
|
1613
|
+
this.markModified("disabledExtensions");
|
|
1520
1614
|
await this.save();
|
|
1521
1615
|
}
|
|
1522
1616
|
|
|
@@ -1530,6 +1624,7 @@ export class SettingsManager {
|
|
|
1530
1624
|
if (index !== -1) {
|
|
1531
1625
|
disabled.splice(index, 1);
|
|
1532
1626
|
this.globalSettings.disabledExtensions = disabled;
|
|
1627
|
+
this.markModified("disabledExtensions");
|
|
1533
1628
|
await this.save();
|
|
1534
1629
|
}
|
|
1535
1630
|
}
|
|
@@ -1539,6 +1634,7 @@ export class SettingsManager {
|
|
|
1539
1634
|
if (!disabled.includes(extensionId)) {
|
|
1540
1635
|
disabled.push(extensionId);
|
|
1541
1636
|
this.globalSettings.disabledExtensions = disabled;
|
|
1637
|
+
this.markModified("disabledExtensions");
|
|
1542
1638
|
await this.save();
|
|
1543
1639
|
}
|
|
1544
1640
|
}
|
|
@@ -1549,6 +1645,7 @@ export class SettingsManager {
|
|
|
1549
1645
|
|
|
1550
1646
|
async setTtsrSettings(settings: TtsrSettings): Promise<void> {
|
|
1551
1647
|
this.globalSettings.ttsr = { ...this.globalSettings.ttsr, ...settings };
|
|
1648
|
+
this.markModified("ttsr");
|
|
1552
1649
|
await this.save();
|
|
1553
1650
|
}
|
|
1554
1651
|
|
|
@@ -1561,6 +1658,7 @@ export class SettingsManager {
|
|
|
1561
1658
|
this.globalSettings.ttsr = {};
|
|
1562
1659
|
}
|
|
1563
1660
|
this.globalSettings.ttsr.enabled = enabled;
|
|
1661
|
+
this.markModified("ttsr", "enabled");
|
|
1564
1662
|
await this.save();
|
|
1565
1663
|
}
|
|
1566
1664
|
|
|
@@ -1573,6 +1671,7 @@ export class SettingsManager {
|
|
|
1573
1671
|
this.globalSettings.ttsr = {};
|
|
1574
1672
|
}
|
|
1575
1673
|
this.globalSettings.ttsr.contextMode = mode;
|
|
1674
|
+
this.markModified("ttsr", "contextMode");
|
|
1576
1675
|
await this.save();
|
|
1577
1676
|
}
|
|
1578
1677
|
|
|
@@ -1585,6 +1684,7 @@ export class SettingsManager {
|
|
|
1585
1684
|
this.globalSettings.ttsr = {};
|
|
1586
1685
|
}
|
|
1587
1686
|
this.globalSettings.ttsr.repeatMode = mode;
|
|
1687
|
+
this.markModified("ttsr", "repeatMode");
|
|
1588
1688
|
await this.save();
|
|
1589
1689
|
}
|
|
1590
1690
|
|
|
@@ -1597,6 +1697,7 @@ export class SettingsManager {
|
|
|
1597
1697
|
this.globalSettings.ttsr = {};
|
|
1598
1698
|
}
|
|
1599
1699
|
this.globalSettings.ttsr.repeatGap = gap;
|
|
1700
|
+
this.markModified("ttsr", "repeatGap");
|
|
1600
1701
|
await this.save();
|
|
1601
1702
|
}
|
|
1602
1703
|
|
|
@@ -1620,8 +1721,12 @@ export class SettingsManager {
|
|
|
1620
1721
|
delete this.globalSettings.statusLine.leftSegments;
|
|
1621
1722
|
delete this.globalSettings.statusLine.rightSegments;
|
|
1622
1723
|
delete this.globalSettings.statusLine.segmentOptions;
|
|
1724
|
+
this.markModified("statusLine", "leftSegments");
|
|
1725
|
+
this.markModified("statusLine", "rightSegments");
|
|
1726
|
+
this.markModified("statusLine", "segmentOptions");
|
|
1623
1727
|
}
|
|
1624
1728
|
this.globalSettings.statusLine.preset = preset;
|
|
1729
|
+
this.markModified("statusLine", "preset");
|
|
1625
1730
|
await this.save();
|
|
1626
1731
|
}
|
|
1627
1732
|
|
|
@@ -1634,6 +1739,7 @@ export class SettingsManager {
|
|
|
1634
1739
|
this.globalSettings.statusLine = {};
|
|
1635
1740
|
}
|
|
1636
1741
|
this.globalSettings.statusLine.separator = separator;
|
|
1742
|
+
this.markModified("statusLine", "separator");
|
|
1637
1743
|
await this.save();
|
|
1638
1744
|
}
|
|
1639
1745
|
|
|
@@ -1646,9 +1752,11 @@ export class SettingsManager {
|
|
|
1646
1752
|
this.globalSettings.statusLine = {};
|
|
1647
1753
|
}
|
|
1648
1754
|
this.globalSettings.statusLine.leftSegments = segments;
|
|
1755
|
+
this.markModified("statusLine", "leftSegments");
|
|
1649
1756
|
// Setting segments explicitly implies custom preset
|
|
1650
1757
|
if (this.globalSettings.statusLine.preset !== "custom") {
|
|
1651
1758
|
this.globalSettings.statusLine.preset = "custom";
|
|
1759
|
+
this.markModified("statusLine", "preset");
|
|
1652
1760
|
}
|
|
1653
1761
|
await this.save();
|
|
1654
1762
|
}
|
|
@@ -1662,9 +1770,11 @@ export class SettingsManager {
|
|
|
1662
1770
|
this.globalSettings.statusLine = {};
|
|
1663
1771
|
}
|
|
1664
1772
|
this.globalSettings.statusLine.rightSegments = segments;
|
|
1773
|
+
this.markModified("statusLine", "rightSegments");
|
|
1665
1774
|
// Setting segments explicitly implies custom preset
|
|
1666
1775
|
if (this.globalSettings.statusLine.preset !== "custom") {
|
|
1667
1776
|
this.globalSettings.statusLine.preset = "custom";
|
|
1777
|
+
this.markModified("statusLine", "preset");
|
|
1668
1778
|
}
|
|
1669
1779
|
await this.save();
|
|
1670
1780
|
}
|
|
@@ -1688,6 +1798,7 @@ export class SettingsManager {
|
|
|
1688
1798
|
this.globalSettings.statusLine.segmentOptions[segment] = {} as NonNullable<StatusLineSegmentOptions[K]>;
|
|
1689
1799
|
}
|
|
1690
1800
|
(this.globalSettings.statusLine.segmentOptions[segment] as Record<string, unknown>)[option as string] = value;
|
|
1801
|
+
this.markModified("statusLine", "segmentOptions");
|
|
1691
1802
|
await this.save();
|
|
1692
1803
|
}
|
|
1693
1804
|
|
|
@@ -1706,6 +1817,7 @@ export class SettingsManager {
|
|
|
1706
1817
|
if (Object.keys(segmentOptions).length === 0) {
|
|
1707
1818
|
delete this.globalSettings.statusLine?.segmentOptions;
|
|
1708
1819
|
}
|
|
1820
|
+
this.markModified("statusLine", "segmentOptions");
|
|
1709
1821
|
await this.save();
|
|
1710
1822
|
}
|
|
1711
1823
|
|
|
@@ -1718,6 +1830,7 @@ export class SettingsManager {
|
|
|
1718
1830
|
this.globalSettings.statusLine = {};
|
|
1719
1831
|
}
|
|
1720
1832
|
this.globalSettings.statusLine.showHookStatus = show;
|
|
1833
|
+
this.markModified("statusLine", "showHookStatus");
|
|
1721
1834
|
await this.save();
|
|
1722
1835
|
}
|
|
1723
1836
|
|
|
@@ -1727,6 +1840,7 @@ export class SettingsManager {
|
|
|
1727
1840
|
|
|
1728
1841
|
async setDoubleEscapeAction(action: "branch" | "tree"): Promise<void> {
|
|
1729
1842
|
this.globalSettings.doubleEscapeAction = action;
|
|
1843
|
+
this.markModified("doubleEscapeAction");
|
|
1730
1844
|
await this.save();
|
|
1731
1845
|
}
|
|
1732
1846
|
|
|
@@ -1745,6 +1859,7 @@ export class SettingsManager {
|
|
|
1745
1859
|
|
|
1746
1860
|
async setShowHardwareCursor(show: boolean): Promise<void> {
|
|
1747
1861
|
this.globalSettings.showHardwareCursor = show;
|
|
1862
|
+
this.markModified("showHardwareCursor");
|
|
1748
1863
|
await this.save();
|
|
1749
1864
|
}
|
|
1750
1865
|
|
|
@@ -1761,6 +1876,7 @@ export class SettingsManager {
|
|
|
1761
1876
|
*/
|
|
1762
1877
|
async setEnvironmentVariables(envVars: Record<string, string>): Promise<void> {
|
|
1763
1878
|
this.globalSettings.env = { ...envVars };
|
|
1879
|
+
this.markModified("env");
|
|
1764
1880
|
await this.save();
|
|
1765
1881
|
}
|
|
1766
1882
|
|
|
@@ -1769,6 +1885,7 @@ export class SettingsManager {
|
|
|
1769
1885
|
*/
|
|
1770
1886
|
async clearEnvironmentVariables(): Promise<void> {
|
|
1771
1887
|
delete this.globalSettings.env;
|
|
1888
|
+
this.markModified("env");
|
|
1772
1889
|
await this.save();
|
|
1773
1890
|
}
|
|
1774
1891
|
|
|
@@ -1780,6 +1897,7 @@ export class SettingsManager {
|
|
|
1780
1897
|
this.globalSettings.env = {};
|
|
1781
1898
|
}
|
|
1782
1899
|
this.globalSettings.env[key] = value;
|
|
1900
|
+
this.markModified("env", key);
|
|
1783
1901
|
await this.save();
|
|
1784
1902
|
}
|
|
1785
1903
|
|
|
@@ -1789,6 +1907,7 @@ export class SettingsManager {
|
|
|
1789
1907
|
async removeEnvironmentVariable(key: string): Promise<void> {
|
|
1790
1908
|
if (this.globalSettings.env) {
|
|
1791
1909
|
delete this.globalSettings.env[key];
|
|
1910
|
+
this.markModified("env", key);
|
|
1792
1911
|
await this.save();
|
|
1793
1912
|
}
|
|
1794
1913
|
}
|
|
@@ -502,6 +502,19 @@ export class AgentSession {
|
|
|
502
502
|
// Track assistant message for auto-compaction (checked on agent_end)
|
|
503
503
|
if (event.message.role === "assistant") {
|
|
504
504
|
this._lastAssistantMessage = event.message;
|
|
505
|
+
|
|
506
|
+
// Reset retry counter immediately on successful assistant response
|
|
507
|
+
// This prevents accumulation across multiple LLM calls within a turn
|
|
508
|
+
const assistantMsg = event.message as AssistantMessage;
|
|
509
|
+
if (assistantMsg.stopReason !== "error" && this._retryAttempt > 0) {
|
|
510
|
+
this._emit({
|
|
511
|
+
type: "auto_retry_end",
|
|
512
|
+
success: true,
|
|
513
|
+
attempt: this._retryAttempt,
|
|
514
|
+
});
|
|
515
|
+
this._retryAttempt = 0;
|
|
516
|
+
this._resolveRetry();
|
|
517
|
+
}
|
|
505
518
|
}
|
|
506
519
|
|
|
507
520
|
if (event.message.role === "toolResult") {
|
|
@@ -530,16 +543,6 @@ export class AgentSession {
|
|
|
530
543
|
if (this._isRetryableError(msg)) {
|
|
531
544
|
const didRetry = await this._handleRetryableError(msg);
|
|
532
545
|
if (didRetry) return; // Retry was initiated, don't proceed to compaction
|
|
533
|
-
} else if (this._retryAttempt > 0) {
|
|
534
|
-
// Previous retry succeeded - emit success event and reset counter
|
|
535
|
-
this._emit({
|
|
536
|
-
type: "auto_retry_end",
|
|
537
|
-
success: true,
|
|
538
|
-
attempt: this._retryAttempt,
|
|
539
|
-
});
|
|
540
|
-
this._retryAttempt = 0;
|
|
541
|
-
// Resolve the retry promise so waitForRetry() completes
|
|
542
|
-
this._resolveRetry();
|
|
543
546
|
}
|
|
544
547
|
|
|
545
548
|
await this._checkCompaction(msg);
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -13,6 +13,17 @@ function tryMacOSScreenshotPath(filePath: string): string {
|
|
|
13
13
|
return filePath.replace(/ (AM|PM)\./g, `${NARROW_NO_BREAK_SPACE}$1.`);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function tryNFDVariant(filePath: string): string {
|
|
17
|
+
// macOS stores filenames in NFD (decomposed) form, try converting user input to NFD
|
|
18
|
+
return filePath.normalize("NFD");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function tryCurlyQuoteVariant(filePath: string): string {
|
|
22
|
+
// macOS uses U+2019 (right single quotation mark) in screenshot names like "Capture d'écran"
|
|
23
|
+
// Users typically type U+0027 (straight apostrophe)
|
|
24
|
+
return filePath.replace(/'/g, "\u2019");
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
function fileExists(filePath: string): boolean {
|
|
17
28
|
try {
|
|
18
29
|
fs.accessSync(filePath, fs.constants.F_OK);
|
|
@@ -52,9 +63,28 @@ export function resolveReadPath(filePath: string, cwd: string): string {
|
|
|
52
63
|
return resolved;
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
// Try macOS AM/PM variant (narrow no-break space before AM/PM)
|
|
67
|
+
const amPmVariant = tryMacOSScreenshotPath(resolved);
|
|
68
|
+
if (amPmVariant !== resolved && fileExists(amPmVariant)) {
|
|
69
|
+
return amPmVariant;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Try NFD variant (macOS stores filenames in NFD form)
|
|
73
|
+
const nfdVariant = tryNFDVariant(resolved);
|
|
74
|
+
if (nfdVariant !== resolved && fileExists(nfdVariant)) {
|
|
75
|
+
return nfdVariant;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Try curly quote variant (macOS uses U+2019 in screenshot names)
|
|
79
|
+
const curlyVariant = tryCurlyQuoteVariant(resolved);
|
|
80
|
+
if (curlyVariant !== resolved && fileExists(curlyVariant)) {
|
|
81
|
+
return curlyVariant;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Try combined NFD + curly quote (for French macOS screenshots like "Capture d'écran")
|
|
85
|
+
const nfdCurlyVariant = tryCurlyQuoteVariant(nfdVariant);
|
|
86
|
+
if (nfdCurlyVariant !== resolved && fileExists(nfdCurlyVariant)) {
|
|
87
|
+
return nfdCurlyVariant;
|
|
58
88
|
}
|
|
59
89
|
|
|
60
90
|
return resolved;
|