iosm-cli 0.2.8 → 0.2.10

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 (100) hide show
  1. package/CHANGELOG.md +66 -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 +8 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +490 -3
  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/slash-commands.d.ts.map +1 -1
  25. package/dist/core/slash-commands.js +4 -0
  26. package/dist/core/slash-commands.js.map +1 -1
  27. package/dist/core/system-prompt.d.ts.map +1 -1
  28. package/dist/core/system-prompt.js +32 -1
  29. package/dist/core/system-prompt.js.map +1 -1
  30. package/dist/core/tools/db-run.d.ts +84 -0
  31. package/dist/core/tools/db-run.d.ts.map +1 -0
  32. package/dist/core/tools/db-run.js +690 -0
  33. package/dist/core/tools/db-run.js.map +1 -0
  34. package/dist/core/tools/index.d.ts +44 -0
  35. package/dist/core/tools/index.d.ts.map +1 -1
  36. package/dist/core/tools/index.js +16 -0
  37. package/dist/core/tools/index.js.map +1 -1
  38. package/dist/core/tools/lint-run.d.ts +42 -0
  39. package/dist/core/tools/lint-run.d.ts.map +1 -0
  40. package/dist/core/tools/lint-run.js +276 -0
  41. package/dist/core/tools/lint-run.js.map +1 -0
  42. package/dist/core/tools/test-run.d.ts +36 -0
  43. package/dist/core/tools/test-run.d.ts.map +1 -0
  44. package/dist/core/tools/test-run.js +255 -0
  45. package/dist/core/tools/test-run.js.map +1 -0
  46. package/dist/core/tools/typecheck-run.d.ts +44 -0
  47. package/dist/core/tools/typecheck-run.d.ts.map +1 -0
  48. package/dist/core/tools/typecheck-run.js +343 -0
  49. package/dist/core/tools/typecheck-run.js.map +1 -0
  50. package/dist/core/tools/verification-runner.d.ts +53 -0
  51. package/dist/core/tools/verification-runner.d.ts.map +1 -0
  52. package/dist/core/tools/verification-runner.js +235 -0
  53. package/dist/core/tools/verification-runner.js.map +1 -0
  54. package/dist/core/ultrathink.d.ts +122 -0
  55. package/dist/core/ultrathink.d.ts.map +1 -0
  56. package/dist/core/ultrathink.js +621 -0
  57. package/dist/core/ultrathink.js.map +1 -0
  58. package/dist/index.d.ts +2 -2
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  63. package/dist/modes/interactive/components/branch-summary-message.js +2 -1
  64. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  65. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  66. package/dist/modes/interactive/components/compaction-summary-message.js +2 -1
  67. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  68. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/custom-message.js +2 -1
  70. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  71. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  72. package/dist/modes/interactive/components/skill-invocation-message.js +4 -2
  73. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  74. package/dist/modes/interactive/components/subagent-message.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/subagent-message.js +3 -1
  76. package/dist/modes/interactive/components/subagent-message.js.map +1 -1
  77. package/dist/modes/interactive/components/task-plan-message.d.ts.map +1 -1
  78. package/dist/modes/interactive/components/task-plan-message.js +2 -1
  79. package/dist/modes/interactive/components/task-plan-message.js.map +1 -1
  80. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  81. package/dist/modes/interactive/components/tool-execution.js +25 -7
  82. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  83. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  84. package/dist/modes/interactive/components/user-message.js +2 -1
  85. package/dist/modes/interactive/components/user-message.js.map +1 -1
  86. package/dist/modes/interactive/interactive-mode.d.ts +5 -0
  87. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  88. package/dist/modes/interactive/interactive-mode.js +523 -9
  89. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  90. package/dist/modes/interactive/theme/dark.json +39 -38
  91. package/dist/modes/interactive/theme/light.json +29 -29
  92. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  93. package/dist/modes/interactive/theme/theme.js +16 -25
  94. package/dist/modes/interactive/theme/theme.js.map +1 -1
  95. package/dist/modes/interactive/theme/universal.json +85 -0
  96. package/docs/cli-reference.md +21 -1
  97. package/docs/configuration.md +76 -1
  98. package/docs/development-and-testing.md +1 -1
  99. package/docs/interactive-mode.md +11 -2
  100. 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"],
