iosm-cli 0.2.8 → 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 (93) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +3 -3
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +7 -3
  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 +5 -1
  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 +188 -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 +18 -0
  18. package/dist/core/settings-manager.d.ts.map +1 -1
  19. package/dist/core/settings-manager.js +29 -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 +32 -1
  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/index.d.ts +44 -0
  32. package/dist/core/tools/index.d.ts.map +1 -1
  33. package/dist/core/tools/index.js +16 -0
  34. package/dist/core/tools/index.js.map +1 -1
  35. package/dist/core/tools/lint-run.d.ts +42 -0
  36. package/dist/core/tools/lint-run.d.ts.map +1 -0
  37. package/dist/core/tools/lint-run.js +276 -0
  38. package/dist/core/tools/lint-run.js.map +1 -0
  39. package/dist/core/tools/test-run.d.ts +36 -0
  40. package/dist/core/tools/test-run.d.ts.map +1 -0
  41. package/dist/core/tools/test-run.js +255 -0
  42. package/dist/core/tools/test-run.js.map +1 -0
  43. package/dist/core/tools/typecheck-run.d.ts +44 -0
  44. package/dist/core/tools/typecheck-run.d.ts.map +1 -0
  45. package/dist/core/tools/typecheck-run.js +343 -0
  46. package/dist/core/tools/typecheck-run.js.map +1 -0
  47. package/dist/core/tools/verification-runner.d.ts +53 -0
  48. package/dist/core/tools/verification-runner.d.ts.map +1 -0
  49. package/dist/core/tools/verification-runner.js +235 -0
  50. package/dist/core/tools/verification-runner.js.map +1 -0
  51. package/dist/index.d.ts +2 -2
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +1 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  56. package/dist/modes/interactive/components/branch-summary-message.js +2 -1
  57. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  58. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  59. package/dist/modes/interactive/components/compaction-summary-message.js +2 -1
  60. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  61. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  62. package/dist/modes/interactive/components/custom-message.js +2 -1
  63. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  64. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  65. package/dist/modes/interactive/components/skill-invocation-message.js +4 -2
  66. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  67. package/dist/modes/interactive/components/subagent-message.d.ts.map +1 -1
  68. package/dist/modes/interactive/components/subagent-message.js +3 -1
  69. package/dist/modes/interactive/components/subagent-message.js.map +1 -1
  70. package/dist/modes/interactive/components/task-plan-message.d.ts.map +1 -1
  71. package/dist/modes/interactive/components/task-plan-message.js +2 -1
  72. package/dist/modes/interactive/components/task-plan-message.js.map +1 -1
  73. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  74. package/dist/modes/interactive/components/tool-execution.js +25 -7
  75. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  76. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  77. package/dist/modes/interactive/components/user-message.js +2 -1
  78. package/dist/modes/interactive/components/user-message.js.map +1 -1
  79. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  80. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  81. package/dist/modes/interactive/interactive-mode.js +494 -9
  82. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  83. package/dist/modes/interactive/theme/dark.json +39 -38
  84. package/dist/modes/interactive/theme/light.json +29 -29
  85. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  86. package/dist/modes/interactive/theme/theme.js +16 -25
  87. package/dist/modes/interactive/theme/theme.js.map +1 -1
  88. package/dist/modes/interactive/theme/universal.json +85 -0
  89. package/docs/cli-reference.md +16 -1
  90. package/docs/configuration.md +76 -1
  91. package/docs/development-and-testing.md +1 -1
  92. package/docs/interactive-mode.md +7 -2
  93. 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"],
