iosm-cli 0.2.7 → 0.2.9

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 (149) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/README.md +4 -4
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +12 -4
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-profiles.d.ts.map +1 -1
  7. package/dist/core/agent-profiles.js +15 -2
  8. package/dist/core/agent-profiles.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +3 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +214 -2
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/sdk.d.ts +2 -2
  14. package/dist/core/sdk.d.ts.map +1 -1
  15. package/dist/core/sdk.js +7 -4
  16. package/dist/core/sdk.js.map +1 -1
  17. package/dist/core/settings-manager.d.ts +57 -0
  18. package/dist/core/settings-manager.d.ts.map +1 -1
  19. package/dist/core/settings-manager.js +197 -0
  20. package/dist/core/settings-manager.js.map +1 -1
  21. package/dist/core/shadow-guard.d.ts.map +1 -1
  22. package/dist/core/shadow-guard.js +12 -1
  23. package/dist/core/shadow-guard.js.map +1 -1
  24. package/dist/core/system-prompt.d.ts.map +1 -1
  25. package/dist/core/system-prompt.js +109 -4
  26. package/dist/core/system-prompt.js.map +1 -1
  27. package/dist/core/tools/db-run.d.ts +84 -0
  28. package/dist/core/tools/db-run.d.ts.map +1 -0
  29. package/dist/core/tools/db-run.js +690 -0
  30. package/dist/core/tools/db-run.js.map +1 -0
  31. package/dist/core/tools/git-common.d.ts +45 -0
  32. package/dist/core/tools/git-common.d.ts.map +1 -0
  33. package/dist/core/tools/git-common.js +185 -0
  34. package/dist/core/tools/git-common.js.map +1 -0
  35. package/dist/core/tools/git-read.d.ts +15 -13
  36. package/dist/core/tools/git-read.d.ts.map +1 -1
  37. package/dist/core/tools/git-read.js +101 -153
  38. package/dist/core/tools/git-read.js.map +1 -1
  39. package/dist/core/tools/git-write.d.ts +75 -0
  40. package/dist/core/tools/git-write.d.ts.map +1 -0
  41. package/dist/core/tools/git-write.js +298 -0
  42. package/dist/core/tools/git-write.js.map +1 -0
  43. package/dist/core/tools/index.d.ts +91 -1
  44. package/dist/core/tools/index.d.ts.map +1 -1
  45. package/dist/core/tools/index.js +26 -0
  46. package/dist/core/tools/index.js.map +1 -1
  47. package/dist/core/tools/lint-run.d.ts +42 -0
  48. package/dist/core/tools/lint-run.d.ts.map +1 -0
  49. package/dist/core/tools/lint-run.js +276 -0
  50. package/dist/core/tools/lint-run.js.map +1 -0
  51. package/dist/core/tools/task.js +1 -1
  52. package/dist/core/tools/task.js.map +1 -1
  53. package/dist/core/tools/test-run.d.ts +36 -0
  54. package/dist/core/tools/test-run.d.ts.map +1 -0
  55. package/dist/core/tools/test-run.js +255 -0
  56. package/dist/core/tools/test-run.js.map +1 -0
  57. package/dist/core/tools/typecheck-run.d.ts +44 -0
  58. package/dist/core/tools/typecheck-run.d.ts.map +1 -0
  59. package/dist/core/tools/typecheck-run.js +343 -0
  60. package/dist/core/tools/typecheck-run.js.map +1 -0
  61. package/dist/core/tools/verification-runner.d.ts +53 -0
  62. package/dist/core/tools/verification-runner.d.ts.map +1 -0
  63. package/dist/core/tools/verification-runner.js +235 -0
  64. package/dist/core/tools/verification-runner.js.map +1 -0
  65. package/dist/core/tools/web-search.d.ts +72 -0
  66. package/dist/core/tools/web-search.d.ts.map +1 -0
  67. package/dist/core/tools/web-search.js +702 -0
  68. package/dist/core/tools/web-search.js.map +1 -0
  69. package/dist/index.d.ts +2 -2
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +1 -1
  72. package/dist/index.js.map +1 -1
  73. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  74. package/dist/modes/interactive/components/branch-summary-message.js +2 -1
  75. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  76. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  77. package/dist/modes/interactive/components/compaction-summary-message.js +2 -1
  78. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  79. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  80. package/dist/modes/interactive/components/config-selector.js +7 -2
  81. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  82. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  83. package/dist/modes/interactive/components/custom-message.js +2 -1
  84. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  85. package/dist/modes/interactive/components/mcp-selector.d.ts.map +1 -1
  86. package/dist/modes/interactive/components/mcp-selector.js +3 -1
  87. package/dist/modes/interactive/components/mcp-selector.js.map +1 -1
  88. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  89. package/dist/modes/interactive/components/model-selector.js +12 -2
  90. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  91. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  92. package/dist/modes/interactive/components/oauth-selector.js +11 -0
  93. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  94. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  95. package/dist/modes/interactive/components/scoped-models-selector.js +16 -5
  96. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  97. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  98. package/dist/modes/interactive/components/session-selector.js +4 -2
  99. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  100. package/dist/modes/interactive/components/settings-selector.d.ts +25 -0
  101. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  102. package/dist/modes/interactive/components/settings-selector.js +182 -2
  103. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  104. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
  105. package/dist/modes/interactive/components/show-images-selector.js +7 -2
  106. package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
  107. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  108. package/dist/modes/interactive/components/skill-invocation-message.js +4 -2
  109. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  110. package/dist/modes/interactive/components/subagent-message.d.ts.map +1 -1
  111. package/dist/modes/interactive/components/subagent-message.js +3 -1
  112. package/dist/modes/interactive/components/subagent-message.js.map +1 -1
  113. package/dist/modes/interactive/components/task-plan-message.d.ts.map +1 -1
  114. package/dist/modes/interactive/components/task-plan-message.js +2 -1
  115. package/dist/modes/interactive/components/task-plan-message.js.map +1 -1
  116. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
  117. package/dist/modes/interactive/components/theme-selector.js +7 -2
  118. package/dist/modes/interactive/components/theme-selector.js.map +1 -1
  119. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  120. package/dist/modes/interactive/components/thinking-selector.js +7 -2
  121. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  122. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  123. package/dist/modes/interactive/components/tool-execution.js +25 -7
  124. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  125. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/tree-selector.js +18 -3
  127. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  128. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  129. package/dist/modes/interactive/components/user-message-selector.js +8 -0
  130. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  131. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  132. package/dist/modes/interactive/components/user-message.js +2 -1
  133. package/dist/modes/interactive/components/user-message.js.map +1 -1
  134. package/dist/modes/interactive/interactive-mode.d.ts +8 -0
  135. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  136. package/dist/modes/interactive/interactive-mode.js +622 -11
  137. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  138. package/dist/modes/interactive/theme/dark.json +39 -38
  139. package/dist/modes/interactive/theme/light.json +29 -29
  140. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  141. package/dist/modes/interactive/theme/theme.js +16 -25
  142. package/dist/modes/interactive/theme/theme.js.map +1 -1
  143. package/dist/modes/interactive/theme/universal.json +85 -0
  144. package/docs/cli-reference.md +32 -2
  145. package/docs/configuration.md +86 -2
  146. package/docs/development-and-testing.md +1 -1
  147. package/docs/interactive-mode.md +8 -3
  148. package/docs/rpc-json-sdk.md +1 -1
  149. package/package.json +1 -1