@@ -1349,6 +1629,31 @@ export class InteractiveMode {
1349
1629
  }
1350
1630
  return null;
1351
1631
  }
1632
+ getUltrathinkArgumentCompletions(prefix) {
1633
+ const hasTrailingSpace = /\\s$/.test(prefix);
1634
+ const tokens = this.parseSlashArgs(prefix);
1635
+ const queryToken = hasTrailingSpace ? "" : (tokens[tokens.length - 1] ?? "");
1636
+ if (tokens.length === 0) {
1637
+ return [
1638
+ { value: "-q", label: "-q", description: `Iterations (default 5, max 12)` },
1639
+ { value: "--iterations", label: "--iterations", description: "Same as -q" },
1640
+ ];
1641
+ }
1642
+ const previousToken = tokens[tokens.length - 2];
1643
+ if (previousToken === "-q" || previousToken === "--iterations") {
1644
+ const values = ["3", "5", "7", "10", "12"];
1645
+ return values
1646
+ .filter((value) => value.startsWith(queryToken))
1647
+ .map((value) => ({ value, label: value, description: "iteration count" }));
1648
+ }
1649
+ if (queryToken.startsWith("-")) {
1650
+ return [
1651
+ { value: "-q", label: "-q", description: `Iterations (default 5, max 12)` },
1652
+ { value: "--iterations", label: "--iterations", description: "Same as -q" },
1653
+ ].filter((item) => item.value.startsWith(queryToken));
1654
+ }
1655
+ return null;
1656
+ }
1352
1657
  setupAutocomplete(fdPath) {
1353
1658
  // Define commands for autocomplete
1354
1659
  const builtinCommands = BUILTIN_SLASH_COMMANDS.filter((command) => this.activeProfileName === "iosm" || !IOSM_PROFILE_ONLY_COMMANDS.has(command.name));
@@ -1406,6 +1711,10 @@ export class InteractiveMode {
1406
1711
  if (swarmCommand) {
1407
1712
  swarmCommand.getArgumentCompletions = (prefix) => this.getSwarmArgumentCompletions(prefix);
1408
1713
  }
1714
+ const ultrathinkCommand = slashCommands.find((command) => command.name === "ultrathink");
1715
+ if (ultrathinkCommand) {
1716
+ ultrathinkCommand.getArgumentCompletions = (prefix) => this.getUltrathinkArgumentCompletions(prefix);
1717
+ }
1409
1718
  // Convert prompt templates to SlashCommand format for autocomplete
1410
1719
  const templateCommands = this.session.promptTemplates.map((cmd) => ({
1411
1720
  name: cmd.name,
@@ -1784,7 +2093,25 @@ export class InteractiveMode {
1784
2093
  while (true) {
1785
2094
  const userInput = await this.getUserInput();
1786
2095
  try {
1787
- await this.promptWithTaskFallback(userInput);
2096
+ let promptText = userInput;
2097
+ let continuationPrompts = 0;
2098
+ while (true) {
2099
+ const turnStartMessageCount = this.session.messages.length;
2100
+ await this.promptWithTaskFallback(promptText);
2101
+ if (continuationPrompts >= MAX_ASSISTANT_CONTINUATION_PROMPTS_PER_TURN) {
2102
+ break;
2103
+ }
2104
+ const continuationDecision = await this.maybeRequestAgentContinuation(userInput, turnStartMessageCount);
2105
+ if (!continuationDecision || continuationDecision.action === "stay") {
2106
+ break;
2107
+ }
2108
+ if (continuationDecision.action === "new_session") {
2109
+ await this.handleClearCommand();
2110
+ break;
2111
+ }
2112
+ continuationPrompts += 1;
2113
+ promptText = continuationDecision.promptText;
2114
+ }
1788
2115
  }
1789
2116
  catch (error) {
1790
2117
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -5342,7 +5669,7 @@ export class InteractiveMode {
5342
5669
  transport: this.settingsManager.getTransport(),
5343
5670
  thinkingLevel: this.session.thinkingLevel,
5344
5671
  availableThinkingLevels: this.session.getAvailableThinkingLevels(),
5345
- currentTheme: this.settingsManager.getTheme() || "dark",
5672
+ currentTheme: this.settingsManager.getTheme() || "universal",
5346
5673
  availableThemes: getAvailableThemes(),
5347
5674
  hideThinkingBlock: this.hideThinkingBlock,
5348
5675
  collapseChangelog: this.settingsManager.getCollapseChangelog(),
@@ -5506,7 +5833,7 @@ export class InteractiveMode {
5506
5833
  this.settingsManager.setTheme(themeName);
5507
5834
  this.ui.invalidate();
5508
5835
  if (!result.success) {
5509
- this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
5836
+ this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to universal theme.`);
5510
5837
  }
5511
5838
  },
5512
5839
  onThemePreview: (themeName) => {
@@ -8729,7 +9056,9 @@ export class InteractiveMode {
8729
9056
  const mcpConnected = mcpStatuses.filter((status) => status.state === "connected").length;
8730
9057
  const mcpErrored = mcpStatuses.filter((status) => status.state === "error").length;
8731
9058
  const mcpDisabled = mcpStatuses.filter((status) => !status.enabled).length;
8732
- const cliToolStatuses = resolveDoctorCliToolStatuses();
9059
+ const resolveCliToolStatuses = this.resolveDoctorCliToolStatuses ??
9060
+ resolveDoctorCliToolStatuses;
9061
+ const cliToolStatuses = resolveCliToolStatuses();
8733
9062
  const missingCliTools = cliToolStatuses.filter((status) => !status.available).map((status) => status.tool);
8734
9063
  let semanticStatus;
8735
9064
  let semanticStatusError;
@@ -9195,6 +9524,41 @@ export class InteractiveMode {
9195
9524
  return !(message.stopReason === "aborted" && message.content.length === 0);
9196
9525
  });
9197
9526
  }
9527
+ async maybeRequestAgentContinuation(originalUserInput, turnStartMessageCount) {
9528
+ const recentAssistant = this.session.messages
9529
+ .slice(turnStartMessageCount)
9530
+ .reverse()
9531
+ .find((message) => message.role === "assistant");
9532
+ if (!recentAssistant) {
9533
+ return undefined;
9534
+ }
9535
+ const resumeReason = detectAssistantResumeReason(recentAssistant);
9536
+ if (!resumeReason) {
9537
+ return undefined;
9538
+ }
9539
+ const selected = await this.showExtensionSelector(buildAssistantContinuationSelectorTitle(resumeReason), [
9540
+ "1. Yes, continue agent work",
9541
+ "2. Repeat my original request",
9542
+ "3. No, keep this session",
9543
+ "4. Start a new session",
9544
+ ]);
9545
+ if (selected === "4. Start a new session") {
9546
+ return { action: "new_session" };
9547
+ }
9548
+ if (selected === "2. Repeat my original request") {
9549
+ return { action: "repeat_request", promptText: originalUserInput };
9550
+ }
9551
+ if (selected !== "1. Yes, continue agent work") {
9552
+ return { action: "stay" };
9553
+ }
9554
+ return {
9555
+ action: "resume",
9556
+ promptText: buildAssistantResumePrompt({
9557
+ reason: resumeReason,
9558
+ originalPrompt: originalUserInput,
9559
+ }),
9560
+ };
9561
+ }
9198
9562
  sanitizeAssistantDisplayMessage(message) {
9199
9563
  const hideAllTextForOrchestration = this.activeAssistantOrchestrationContext;
9200
9564
  let changed = false;
@@ -9237,7 +9601,157 @@ export class InteractiveMode {
9237
9601
  normalized === "/capabilities" ||
9238
9602
  normalized === "capabilities");
9239
9603
  }
9604
+ async showProtocolRepairRecoverySelector(reason) {
9605
+ const title = reason === "silent_stop"
9606
+ ? "Model returned empty/non-actionable responses after auto-repair.\nChoose what to do next:"
9607
+ : "Model emitted pseudo tool-call markup after auto-repair.\nChoose what to do next:";
9608
+ const retryNow = "1. Retry now (Recommended)";
9609
+ const repeatOriginal = "2. Repeat original request";
9610
+ const switchModel = "3. Switch model and retry";
9611
+ const keepSession = "4. Keep session";
9612
+ const selected = await this.showExtensionSelector(title, [retryNow, repeatOriginal, switchModel, keepSession]);
9613
+ if (!selected || selected === keepSession)
9614
+ return "keep_session";
9615
+ if (selected === repeatOriginal)
9616
+ return "repeat_original";
9617
+ if (selected === switchModel)
9618
+ return "switch_model_retry";
9619
+ return "retry_now";
9620
+ }
9621
+ async showModelSelectorForImmediateRetry(initialSearchInput, providerFilter) {
9622
+ return await new Promise((resolve) => {
9623
+ let settled = false;
9624
+ const settle = (model) => {
9625
+ if (settled)
9626
+ return;
9627
+ settled = true;
9628
+ resolve(model);
9629
+ };
9630
+ this.showSelector((done) => {
9631
+ const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, async (model) => {
9632
+ try {
9633
+ await this.session.setModel(model);
9634
+ this.footer.invalidate();
9635
+ this.updateEditorBorderColor();
9636
+ this.refreshBuiltInHeader();
9637
+ done();
9638
+ this.showStatus(`Model: ${model.provider}/${model.id}`);
9639
+ this.checkDaxnutsEasterEgg(model);
9640
+ settle(model);
9641
+ }
9642
+ catch (error) {
9643
+ done();
9644
+ this.showError(error instanceof Error ? error.message : String(error));
9645
+ settle(undefined);
9646
+ }
9647
+ }, () => {
9648
+ done();
9649
+ this.ui.requestRender();
9650
+ settle(undefined);
9651
+ }, initialSearchInput, providerFilter);
9652
+ return { component: selector, focus: selector };
9653
+ });
9654
+ });
9655
+ }
9656
+ async selectModelForImmediateRetry(preferredProvider) {
9657
+ await this.hydrateMissingProviderModelsForSavedAuth();
9658
+ this.session.modelRegistry.refresh();
9659
+ let models = [];
9660
+ try {
9661
+ models = await this.session.modelRegistry.getAvailable();
9662
+ }
9663
+ catch {
9664
+ models = [];
9665
+ }
9666
+ if (models.length === 0) {
9667
+ this.showStatus("No models available");
9668
+ return undefined;
9669
+ }
9670
+ const providerCounts = new Map();
9671
+ for (const model of models) {
9672
+ providerCounts.set(model.provider, (providerCounts.get(model.provider) ?? 0) + 1);
9673
+ }
9674
+ const providers = Array.from(providerCounts.entries()).sort(([a], [b]) => a.localeCompare(b));
9675
+ if (providers.length === 0) {
9676
+ this.showStatus("No providers available");
9677
+ return undefined;
9678
+ }
9679
+ if (preferredProvider) {
9680
+ const preferred = providers.find(([provider]) => provider.toLowerCase() === preferredProvider.toLowerCase());
9681
+ if (preferred) {
9682
+ return await this.showModelSelectorForImmediateRetry(undefined, preferred[0]);
9683
+ }
9684
+ }
9685
+ if (providers.length === 1) {
9686
+ return await this.showModelSelectorForImmediateRetry(undefined, providers[0]?.[0]);
9687
+ }
9688
+ const optionMap = new Map();
9689
+ const options = ["All providers"];
9690
+ for (const [provider, count] of providers) {
9691
+ const optionLabel = `${provider} (${count})`;
9692
+ optionMap.set(optionLabel, provider);
9693
+ options.push(optionLabel);
9694
+ }
9695
+ const selected = await this.showExtensionSelector("/model: choose provider for retry", options);
9696
+ if (!selected)
9697
+ return undefined;
9698
+ if (selected === "All providers") {
9699
+ return await this.showModelSelectorForImmediateRetry();
9700
+ }
9701
+ const provider = optionMap.get(selected);
9702
+ if (!provider) {
9703
+ this.showWarning("Provider selection is no longer available.");
9704
+ return undefined;
9705
+ }
9706
+ return await this.showModelSelectorForImmediateRetry(undefined, provider);
9707
+ }
9240
9708
  async promptWithTaskFallback(userInput) {
9709
+ const handleProtocolRepairApplied = (reason) => {
9710
+ const showWarning = this.showWarning;
9711
+ if (typeof showWarning === "function") {
9712
+ const message = reason === "silent_stop"
9713
+ ? "Protocol auto-repair: model returned an empty stop response; retrying once."
9714
+ : "Protocol auto-repair: model emitted raw tool-call markup; retrying once.";
9715
+ showWarning.call(this, message);
9716
+ }
9717
+ };
9718
+ const runPromptWithProtocolRecovery = async (promptText, promptOptions) => {
9719
+ let nextPrompt = promptText;
9720
+ let nextOptions = promptOptions;
9721
+ for (let recoveryAttempt = 0; recoveryAttempt < 3; recoveryAttempt += 1) {
9722
+ let exhaustedReason;
9723
+ await promptWithRawToolProtocolRepair({
9724
+ session: this.session,
9725
+ promptText: nextPrompt,
9726
+ promptOptions: nextOptions,
9727
+ onRepairApplied: handleProtocolRepairApplied,
9728
+ onRepairExhausted: async (reason) => {
9729
+ exhaustedReason = reason;
9730
+ },
9731
+ });
9732
+ if (!exhaustedReason) {
9733
+ return;
9734
+ }
9735
+ const selectedAction = await this.showProtocolRepairRecoverySelector(exhaustedReason);
9736
+ if (selectedAction === "keep_session") {
9737
+ return;
9738
+ }
9739
+ if (selectedAction === "repeat_original") {
9740
+ nextPrompt = userInput;
9741
+ nextOptions = undefined;
9742
+ continue;
9743
+ }
9744
+ if (selectedAction === "switch_model_retry") {
9745
+ const selectedModel = await this.selectModelForImmediateRetry();
9746
+ if (!selectedModel) {
9747
+ return;
9748
+ }
9749
+ continue;
9750
+ }
9751
+ // retry_now: rerun current prompt/options
9752
+ }
9753
+ this.showWarning("Protocol auto-repair exhausted repeatedly. Keep session as-is and retry with a different model or a simpler prompt.");
9754
+ };
9241
9755
  const mentionedAgent = this.resolveMentionedAgent(userInput);
9242
9756
  if (mentionedAgent) {
9243
9757
  const cleaned = userInput.replace(/(?:^|\s)@[^\s]+/g, " ").trim();
@@ -9263,7 +9777,7 @@ export class InteractiveMode {
9263
9777
  "Answer normally and concisely in plain language. Do not run task tool for this query.",
9264
9778
  "</agent_capability_query>",
9265
9779
  ].join("\n");
9266
- await this.session.prompt(capabilityPrompt, {
9780
+ await runPromptWithProtocolRecovery(capabilityPrompt, {
9267
9781
  expandPromptTemplates: false,
9268
9782
  source: "interactive",
9269
9783
  });
@@ -9294,7 +9808,7 @@ export class InteractiveMode {
9294
9808
  : []),
9295
9809
  "</orchestrate>",
9296
9810
  ].join("\n");
9297
- await this.session.prompt(mentionPrompt, {
9811
+ await runPromptWithProtocolRecovery(mentionPrompt, {
9298
9812
  expandPromptTemplates: false,
9299
9813
  source: "interactive",
9300
9814
  });
@@ -9327,7 +9841,7 @@ export class InteractiveMode {
9327
9841
  });
9328
9842
  return;
9329
9843
  }
9330
- await this.session.prompt(userInput);
9844
+ await runPromptWithProtocolRecovery(userInput);
9331
9845
  }
9332
9846
  createIosmVerificationEventBridge(options) {
9333
9847
  const loaderMessage = options?.loaderMessage ?? `Verifying workspace... (${appKey(this.keybindings, "interrupt")} to interrupt)`;
@@ -12920,7 +13434,7 @@ The agent will automatically receive IOSM context on every turn.`;
12920
13434
  const themeName = this.settingsManager.getTheme();
12921
13435
  const themeResult = themeName ? setTheme(themeName, true) : { success: true };
12922
13436
  if (!themeResult.success) {
12923
- this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to dark theme.`);
13437
+ this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to universal theme.`);
12924
13438
  }
12925
13439
  const editorPaddingX = this.settingsManager.getEditorPaddingX();
12926
13440
  const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();