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.
- package/CHANGELOG.md +61 -1
- package/README.md +4 -4
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +12 -4
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-profiles.d.ts.map +1 -1
- package/dist/core/agent-profiles.js +15 -2
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-session.d.ts +3 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +214 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +7 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +57 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +197 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/shadow-guard.d.ts.map +1 -1
- package/dist/core/shadow-guard.js +12 -1
- package/dist/core/shadow-guard.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +109 -4
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/db-run.d.ts +84 -0
- package/dist/core/tools/db-run.d.ts.map +1 -0
- package/dist/core/tools/db-run.js +690 -0
- package/dist/core/tools/db-run.js.map +1 -0
- package/dist/core/tools/git-common.d.ts +45 -0
- package/dist/core/tools/git-common.d.ts.map +1 -0
- package/dist/core/tools/git-common.js +185 -0
- package/dist/core/tools/git-common.js.map +1 -0
- package/dist/core/tools/git-read.d.ts +15 -13
- package/dist/core/tools/git-read.d.ts.map +1 -1
- package/dist/core/tools/git-read.js +101 -153
- package/dist/core/tools/git-read.js.map +1 -1
- package/dist/core/tools/git-write.d.ts +75 -0
- package/dist/core/tools/git-write.d.ts.map +1 -0
- package/dist/core/tools/git-write.js +298 -0
- package/dist/core/tools/git-write.js.map +1 -0
- package/dist/core/tools/index.d.ts +91 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +26 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/lint-run.d.ts +42 -0
- package/dist/core/tools/lint-run.d.ts.map +1 -0
- package/dist/core/tools/lint-run.js +276 -0
- package/dist/core/tools/lint-run.js.map +1 -0
- package/dist/core/tools/task.js +1 -1
- package/dist/core/tools/task.js.map +1 -1
- package/dist/core/tools/test-run.d.ts +36 -0
- package/dist/core/tools/test-run.d.ts.map +1 -0
- package/dist/core/tools/test-run.js +255 -0
- package/dist/core/tools/test-run.js.map +1 -0
- package/dist/core/tools/typecheck-run.d.ts +44 -0
- package/dist/core/tools/typecheck-run.d.ts.map +1 -0
- package/dist/core/tools/typecheck-run.js +343 -0
- package/dist/core/tools/typecheck-run.js.map +1 -0
- package/dist/core/tools/verification-runner.d.ts +53 -0
- package/dist/core/tools/verification-runner.d.ts.map +1 -0
- package/dist/core/tools/verification-runner.js +235 -0
- package/dist/core/tools/verification-runner.js.map +1 -0
- package/dist/core/tools/web-search.d.ts +72 -0
- package/dist/core/tools/web-search.d.ts.map +1 -0
- package/dist/core/tools/web-search.js +702 -0
- package/dist/core/tools/web-search.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +2 -1
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +2 -1
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +7 -2
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +2 -1
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/mcp-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/mcp-selector.js +3 -1
- package/dist/modes/interactive/components/mcp-selector.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +12 -2
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +11 -0
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +16 -5
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +4 -2
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +25 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +182 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.js +7 -2
- package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +4 -2
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/subagent-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/subagent-message.js +3 -1
- package/dist/modes/interactive/components/subagent-message.js.map +1 -1
- package/dist/modes/interactive/components/task-plan-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/task-plan-message.js +2 -1
- package/dist/modes/interactive/components/task-plan-message.js.map +1 -1
- package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/theme-selector.js +7 -2
- package/dist/modes/interactive/components/theme-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +7 -2
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +25 -7
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +18 -3
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +8 -0
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +2 -1
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +622 -11
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +39 -38
- package/dist/modes/interactive/theme/light.json +29 -29
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +16 -25
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/interactive/theme/universal.json +85 -0
- package/docs/cli-reference.md +32 -2
- package/docs/configuration.md +86 -2
- package/docs/development-and-testing.md +1 -1
- package/docs/interactive-mode.md +8 -3
- package/docs/rpc-json-sdk.md +1 -1
- 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
|
-
|
|
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.
|
|
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.
|
|
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() || "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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();
|