@@ -11,7 +11,7 @@ import { spawn, spawnSync } from "child_process";
11
11
  import { APP_NAME, CHANGELOG_URL, ENV_SESSION_TRACE, ENV_OFFLINE, ENV_SKIP_VERSION_CHECK, getAgentDir, getAuthPath, getDebugLogPath, getModelsPath, getSessionTracePath, getShareViewerUrl, getUpdateInstruction, isSessionTraceEnabled, PACKAGE_NAME, VERSION, } from "../../config.js";
12
12
  import { AuthStorage } from "../../core/auth-storage.js";
13
13
  import { getAgentProfile, getMainProfileNames, getProfileNames, isReadOnlyProfileName, isValidProfileName, } from "../../core/agent-profiles.js";
14
- import { parseSkillBlock } from "../../core/agent-session.js";
14
+ import { parseSkillBlock, } from "../../core/agent-session.js";
15
15
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
16
16
  import { KeybindingsManager } from "../../core/keybindings.js";
17
17
  import { createCompactionSummaryMessage, INTERNAL_UI_META_CUSTOM_TYPE, isInternalUiMetaDetails, } from "../../core/messages.js";
@@ -119,6 +119,211 @@ function extractTaskToolErrorText(result) {
119
119
  return candidate.output.trim();
120
120
  return undefined;
121
121
  }
