feat(ai): make agent turn limit configurable

This commit is contained in:
Abeautifulsnow authored and GitHub committed 2026-07-23 13:57:06 +08:00
1 parent 21e9578f78
commit 9253203d99
21 files changed
+307 -5

No files matched your search

@@ -48,6 +48,7 @@ import {
} from "@/stores/settingsStore";
import { createRunStatementButtonDom, loadEditorTheme, editorFontTheme } from "@/lib/editor/editorThemes";
import { orderAiConfigsForDisplay } from "@/lib/ai/aiConfigOrdering";
import { MAX_AGENT_TURNS_DEFAULT, MAX_AGENT_TURNS_MAX, MAX_AGENT_TURNS_MIN, maxAgentTurnsOutOfRange, normalizeMaxAgentTurns } from "@/lib/ai/maxAgentTurns";
import { normalizeAiModelEffortLevels, normalizeClaudeCodeReasoningLevel } from "@/lib/ai/aiModelEffort";
import ThemeCustomizerDialog from "./ThemeCustomizerDialog.vue";
import TunnelProfileManager from "@/components/connection/TunnelProfileManager.vue";
@@ -65,6 +66,8 @@ import {
forgetWebdavSyncSecretsPassphrase,
forgetWebdavSavedPassword,
getAppSupportInfo,
loadMaxAgentTurns,
saveMaxAgentTurns,
saveWebdavSyncSecretsPreference,
saveWebdavSavedPassword,
saveSnippetSavedToken,
@@ -1954,6 +1957,7 @@ watch(activeSettingsTab, async (tab) => {
if (tab === "mcp" && !mcpStatus.value && !mcpStatusLoading.value) void refreshMcpStatus();
if (tab === "ai" && aiIsCliProvider.value) void ensureCliMcpStatus();
if (tab === "ai") {
void loadMaxAgentTurnsSetting();
// Await completion so we don't snapshot an empty default when the store
// is still loading its first payload (init via App.vue is fire-and-forget).
await promptTemplateStore.ensureLoaded();
@@ -2137,6 +2141,45 @@ function globalInstructionsTooLong(): boolean {
return promptTemplateCharacterCount(editGlobalInstructions.value) > GLOBAL_INSTRUCTIONS_MAX;
}
// Agent turn limit for DBX's API-backed agent loop. CLI providers enforce their own limits.
// Mirrors DEFAULT/MIN/MAX_MAX_AGENT_TURNS in crates/dbx-core/src/agent_loop.rs —
// keep in sync; the backend clamp on save/load is the actual source of truth.
const editMaxAgentTurns = ref<number | undefined>(undefined);
const maxAgentTurnsSaving = ref(false);
const maxAgentTurnsLoaded = ref(false);
const maxAgentTurnsLoading = ref(false);
const maxAgentTurnsLoadError = ref("");
async function loadMaxAgentTurnsSetting() {
if (maxAgentTurnsLoaded.value || maxAgentTurnsLoading.value) return;
maxAgentTurnsLoading.value = true;
maxAgentTurnsLoadError.value = "";
try {
editMaxAgentTurns.value = await loadMaxAgentTurns();
maxAgentTurnsLoaded.value = true;
} catch (e: any) {
maxAgentTurnsLoadError.value = e?.message || String(e);
toast(maxAgentTurnsLoadError.value, 5000);
} finally {
maxAgentTurnsLoading.value = false;
}
}
async function saveMaxAgentTurnsSetting() {
if (!maxAgentTurnsLoaded.value) return;
const clamped = normalizeMaxAgentTurns(editMaxAgentTurns.value);
maxAgentTurnsSaving.value = true;
try {
await saveMaxAgentTurns(clamped);
editMaxAgentTurns.value = clamped;
toast(t("ai.maxAgentTurnsSaved"));
} catch (e: any) {
toast(e?.message || String(e), 5000);
} finally {
maxAgentTurnsSaving.value = false;
}
}
// AI Config Delete Confirmation
const aiDeleteConfirmOpen = ref(false);
const aiDeleteConfigId = ref<string | null>(null);
@@ -4768,6 +4811,28 @@ onUnmounted(cleanupPreviewEditor);
</div>
</div>
<!-- Agent Turn Limit (list mode, global) -->
<div v-if="aiConfigListMode === 'list'" class="space-y-3">
<Separator />
<div>
<h3 class="text-sm font-medium">{{ t("ai.maxAgentTurns") }}</h3>
<p class="text-xs text-muted-foreground">{{ t("ai.maxAgentTurnsDescription") }}</p>
</div>
<div class="flex items-center gap-2">
<Input v-model.number="editMaxAgentTurns" type="number" :min="MAX_AGENT_TURNS_MIN" :max="MAX_AGENT_TURNS_MAX" step="1" class="h-8 w-32 text-xs" :placeholder="String(MAX_AGENT_TURNS_DEFAULT)" :disabled="!maxAgentTurnsLoaded || maxAgentTurnsSaving" />
<span class="text-xs" :class="maxAgentTurnsOutOfRange(editMaxAgentTurns) ? 'text-destructive' : 'text-muted-foreground'">
{{ t("ai.maxAgentTurnsRange", { min: MAX_AGENT_TURNS_MIN, max: MAX_AGENT_TURNS_MAX, default: MAX_AGENT_TURNS_DEFAULT }) }}
</span>
<div class="flex-1"></div>
<Button v-if="maxAgentTurnsLoadError" type="button" size="sm" variant="outline" :disabled="maxAgentTurnsLoading" @click="loadMaxAgentTurnsSetting">
{{ t("common.retry") }}
</Button>
<Button type="button" size="sm" :disabled="!maxAgentTurnsLoaded || maxAgentTurnsSaving || maxAgentTurnsOutOfRange(editMaxAgentTurns)" @click="saveMaxAgentTurnsSetting">
{{ maxAgentTurnsSaving ? t("common.processing") : t("common.save") }}
</Button>
</div>
</div>
<!-- Prompt Templates Management (list mode) -->
<div v-if="aiConfigListMode === 'list'" class="space-y-3">
<Separator />