@@ -1784,7 +2064,25 @@ export class InteractiveMode {
1784
2064
  while (true) {
1785
2065
  const userInput = await this.getUserInput();
1786
2066
  try {
1787
- 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
+ }
1788
2086
  }
1789
2087
  catch (error) {
1790
2088
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -5342,7 +5640,7 @@ export class InteractiveMode {
5342
5640
  transport: this.settingsManager.getTransport(),
5343
5641
  thinkingLevel: this.session.thinkingLevel,
5344
5642
  availableThinkingLevels: this.session.getAvailableThinkingLevels(),
5345
- currentTheme: this.settingsManager.getTheme() || "dark",
5643
+ currentTheme: this.settingsManager.getTheme() || "universal",
5346
5644
  availableThemes: getAvailableThemes(),
5347
5645
  hideThinkingBlock: this.hideThinkingBlock,
5348
5646
  collapseChangelog: this.settingsManager.getCollapseChangelog(),
@@ -5506,7 +5804,7 @@ export class InteractiveMode {
5506
5804
  this.settingsManager.setTheme(themeName);
5507
5805
  this.ui.invalidate();
5508
5806
  if (!result.success) {
5509
- 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.`);
5510
5808
  }
5511
5809
  },
5512
5810
  onThemePreview: (themeName) => {
@@ -8729,7 +9027,9 @@ export class InteractiveMode {
8729
9027
  const mcpConnected = mcpStatuses.filter((status) => status.state === "connected").length;
8730
9028
  const mcpErrored = mcpStatuses.filter((status) => status.state === "error").length;
8731
9029
  const mcpDisabled = mcpStatuses.filter((status) => !status.enabled).length;
8732
- const cliToolStatuses = resolveDoctorCliToolStatuses();
9030
+ const resolveCliToolStatuses = this.resolveDoctorCliToolStatuses ??
9031
+ resolveDoctorCliToolStatuses;
9032
+ const cliToolStatuses = resolveCliToolStatuses();
8733
9033
  const missingCliTools = cliToolStatuses.filter((status) => !status.available).map((status) => status.tool);
8734
9034
  let semanticStatus;
8735
9035
  let semanticStatusError;
@@ -9195,6 +9495,41 @@ export class InteractiveMode {
9195
9495
  return !(message.stopReason === "aborted" && message.content.length === 0);
9196
9496
  });
9197
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
+ }
9198
9533
  sanitizeAssistantDisplayMessage(message) {
9199
9534
  const hideAllTextForOrchestration = this.activeAssistantOrchestrationContext;
9200
9535
  let changed = false;
@@ -9237,7 +9572,157 @@ export class InteractiveMode {
9237
9572
  normalized === "/capabilities" ||
9238
9573
  normalized === "capabilities");
9239
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
+ }
9240
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
+ };
9241
9726
  const mentionedAgent = this.resolveMentionedAgent(userInput);
9242
9727
  if (mentionedAgent) {
9243
9728
  const cleaned = userInput.replace(/(?:^|\s)@[^\s]+/g, " ").trim();
@@ -9263,7 +9748,7 @@ export class InteractiveMode {
9263
9748
  "Answer normally and concisely in plain language. Do not run task tool for this query.",
9264
9749
  "</agent_capability_query>",
9265
9750
  ].join("\n");
9266
- await this.session.prompt(capabilityPrompt, {
9751
+ await runPromptWithProtocolRecovery(capabilityPrompt, {
9267
9752
  expandPromptTemplates: false,
9268
9753
  source: "interactive",
9269
9754
  });
@@ -9294,7 +9779,7 @@ export class InteractiveMode {
9294
9779
  : []),
9295
9780
  "</orchestrate>",
9296
9781
  ].join("\n");
9297
- await this.session.prompt(mentionPrompt, {
9782
+ await runPromptWithProtocolRecovery(mentionPrompt, {
9298
9783
  expandPromptTemplates: false,
9299
9784
  source: "interactive",
9300
9785
  });
@@ -9327,7 +9812,7 @@ export class InteractiveMode {
9327
9812
  });
9328
9813
  return;
9329
9814
  }
9330
- await this.session.prompt(userInput);
9815
+ await runPromptWithProtocolRecovery(userInput);
9331
9816
  }
9332
9817
  createIosmVerificationEventBridge(options) {
9333
9818
  const loaderMessage = options?.loaderMessage ?? `Verifying workspace... (${appKey(this.keybindings, "interrupt")} to interrupt)`;
@@ -12920,7 +13405,7 @@ The agent will automatically receive IOSM context on every turn.`;
12920
13405
  const themeName = this.settingsManager.getTheme();
12921
13406
  const themeResult = themeName ? setTheme(themeName, true) : { success: true };
12922
13407
  if (!themeResult.success) {
12923
- 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.`);
12924
13409
  }
12925
13410
  const editorPaddingX = this.settingsManager.getEditorPaddingX();
12926
13411
  const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();