122
+ const MAX_TOOL_PROTOCOL_REPAIR_ATTEMPTS = 2;
123
+ const MAX_ASSISTANT_CONTINUATION_PROMPTS_PER_TURN = 1;
124
+ function extractAssistantProtocolText(message) {
125
+ const parts = [];
126
+ for (const content of message.content) {
127
+ if (content.type === "text" && typeof content.text === "string" && content.text.trim()) {
128
+ parts.push(content.text.trim());
129
+ continue;
130
+ }
131
+ if (content.type !== "thinking")
132
+ continue;
133
+ const record = content;
134
+ const thinking = record.thinking;
135
+ if (typeof thinking === "string" && thinking.trim()) {
136
+ parts.push(thinking.trim());
137
+ }
138
+ }
139
+ return parts.join("\n\n").trim();
140
+ }
141
+ function extractAssistantVisibleText(message) {
142
+ return message.content
143
+ .filter((content) => content.type === "text")
144
+ .map((content) => content.text.trim())
145
+ .filter((text) => text.length > 0)
146
+ .join("\n\n")
147
+ .trim();
148
+ }
149
+ function isNonActionableVisibleAssistantText(text) {
150
+ const trimmed = text.trim();
151
+ if (trimmed.length === 0)
152
+ return true;
153
+ return /^\[\s*output\s+truncated[^\]]*\]?\s*\.?$/i.test(trimmed);
154
+ }
155
+ function detectRawToolProtocolIssue(text) {
156
+ const trimmed = text.trim();
157
+ if (!trimmed)
158
+ return undefined;
159
+ const hasToolCallOpenBlock = /(^|\n)\s*<\s*tool_call\b/i.test(trimmed);
160
+ const hasToolCallCloseTag = /<\/\s*tool_call\s*>/i.test(trimmed);
161
+ const hasFunctionBlock = /(^|\n)\s*<\s*function\s*=\s*[A-Za-z0-9._:-]+/i.test(trimmed);
162
+ const hasParameterBlock = /(^|\n)\s*<\s*parameter\s*=\s*[A-Za-z0-9._:-]+\s*>/i.test(trimmed);
163
+ // Treat markup as protocol issue only when it resembles an executable pseudo-call structure.
164
+ // Plain inline mentions like "raw <tool_call>/<function=...> markup" should not trigger repair.
165
+ const hasToolCallMarkup = (hasToolCallOpenBlock && (hasToolCallCloseTag || hasFunctionBlock || hasParameterBlock)) ||
166
+ (hasFunctionBlock && hasParameterBlock);
167
+ const hasDelegateTaskMarkup = /<\s*delegate_task\b/i.test(trimmed) && /<\/\s*delegate_task\s*>/i.test(trimmed);
168
+ if (!hasToolCallMarkup && !hasDelegateTaskMarkup) {
169
+ return undefined;
170
+ }
171
+ return {
172
+ hasToolCallMarkup,
173
+ hasDelegateTaskMarkup,
174
+ };
175
+ }
176
+ function detectAssistantResumeReason(message) {
177
+ if (message.stopReason === "aborted")
178
+ return "user_aborted";
179
+ if (message.stopReason === "error")
180
+ return "interrupted_error";
181
+ return undefined;
182
+ }
183
+ function buildAssistantResumePrompt(input) {
184
+ const originalPrompt = input.originalPrompt.trim();
185
+ const boundedOriginalPrompt = originalPrompt.length > 2_000 ? `${originalPrompt.slice(0, 2_000).trimEnd()}...` : originalPrompt;
186
+ const reasonLine = input.reason === "user_aborted"
187
+ ? "Previous assistant turn was cancelled before completion."
188
+ : "Previous assistant turn ended with a tool/model interruption before completion.";
189
+ return [
190
+ "[ASSISTANT_RESUME_REQUEST]",
191
+ reasonLine,
192
+ "Continue the same user request from current in-memory state.",
193
+ "Do not repeat completed steps unless required.",
194
+ "Use only real structured tool calls when tools are needed.",
195
+ "Do not emit pseudo XML-like tool markup.",
196
+ "<original_user_request>",
197
+ boundedOriginalPrompt,
198
+ "</original_user_request>",
199
+ "[/ASSISTANT_RESUME_REQUEST]",
200
+ ].join("\n");
201
+ }
202
+ function buildAssistantContinuationSelectorTitle(reason) {
203
+ if (reason === "user_aborted") {
204
+ return "You stopped the current run.\nChoose what to do next:";
205
+ }
206
+ return "Tool invocation failed on the model side.\nChoose what to do next:";
207
+ }
208
+ function buildRawToolProtocolCorrectionPrompt(input) {
209
+ const reasons = [
210
+ input.issue.hasToolCallMarkup ? "raw <tool_call>/<function=...> markup" : undefined,
211
+ input.issue.hasDelegateTaskMarkup ? "raw <delegate_task> blocks" : undefined,
212
+ ].filter((item) => typeof item === "string");
213
+ const originalPrompt = input.originalPrompt.trim();
214
+ const boundedOriginalPrompt = originalPrompt.length > 2_000 ? `${originalPrompt.slice(0, 2_000).trimEnd()}...` : originalPrompt;
215
+ return [
216
+ "[TOOL_PROTOCOL_CORRECTION]",
217
+ `Previous assistant output included ${reasons.join(" and ")} in plain text.`,
218
+ "These XML-like blocks are not executable tool calls and are ignored by the runtime.",
219
+ "Retry now and follow this protocol exactly:",
220
+ "1) Do not output XML/pseudo-call tags (<tool_call>, <function=...>, <delegate_task>).",
221
+ "2) If a tool is needed, emit real structured tool calls only.",
222
+ "3) Prefer structured built-ins (db_run, typecheck_run, test_run, lint_run) when applicable.",
223
+ "4) If no tool is needed, return a normal direct answer.",
224
+ input.hasPriorToolActivity
225
+ ? "5) Continue from the current in-memory state; avoid repeating already completed tool steps unless necessary."
226
+ : undefined,
227
+ "Execute the original user request now.",
228
+ "<original_user_request>",
229
+ boundedOriginalPrompt,
230
+ "</original_user_request>",
231
+ "[/TOOL_PROTOCOL_CORRECTION]",
232
+ ]
233
+ .filter((line) => typeof line === "string")
234
+ .join("\n");
235
+ }
236
+ function buildRawToolSilentStopRecoveryPrompt(input) {
237
+ const originalPrompt = input.originalPrompt.trim();
238
+ const boundedOriginalPrompt = originalPrompt.length > 2_000 ? `${originalPrompt.slice(0, 2_000).trimEnd()}...` : originalPrompt;
239
+ return [
240
+ "[ASSISTANT_STALL_RECOVERY]",
241
+ "Previous assistant output ended with stop but produced no visible text and no executable tool calls.",
242
+ "Retry now and continue the same request.",
243
+ "1) Do not return an empty response.",
244
+ "2) If a tool is needed, emit real structured tool calls.",
245
+ "3) If no tool is needed, return a normal direct answer.",
246
+ input.hasPriorToolActivity
247
+ ? "4) Continue from the current in-memory state; avoid repeating already completed tool steps unless necessary."
248
+ : undefined,
249
+ "Execute the original user request now.",
250
+ "<original_user_request>",
251
+ boundedOriginalPrompt,
252
+ "</original_user_request>",
253
+ "[/ASSISTANT_STALL_RECOVERY]",
254
+ ]
255
+ .filter((line) => typeof line === "string")
256
+ .join("\n");
257
+ }
258
+ async function promptWithRawToolProtocolRepair(input) {
259
+ let currentPrompt = input.promptText;
260
+ let currentOptions = {
261
+ ...(input.promptOptions ?? {}),
262
+ skipProtocolAutoRepair: true,
263
+ };
264
+ for (let repairAttempt = 0; repairAttempt <= MAX_TOOL_PROTOCOL_REPAIR_ATTEMPTS; repairAttempt += 1) {
265
+ let toolCallsStarted = 0;
266
+ let latestAssistantProtocolText = "";
267
+ let latestAssistantStopReason;
268
+ let latestAssistantVisibleText = "";
269
+ let latestAssistantHasInlineToolCalls = false;
270
+ const unsubscribe = typeof input.session.subscribe === "function"
271
+ ? input.session.subscribe((event) => {
272
+ if (event.type === "tool_execution_start") {
273
+ toolCallsStarted += 1;
274
+ return;
275
+ }
276
+ if (event.type === "message_end" && event.message.role === "assistant") {
277
+ latestAssistantStopReason = event.message.stopReason;
278
+ const assistantMessage = event.message;
279
+ latestAssistantProtocolText = extractAssistantProtocolText(assistantMessage);
280
+ latestAssistantVisibleText = extractAssistantVisibleText(assistantMessage);
281
+ latestAssistantHasInlineToolCalls = assistantMessage.content.some((part) => part.type === "toolCall");
282
+ }
283
+ })
284
+ : undefined;
285
+ try {
286
+ await input.session.prompt(currentPrompt, currentOptions);
287
+ }
288
+ finally {
289
+ unsubscribe?.();
290
+ }
291
+ const issue = detectRawToolProtocolIssue(latestAssistantProtocolText);
292
+ const silentStopWithoutOutput = !issue &&
293
+ latestAssistantStopReason === "stop" &&
294
+ !latestAssistantHasInlineToolCalls &&
295
+ isNonActionableVisibleAssistantText(latestAssistantVisibleText);
296
+ if (!issue && !silentStopWithoutOutput) {
297
+ return;
298
+ }
299
+ if (repairAttempt >= MAX_TOOL_PROTOCOL_REPAIR_ATTEMPTS) {
300
+ await input.onRepairExhausted?.(issue ? "raw_markup" : "silent_stop");
301
+ return;
302
+ }
303
+ const hasPriorToolActivity = toolCallsStarted > 0 || repairAttempt > 0;
304
+ if (issue) {
305
+ input.onRepairApplied?.("raw_markup");
306
+ currentPrompt = buildRawToolProtocolCorrectionPrompt({
307
+ originalPrompt: input.promptText,
308
+ issue,
309
+ hasPriorToolActivity,
310
+ });
311
+ }
312
+ else {
313
+ input.onRepairApplied?.("silent_stop");
314
+ currentPrompt = buildRawToolSilentStopRecoveryPrompt({
315
+ originalPrompt: input.promptText,
316
+ hasPriorToolActivity,
317
+ });
318
+ }
319
+ currentOptions = {
320
+ expandPromptTemplates: false,
321
+ skipOrchestrationDirective: true,
322
+ skipProtocolAutoRepair: true,
323
+ source: "interactive",
324
+ };
325
+ }
326
+ }
122
327
  function buildMetaParallelismCorrection(input) {
123
328
  const validationFailure = input.taskToolError && /Validation failed for tool "task"/i.test(input.taskToolError)
124
329
  ? input.taskToolError.split("\n").slice(0, 3).join("\n")
@@ -444,6 +649,81 @@ const DOCTOR_CLI_TOOL_SPECS = [
444
649
  candidates: ["semgrep"],
445
650
  hint: "Install semgrep (pipx install semgrep or pip install semgrep).",
446
651
  },
652
+ {
653
+ tool: "vitest",
654
+ candidates: ["vitest"],
655
+ hint: "Install vitest (npm/pnpm/yarn add -D vitest).",
656
+ },
657
+ {
658
+ tool: "jest",
659
+ candidates: ["jest"],
660
+ hint: "Install jest (npm/pnpm/yarn add -D jest).",
661
+ },
662
+ {
663
+ tool: "pytest",
664
+ candidates: ["python3", "pytest"],
665
+ hint: "Install pytest (python3 -m pip install pytest or pipx install pytest).",
666
+ },
667
+ {
668
+ tool: "eslint",
669
+ candidates: ["eslint"],
670
+ hint: "Install eslint (npm/pnpm/yarn add -D eslint).",
671
+ },
672
+ {
673
+ tool: "prettier",
674
+ candidates: ["prettier"],
675
+ hint: "Install prettier (npm/pnpm/yarn add -D prettier).",
676
+ },
677
+ {
678
+ tool: "stylelint",
679
+ candidates: ["stylelint"],
680
+ hint: "Install stylelint (npm/pnpm/yarn add -D stylelint).",
681
+ },
682
+ {
683
+ tool: "tsc",
684
+ candidates: ["tsc"],
685
+ hint: "Install TypeScript compiler (npm/pnpm/yarn add -D typescript).",
686
+ },
687
+ {
688
+ tool: "vue_tsc",
689
+ candidates: ["vue-tsc"],
690
+ hint: "Install vue-tsc (npm/pnpm/yarn add -D vue-tsc).",
691
+ },
692
+ {
693
+ tool: "pyright",
694
+ candidates: ["pyright"],
695
+ hint: "Install pyright (npm i -D pyright or pipx install pyright).",
696
+ },
697
+ {
698
+ tool: "mypy",
699
+ candidates: ["mypy", "python3"],
700
+ hint: "Install mypy (python3 -m pip install mypy or pipx install mypy).",
701
+ },
702
+ {
703
+ tool: "psql",
704
+ candidates: ["psql"],
705
+ hint: "Install PostgreSQL CLI (psql).",
706
+ },
707
+ {
708
+ tool: "mysql",
709
+ candidates: ["mysql"],
710
+ hint: "Install MySQL CLI client (mysql).",
711
+ },
712
+ {
713
+ tool: "sqlite3",
714
+ candidates: ["sqlite3"],
715
+ hint: "Install sqlite3 CLI.",
716
+ },
717
+ {
718
+ tool: "mongosh",
719
+ candidates: ["mongosh"],
720
+ hint: "Install MongoDB shell (mongosh).",
721
+ },
722
+ {
723
+ tool: "redis-cli",
724
+ candidates: ["redis-cli"],
725
+ hint: "Install Redis CLI (redis-cli).",
726
+ },
447
727
  {
448
728
  tool: "sed",
449
729
  candidates: ["sed"],
@@ -692,6 +972,8 @@ export class InteractiveMode {
692
972
  // Extension UI state
693
973
  this.extensionSelector = undefined;
694
974
  this.extensionInput = undefined;
975
+ this.extensionInputRestoreComponent = undefined;
976
+ this.extensionInputRestoreFocus = undefined;
695
977
  this.extensionEditor = undefined;
696
978
  this.extensionTerminalInputUnsubscribers = new Set();
697
979
  // Extension widgets (components rendered above/below the editor)
@@ -716,6 +998,9 @@ export class InteractiveMode {
716
998
  this.modelsDevProviderCatalogRefreshPromise = undefined;
717
999
  // Custom header from extension (undefined = use built-in header)
718
1000
  this.customHeader = undefined;
1001
+ // Active selector shown in editor container (used to restore UI after temporary dialogs)
1002
+ this.activeSelectorComponent = undefined;
1003
+ this.activeSelectorFocus = undefined;
719
1004
  /**
720
1005
  * Gracefully shutdown the agent.
721
1006
  * Emits shutdown event to extensions, then exits.
@@ -1779,7 +2064,25 @@ export class InteractiveMode {
1779
2064
  while (true) {
1780
2065
  const userInput = await this.getUserInput();
1781
2066
  try {
1782
- await this.promptWithTaskFallback(userInput);
2067
+ let promptText = userInput;
2068
+ let continuationPrompts = 0;
2069
+ while (true) {
2070
+ const turnStartMessageCount = this.session.messages.length;
2071
+ await this.promptWithTaskFallback(promptText);
2072
+ if (continuationPrompts >= MAX_ASSISTANT_CONTINUATION_PROMPTS_PER_TURN) {
2073
+ break;
2074
+ }
2075
+ const continuationDecision = await this.maybeRequestAgentContinuation(userInput, turnStartMessageCount);
2076
+ if (!continuationDecision || continuationDecision.action === "stay") {
2077
+ break;
2078
+ }
2079
+ if (continuationDecision.action === "new_session") {
2080
+ await this.handleClearCommand();
2081
+ break;
2082
+ }
2083
+ continuationPrompts += 1;
2084
+ promptText = continuationDecision.promptText;
2085
+ }
1783
2086
  }
1784
2087
  catch (error) {
1785
2088
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -2595,6 +2898,12 @@ export class InteractiveMode {
2595
2898
  resolve(undefined);
2596
2899
  };
2597
2900
  opts?.signal?.addEventListener("abort", onAbort, { once: true });
2901
+ const canRestoreSelector = this.activeSelectorComponent !== undefined &&
2902
+ this.editorContainer.children.includes(this.activeSelectorComponent);
2903
+ this.extensionInputRestoreComponent = canRestoreSelector ? this.activeSelectorComponent : this.editor;
2904
+ this.extensionInputRestoreFocus = canRestoreSelector
2905
+ ? (this.activeSelectorFocus ?? this.activeSelectorComponent)
2906
+ : this.editor;
2598
2907
  this.extensionInput = new ExtensionInputComponent(title, placeholder, (value) => {
2599
2908
  opts?.signal?.removeEventListener("abort", onAbort);
2600
2909
  this.hideExtensionInput();
@@ -2616,9 +2925,13 @@ export class InteractiveMode {
2616
2925
  hideExtensionInput() {
2617
2926
  this.extensionInput?.dispose();
2618
2927
  this.editorContainer.clear();
2619
- this.editorContainer.addChild(this.editor);
2928
+ const restoreComponent = this.extensionInputRestoreComponent ?? this.editor;
2929
+ const restoreFocus = this.extensionInputRestoreFocus ?? restoreComponent;
2930
+ this.editorContainer.addChild(restoreComponent);
2620
2931
  this.extensionInput = undefined;
2621
- this.ui.setFocus(this.editor);
2932
+ this.extensionInputRestoreComponent = undefined;
2933
+ this.extensionInputRestoreFocus = undefined;
2934
+ this.ui.setFocus(restoreFocus);
2622
2935
  this.ui.requestRender();
2623
2936
  }
2624
2937
  /**
@@ -4837,8 +5150,12 @@ export class InteractiveMode {
4837
5150
  this.editorContainer.clear();
4838
5151
  this.editorContainer.addChild(this.editor);
4839
5152
  this.ui.setFocus(this.editor);
5153
+ this.activeSelectorComponent = undefined;
5154
+ this.activeSelectorFocus = undefined;
4840
5155
  };
4841
5156
  const { component, focus } = create(done);
5157
+ this.activeSelectorComponent = component;
5158
+ this.activeSelectorFocus = focus;
4842
5159
  this.editorContainer.clear();
4843
5160
  this.editorContainer.addChild(component);
4844
5161
  this.ui.setFocus(focus);
@@ -5323,7 +5640,7 @@ export class InteractiveMode {
5323
5640
  transport: this.settingsManager.getTransport(),
5324
5641
  thinkingLevel: this.session.thinkingLevel,
5325
5642
  availableThinkingLevels: this.session.getAvailableThinkingLevels(),
5326
- currentTheme: this.settingsManager.getTheme() || "dark",
5643
+ currentTheme: this.settingsManager.getTheme() || "universal",
5327
5644
  availableThemes: getAvailableThemes(),
5328
5645
  hideThinkingBlock: this.hideThinkingBlock,
5329
5646
  collapseChangelog: this.settingsManager.getCollapseChangelog(),
@@ -5334,6 +5651,16 @@ export class InteractiveMode {
5334
5651
  autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
5335
5652
  quietStartup: this.settingsManager.getQuietStartup(),
5336
5653
  clearOnShrink: this.settingsManager.getClearOnShrink(),
5654
+ webSearchEnabled: this.settingsManager.getWebSearchEnabled(),
5655
+ webSearchProviderMode: this.settingsManager.getWebSearchProviderMode(),
5656
+ webSearchFallbackMode: this.settingsManager.getWebSearchFallbackMode(),
5657
+ webSearchSafeSearch: this.settingsManager.getWebSearchSafeSearch(),
5658
+ webSearchMaxResults: this.settingsManager.getWebSearchMaxResults(),
5659
+ webSearchTimeoutSeconds: this.settingsManager.getWebSearchTimeoutSeconds(),
5660
+ webSearchTavilyApiKeyConfigured: this.settingsManager.isWebSearchTavilyApiKeyConfigured(),
5661
+ webSearchSearxngUrlConfigured: this.settingsManager.isWebSearchSearxngUrlConfigured(),
5662
+ githubToolsNetworkEnabled: this.settingsManager.getGithubToolsNetworkEnabled(),
5663
+ githubToolsTokenConfigured: this.settingsManager.isGithubToolsTokenConfigured(),
5337
5664
  }, {
5338
5665
  onAutoCompactChange: (enabled) => {
5339
5666
  this.session.setAutoCompactionEnabled(enabled);
@@ -5370,6 +5697,103 @@ export class InteractiveMode {
5370
5697
  this.settingsManager.setTransport(transport);
5371
5698
  this.session.agent.setTransport(transport);
5372
5699
  },
5700
+ onWebSearchEnabledChange: (enabled) => {
5701
+ this.settingsManager.setWebSearchEnabled(enabled);
5702
+ },
5703
+ onWebSearchProviderModeChange: (mode) => {
5704
+ this.settingsManager.setWebSearchProviderMode(mode);
5705
+ },
5706
+ onWebSearchFallbackModeChange: (mode) => {
5707
+ this.settingsManager.setWebSearchFallbackMode(mode);
5708
+ },
5709
+ onWebSearchSafeSearchChange: (mode) => {
5710
+ this.settingsManager.setWebSearchSafeSearch(mode);
5711
+ },
5712
+ onWebSearchMaxResultsChange: (maxResults) => {
5713
+ this.settingsManager.setWebSearchMaxResults(maxResults);
5714
+ },
5715
+ onWebSearchTimeoutSecondsChange: (timeoutSeconds) => {
5716
+ this.settingsManager.setWebSearchTimeoutSeconds(timeoutSeconds);
5717
+ },
5718
+ onWebSearchTavilyApiKeyAction: async (action) => {
5719
+ if (action === "clear") {
5720
+ this.settingsManager.setWebSearchTavilyApiKey(undefined);
5721
+ await this.settingsManager.flush();
5722
+ this.showStatus("Web Search Tool: Tavily API key cleared.");
5723
+ return "not configured";
5724
+ }
5725
+ const current = this.settingsManager.getWebSearchTavilyApiKey();
5726
+ const entered = await this.showExtensionInput("Web Search Tool: Tavily API key", current ?? "tvly-...");
5727
+ if (entered === undefined) {
5728
+ return this.settingsManager.isWebSearchTavilyApiKeyConfigured() ? "configured" : "not configured";
5729
+ }
5730
+ const normalized = entered.trim();
5731
+ if (!normalized) {
5732
+ this.showWarning("Tavily API key cannot be empty.");
5733
+ return this.settingsManager.isWebSearchTavilyApiKeyConfigured() ? "configured" : "not configured";
5734
+ }
5735
+ this.settingsManager.setWebSearchTavilyApiKey(normalized);
5736
+ await this.settingsManager.flush();
5737
+ this.showStatus("Web Search Tool: Tavily API key saved.");
5738
+ return "configured";
5739
+ },
5740
+ onWebSearchSearxngUrlAction: async (action) => {
5741
+ if (action === "clear") {
5742
+ this.settingsManager.setWebSearchSearxngUrl(undefined);
5743
+ await this.settingsManager.flush();
5744
+ this.showStatus("Web Search Tool: SearXNG base URL cleared.");
5745
+ return "not configured";
5746
+ }
5747
+ const current = this.settingsManager.getWebSearchSearxngUrl();
5748
+ const entered = await this.showExtensionInput("Web Search Tool: SearXNG base URL", current ?? "https://searx.example");
5749
+ if (entered === undefined) {
5750
+ return this.settingsManager.isWebSearchSearxngUrlConfigured() ? "configured" : "not configured";
5751
+ }
5752
+ const normalized = entered.trim();
5753
+ if (!normalized) {
5754
+ this.showWarning("SearXNG base URL cannot be empty.");
5755
+ return this.settingsManager.isWebSearchSearxngUrlConfigured() ? "configured" : "not configured";
5756
+ }
5757
+ try {
5758
+ const parsed = new URL(normalized);
5759
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
5760
+ throw new Error("SearXNG URL must use http or https.");
5761
+ }
5762
+ }
5763
+ catch (error) {
5764
+ this.showWarning(error instanceof Error ? error.message : "Invalid SearXNG URL.");
5765
+ return this.settingsManager.isWebSearchSearxngUrlConfigured() ? "configured" : "not configured";
5766
+ }
5767
+ this.settingsManager.setWebSearchSearxngUrl(normalized);
5768
+ await this.settingsManager.flush();
5769
+ this.showStatus("Web Search Tool: SearXNG base URL saved.");
5770
+ return "configured";
5771
+ },
5772
+ onGithubToolsNetworkEnabledChange: (enabled) => {
5773
+ this.settingsManager.setGithubToolsNetworkEnabled(enabled);
5774
+ },
5775
+ onGithubToolsTokenAction: async (action) => {
5776
+ if (action === "clear") {
5777
+ this.settingsManager.setGithubToolsToken(undefined);
5778
+ await this.settingsManager.flush();
5779
+ this.showStatus("Github tools: token cleared.");
5780
+ return "not configured";
5781
+ }
5782
+ const current = this.settingsManager.getGithubToolsToken();
5783
+ const entered = await this.showExtensionInput("Github tools: token", current ?? "ghp_...");
5784
+ if (entered === undefined) {
5785
+ return this.settingsManager.isGithubToolsTokenConfigured() ? "configured" : "not configured";
5786
+ }
5787
+ const normalized = entered.trim();
5788
+ if (!normalized) {
5789
+ this.showWarning("GitHub token cannot be empty.");
5790
+ return this.settingsManager.isGithubToolsTokenConfigured() ? "configured" : "not configured";
5791
+ }
5792
+ this.settingsManager.setGithubToolsToken(normalized);
5793
+ await this.settingsManager.flush();
5794
+ this.showStatus("Github tools: token saved.");
5795
+ return "configured";
5796
+ },
5373
5797
  onThinkingLevelChange: (level) => {
5374
5798
  this.session.setThinkingLevel(level);
5375
5799
  this.footer.invalidate();
@@ -5380,7 +5804,7 @@ export class InteractiveMode {
5380
5804
  this.settingsManager.setTheme(themeName);
5381
5805
  this.ui.invalidate();
5382
5806
  if (!result.success) {
5383
- this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
5807
+ this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to universal theme.`);
5384
5808
  }
5385
5809
  },
5386
5810
  onThemePreview: (themeName) => {
@@ -8603,7 +9027,9 @@ export class InteractiveMode {
8603
9027
  const mcpConnected = mcpStatuses.filter((status) => status.state === "connected").length;
8604
9028
  const mcpErrored = mcpStatuses.filter((status) => status.state === "error").length;
8605
9029
  const mcpDisabled = mcpStatuses.filter((status) => !status.enabled).length;
8606
- const cliToolStatuses = resolveDoctorCliToolStatuses();
9030
+ const resolveCliToolStatuses = this.resolveDoctorCliToolStatuses ??
9031
+ resolveDoctorCliToolStatuses;
9032
+ const cliToolStatuses = resolveCliToolStatuses();
8607
9033
  const missingCliTools = cliToolStatuses.filter((status) => !status.available).map((status) => status.tool);
8608
9034
  let semanticStatus;
8609
9035
  let semanticStatusError;
@@ -9069,6 +9495,41 @@ export class InteractiveMode {
9069
9495
  return !(message.stopReason === "aborted" && message.content.length === 0);
9070
9496
  });
9071
9497
  }
9498
+ async maybeRequestAgentContinuation(originalUserInput, turnStartMessageCount) {
9499
+ const recentAssistant = this.session.messages
9500
+ .slice(turnStartMessageCount)
9501
+ .reverse()
9502
+ .find((message) => message.role === "assistant");
9503
+ if (!recentAssistant) {
9504
+ return undefined;
9505
+ }
9506
+ const resumeReason = detectAssistantResumeReason(recentAssistant);
9507
+ if (!resumeReason) {
9508
+ return undefined;
9509
+ }
9510
+ const selected = await this.showExtensionSelector(buildAssistantContinuationSelectorTitle(resumeReason), [
9511
+ "1. Yes, continue agent work",
9512
+ "2. Repeat my original request",
9513
+ "3. No, keep this session",
9514
+ "4. Start a new session",
9515
+ ]);
9516
+ if (selected === "4. Start a new session") {
9517
+ return { action: "new_session" };
9518
+ }
9519
+ if (selected === "2. Repeat my original request") {
9520
+ return { action: "repeat_request", promptText: originalUserInput };
9521
+ }
9522
+ if (selected !== "1. Yes, continue agent work") {
9523
+ return { action: "stay" };
9524
+ }
9525
+ return {
9526
+ action: "resume",
9527
+ promptText: buildAssistantResumePrompt({
9528
+ reason: resumeReason,
9529
+ originalPrompt: originalUserInput,
9530
+ }),
9531
+ };
9532
+ }
9072
9533
  sanitizeAssistantDisplayMessage(message) {
9073
9534
  const hideAllTextForOrchestration = this.activeAssistantOrchestrationContext;
9074
9535
  let changed = false;
@@ -9111,7 +9572,157 @@ export class InteractiveMode {
9111
9572
  normalized === "/capabilities" ||
9112
9573
  normalized === "capabilities");
9113
9574
  }
9575
+ async showProtocolRepairRecoverySelector(reason) {
9576
+ const title = reason === "silent_stop"
9577
+ ? "Model returned empty/non-actionable responses after auto-repair.\nChoose what to do next:"
9578
+ : "Model emitted pseudo tool-call markup after auto-repair.\nChoose what to do next:";
9579
+ const retryNow = "1. Retry now (Recommended)";
9580
+ const repeatOriginal = "2. Repeat original request";
9581
+ const switchModel = "3. Switch model and retry";
9582
+ const keepSession = "4. Keep session";
9583
+ const selected = await this.showExtensionSelector(title, [retryNow, repeatOriginal, switchModel, keepSession]);
9584
+ if (!selected || selected === keepSession)
9585
+ return "keep_session";
9586
+ if (selected === repeatOriginal)
9587
+ return "repeat_original";
9588
+ if (selected === switchModel)
9589
+ return "switch_model_retry";
9590
+ return "retry_now";
9591
+ }
9592
+ async showModelSelectorForImmediateRetry(initialSearchInput, providerFilter) {
9593
+ return await new Promise((resolve) => {
9594
+ let settled = false;
9595
+ const settle = (model) => {
9596
+ if (settled)
9597
+ return;
9598
+ settled = true;
9599
+ resolve(model);
9600
+ };
9601
+ this.showSelector((done) => {
9602
+ const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, async (model) => {
9603
+ try {
9604
+ await this.session.setModel(model);
9605
+ this.footer.invalidate();
9606
+ this.updateEditorBorderColor();
9607
+ this.refreshBuiltInHeader();
9608
+ done();
9609
+ this.showStatus(`Model: ${model.provider}/${model.id}`);
9610
+ this.checkDaxnutsEasterEgg(model);
9611
+ settle(model);
9612
+ }
9613
+ catch (error) {
9614
+ done();
9615
+ this.showError(error instanceof Error ? error.message : String(error));
9616
+ settle(undefined);
9617
+ }
9618
+ }, () => {
9619
+ done();
9620
+ this.ui.requestRender();
9621
+ settle(undefined);
9622
+ }, initialSearchInput, providerFilter);
9623
+ return { component: selector, focus: selector };
9624
+ });
9625
+ });
9626
+ }
9627
+ async selectModelForImmediateRetry(preferredProvider) {
9628
+ await this.hydrateMissingProviderModelsForSavedAuth();
9629
+ this.session.modelRegistry.refresh();
9630
+ let models = [];
9631
+ try {
9632
+ models = await this.session.modelRegistry.getAvailable();
9633
+ }
9634
+ catch {
9635
+ models = [];
9636
+ }
9637
+ if (models.length === 0) {
9638
+ this.showStatus("No models available");
9639
+ return undefined;
9640
+ }
9641
+ const providerCounts = new Map();
9642
+ for (const model of models) {
9643
+ providerCounts.set(model.provider, (providerCounts.get(model.provider) ?? 0) + 1);
9644
+ }
9645
+ const providers = Array.from(providerCounts.entries()).sort(([a], [b]) => a.localeCompare(b));
9646
+ if (providers.length === 0) {
9647
+ this.showStatus("No providers available");
9648
+ return undefined;
9649
+ }
9650
+ if (preferredProvider) {
9651
+ const preferred = providers.find(([provider]) => provider.toLowerCase() === preferredProvider.toLowerCase());
9652
+ if (preferred) {
9653
+ return await this.showModelSelectorForImmediateRetry(undefined, preferred[0]);
9654
+ }
9655
+ }
9656
+ if (providers.length === 1) {
9657
+ return await this.showModelSelectorForImmediateRetry(undefined, providers[0]?.[0]);
9658
+ }
9659
+ const optionMap = new Map();
9660
+ const options = ["All providers"];
9661
+ for (const [provider, count] of providers) {
9662
+ const optionLabel = `${provider} (${count})`;
9663
+ optionMap.set(optionLabel, provider);
9664
+ options.push(optionLabel);
9665
+ }
9666
+ const selected = await this.showExtensionSelector("/model: choose provider for retry", options);
9667
+ if (!selected)
9668
+ return undefined;
9669
+ if (selected === "All providers") {
9670
+ return await this.showModelSelectorForImmediateRetry();
9671
+ }
9672
+ const provider = optionMap.get(selected);
9673
+ if (!provider) {
9674
+ this.showWarning("Provider selection is no longer available.");
9675
+ return undefined;
9676
+ }
9677
+ return await this.showModelSelectorForImmediateRetry(undefined, provider);
9678
+ }
9114
9679
  async promptWithTaskFallback(userInput) {
9680
+ const handleProtocolRepairApplied = (reason) => {
9681
+ const showWarning = this.showWarning;
9682
+ if (typeof showWarning === "function") {
9683
+ const message = reason === "silent_stop"
9684
+ ? "Protocol auto-repair: model returned an empty stop response; retrying once."
9685
+ : "Protocol auto-repair: model emitted raw tool-call markup; retrying once.";
9686
+ showWarning.call(this, message);
9687
+ }
9688
+ };
9689
+ const runPromptWithProtocolRecovery = async (promptText, promptOptions) => {
9690
+ let nextPrompt = promptText;
9691
+ let nextOptions = promptOptions;
9692
+ for (let recoveryAttempt = 0; recoveryAttempt < 3; recoveryAttempt += 1) {
9693
+ let exhaustedReason;
9694
+ await promptWithRawToolProtocolRepair({
9695
+ session: this.session,
9696
+ promptText: nextPrompt,
9697
+ promptOptions: nextOptions,
9698
+ onRepairApplied: handleProtocolRepairApplied,
9699
+ onRepairExhausted: async (reason) => {
9700
+ exhaustedReason = reason;
9701
+ },
9702
+ });
9703
+ if (!exhaustedReason) {
9704
+ return;
9705
+ }
9706
+ const selectedAction = await this.showProtocolRepairRecoverySelector(exhaustedReason);
9707
+ if (selectedAction === "keep_session") {
9708
+ return;
9709
+ }
9710
+ if (selectedAction === "repeat_original") {
9711
+ nextPrompt = userInput;
9712
+ nextOptions = undefined;
9713
+ continue;
9714
+ }
9715
+ if (selectedAction === "switch_model_retry") {
9716
+ const selectedModel = await this.selectModelForImmediateRetry();
9717
+ if (!selectedModel) {
9718
+ return;
9719
+ }
9720
+ continue;
9721
+ }
9722
+ // retry_now: rerun current prompt/options
9723
+ }
9724
+ this.showWarning("Protocol auto-repair exhausted repeatedly. Keep session as-is and retry with a different model or a simpler prompt.");
9725
+ };
9115
9726
  const mentionedAgent = this.resolveMentionedAgent(userInput);
9116
9727
  if (mentionedAgent) {
9117
9728
  const cleaned = userInput.replace(/(?:^|\s)@[^\s]+/g, " ").trim();
@@ -9137,7 +9748,7 @@ export class InteractiveMode {
9137
9748
  "Answer normally and concisely in plain language. Do not run task tool for this query.",
9138
9749
  "</agent_capability_query>",
9139
9750
  ].join("\n");
9140
- await this.session.prompt(capabilityPrompt, {
9751
+ await runPromptWithProtocolRecovery(capabilityPrompt, {
9141
9752
  expandPromptTemplates: false,
9142
9753
  source: "interactive",
9143
9754
  });
@@ -9168,7 +9779,7 @@ export class InteractiveMode {
9168
9779
  : []),
9169
9780
  "</orchestrate>",
9170
9781
  ].join("\n");
9171
- await this.session.prompt(mentionPrompt, {
9782
+ await runPromptWithProtocolRecovery(mentionPrompt, {
9172
9783
  expandPromptTemplates: false,
9173
9784
  source: "interactive",
9174
9785
  });
@@ -9201,7 +9812,7 @@ export class InteractiveMode {
9201
9812
  });
9202
9813
  return;
9203
9814
  }
9204
- await this.session.prompt(userInput);
9815
+ await runPromptWithProtocolRecovery(userInput);
9205
9816
  }
9206
9817
  createIosmVerificationEventBridge(options) {
9207
9818
  const loaderMessage = options?.loaderMessage ?? `Verifying workspace... (${appKey(this.keybindings, "interrupt")} to interrupt)`;
@@ -12794,7 +13405,7 @@ The agent will automatically receive IOSM context on every turn.`;
12794
13405
  const themeName = this.settingsManager.getTheme();
12795
13406
  const themeResult = themeName ? setTheme(themeName, true) : { success: true };
12796
13407
  if (!themeResult.success) {
12797
- this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to dark theme.`);
13408
+ this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to universal theme.`);
12798
13409
  }
12799
13410
  const editorPaddingX = this.settingsManager.getEditorPaddingX();
12800
13411
  const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();