Phase 003: remove the coding-agent and ekko-agent runtimes

Every chat session now runs through the Hermes bridge, so the three chat
chains become feature-equivalent instead of three partially-maintained
dispatch paths. Keeping the coding-agent and in-house agent runtimes alive
meant every bridge change had to be mirrored into two more runtimes that
had already drifted.

Historical rows are read, not migrated: sessions stored with the retired
source normalize to cli on load, workflow graphs naming the retired agents
keep loading and run on the bridge, and the visible-source check plus
SOURCE_LABELS still recognize the old value so those sessions stay listed.

The public surface follows the code: the OpenAPI generator and the MCP
chat-run tool schema drop the six retired entry fields so external callers
are not told about runtimes that no longer exist.

harness-check gains a RETIRED_RUNTIME scan so the residual check is a
mechanical gate instead of a manual grep, with the two surviving read-side
compat sites recorded with reasons.

Refs: docs/phases/phase_003.md
This commit is contained in:
2026-10-16 13:27:50 +08:00
parent c93e1fe1bd
commit 8c1ddf415f
123 changed files with 567 additions and 23599 deletions
@@ -228,7 +228,7 @@ const skillPickerLoading = ref(false)
let skillsLoadedKey = ''
let skillsLoadRequest: Promise<void> | null = null
const isBridgeSession = computed(() => chatStore.activeSession?.source === 'cli')
const isForkCommandSession = computed(() => !!chatStore.activeSession && chatStore.activeSession.source !== 'coding_agent')
const isForkCommandSession = computed(() => !!chatStore.activeSession)
const skillPickerItems = computed(() => {
const byName = new Map<string, SkillInfo>()
for (const category of skillCategories.value) {
@@ -1,7 +1,5 @@
<script setup lang="ts">
import { renameSession, setSessionWorkspace, batchDeleteSessions, exportSession } from "@/api/hermes/sessions";
import type { AvailableModelGroup } from "@/api/hermes/system";
import { fetchCodingAgentsStatus, inferCodingAgentApiMode, normalizeCodingAgentApiMode, type ChatCodingAgentId, type CodingAgentApiMode, type CodingAgentId } from "@/api/coding-agents";
import { useChatStore, type Session } from "@/stores/hermes/chat";
import { useAppStore } from "@/stores/hermes/app";
import { useProfilesStore } from "@/stores/hermes/profiles";
@@ -16,8 +14,6 @@ import {
NSelect,
NTooltip,
NPopconfirm,
NRadioButton,
NRadioGroup,
useMessage,
type DropdownOption,
} from "naive-ui";
@@ -321,14 +317,9 @@ const headerTitle = computed(() =>
);
const showNewChatModal = ref(false);
const newChatAgent = ref<"hermes" | ChatCodingAgentId>("hermes");
const newChatAgentMode = ref<"global" | "scoped">("scoped");
const newChatProfile = ref<string>("default");
const newChatProvider = ref<string>("");
const newChatModel = ref<string>("");
const newChatBaseUrl = ref<string>("");
const newChatApiKey = ref<string>("");
const newChatApiMode = ref<CodingAgentApiMode>("codex_responses");
const newChatWorkspace = ref("");
const newChatLoading = ref(false);
@@ -425,30 +416,6 @@ const hiddenDefaultWorkspaces = computed(() => {
return defaultWorkspaces.value.filter(ws => !visible.has(ws));
});
const CODING_AGENT_AUTH_PROVIDER_KEYS = new Set(["openai-codex", "copilot", "xai-oauth", "nous", "google-gemini-cli", "claude-oauth"]);
const showEkkoAgentEntry = import.meta.env.DEV;
const newChatAgentOptions = computed(() => {
const options = [
{ label: "Hermes", value: "hermes" },
{ label: "Claude Code", value: "claude-code" },
{ label: "Codex", value: "codex" },
];
if (showEkkoAgentEntry) options.push({ label: "Ekko Agent", value: "ekko-agent" });
return options;
});
const newChatApiModeOptions = computed(() => [
{ label: t("codingAgents.protocolOpenAiChat"), value: "chat_completions" },
{ label: t("codingAgents.protocolOpenAiResponses"), value: "codex_responses" },
{ label: t("codingAgents.protocolAnthropicMessages"), value: "anthropic_messages" },
]);
const newChatAgentModeOptions = computed(() => [
{ label: t("codingAgents.launchModeGlobal"), value: "global" },
{ label: t("codingAgents.launchModeScoped"), value: "scoped" },
]);
function getModelGroupsForProfile(profile: string) {
const profileModels = appStore.profileModelGroups.find(
(entry) => entry.profile === profile,
@@ -456,18 +423,8 @@ function getModelGroupsForProfile(profile: string) {
return profileModels?.groups || [];
}
function isCodingAgentAuthProvider(provider?: string) {
return CODING_AGENT_AUTH_PROVIDER_KEYS.has(String(provider || "").toLowerCase());
}
function isNewChatProviderAllowed(group: AvailableModelGroup) {
const mode = newChatAgent.value === "ekko-agent" ? "scoped" : newChatAgentMode.value;
if (!(newChatAgent.value !== "hermes" && mode === "scoped")) return true;
return !isCodingAgentAuthProvider(group.provider);
}
function getSelectableModelGroupsForProfile(profile: string) {
return getModelGroupsForProfile(profile).filter(isNewChatProviderAllowed);
return getModelGroupsForProfile(profile);
}
function getDefaultModelForProfile(profile: string) {
@@ -536,66 +493,28 @@ const selectedNewChatProviderGroup = computed(() =>
newChatModelGroups.value.find((item) => item.provider === newChatProvider.value),
);
const isNewChatCodingAgent = computed(() => newChatAgent.value !== "hermes");
const isNewChatExternalCodingAgent = computed(() => newChatAgent.value === "claude-code" || newChatAgent.value === "codex");
const effectiveNewChatAgentMode = computed(() =>
newChatAgent.value === "ekko-agent" ? "scoped" : newChatAgentMode.value,
);
const isNewChatGlobalCodingAgent = computed(() =>
isNewChatCodingAgent.value && effectiveNewChatAgentMode.value === "global",
);
const newChatUsesProviderModel = computed(() => !isNewChatGlobalCodingAgent.value);
const newChatNeedsBaseUrl = computed(() =>
isNewChatCodingAgent.value && effectiveNewChatAgentMode.value === "scoped" && !selectedNewChatProviderGroup.value?.base_url,
);
const newChatNeedsApiKey = computed(() =>
isNewChatCodingAgent.value && effectiveNewChatAgentMode.value === "scoped" && !selectedNewChatProviderGroup.value?.api_key,
);
const canConfirmNewChat = computed(() => {
if (!newChatProfile.value) return false;
if (!newChatUsesProviderModel.value) return true;
if (!newChatProvider.value || !newChatModel.value) return false;
if (!isNewChatCodingAgent.value) return true;
if (isNewChatExternalCodingAgent.value && !newChatApiMode.value) return false;
if (newChatNeedsBaseUrl.value && !newChatBaseUrl.value.trim()) return false;
if (newChatNeedsApiKey.value && !newChatApiKey.value.trim()) return false;
return true;
});
function defaultNewChatApiMode(group?: AvailableModelGroup): CodingAgentApiMode {
const providerKey = String(group?.provider || newChatProvider.value || "").toLowerCase();
const baseUrl = String(group?.base_url || newChatBaseUrl.value || "").toLowerCase();
return normalizeCodingAgentApiMode(
group?.api_mode,
inferCodingAgentApiMode(providerKey, baseUrl),
);
}
function syncNewChatApiMode() {
newChatApiMode.value = defaultNewChatApiMode(selectedNewChatProviderGroup.value);
}
function syncNewChatModelSelection() {
const defaults = getDefaultModelForProfile(newChatProfile.value);
newChatProvider.value = defaults.provider;
newChatModel.value = defaults.model;
newChatBaseUrl.value = "";
newChatApiKey.value = "";
syncNewChatApiMode();
}
function ensureNewChatProviderSelection() {
if (!newChatUsesProviderModel.value) return;
const currentGroup = selectedNewChatProviderGroup.value;
if (currentGroup && currentGroup.models.includes(newChatModel.value)) {
syncNewChatApiMode();
return;
}
syncNewChatModelSelection();
}
watch(
() => [newChatAgent.value, newChatAgentMode.value, newChatProfile.value],
() => [newChatProfile.value],
() => {
ensureNewChatProviderSelection();
// Reload workspace data when profile changes
@@ -646,56 +565,16 @@ function handleNewChatProfileChange(value: string) {
function handleNewChatProviderChange(value: string) {
newChatProvider.value = value;
newChatModel.value = newChatModelOptions.value[0]?.value || "";
newChatBaseUrl.value = "";
newChatApiKey.value = "";
syncNewChatApiMode();
}
async function confirmNewChat() {
if (isNewChatExternalCodingAgent.value) {
newChatLoading.value = true;
try {
const agentId = newChatAgent.value as CodingAgentId;
const status = await fetchCodingAgentsStatus();
const tool = status.tools.find((item) => item.id === agentId);
if (!tool?.installed) {
const fallbackName = agentId === "codex" ? "Codex" : "Claude Code";
message.warning(t("codingAgents.installRequired", { agent: tool?.name || fallbackName }));
showNewChatModal.value = false;
await router.push({ name: "hermes.codingAgents" });
return;
}
} catch {
message.error(t("codingAgents.loadFailed"));
return;
} finally {
newChatLoading.value = false;
}
}
const group = selectedNewChatProviderGroup.value;
const source = newChatAgent.value === "hermes" ? "cli" : "coding_agent";
const codingAgentMode = effectiveNewChatAgentMode.value;
const isGlobalCodingAgent = source === "coding_agent" && codingAgentMode === "global";
const agent = newChatAgent.value === "codex"
? "codex"
: newChatAgent.value === "claude-code"
? "claude"
: newChatAgent.value === "ekko-agent"
? "ekko-agent"
: "hermes";
const session = chatStore.newChat({
profile: newChatProfile.value,
provider: isGlobalCodingAgent ? undefined : newChatProvider.value,
model: isGlobalCodingAgent ? undefined : newChatModel.value,
source,
agent,
codingAgentId: newChatAgent.value === "hermes" ? undefined : newChatAgent.value,
codingAgentMode: source === "coding_agent" ? codingAgentMode : undefined,
provider: newChatProvider.value,
model: newChatModel.value,
source: "cli",
agent: "hermes",
workspace: newChatWorkspace.value || null,
baseUrl: source === "coding_agent" && !isGlobalCodingAgent ? group?.base_url || newChatBaseUrl.value.trim() || undefined : undefined,
apiKey: source === "coding_agent" && !isGlobalCodingAgent ? group?.api_key || newChatApiKey.value.trim() || undefined : undefined,
apiMode: isNewChatExternalCodingAgent.value && !isGlobalCodingAgent ? newChatApiMode.value : undefined,
});
// Record workspace to recent list
if (newChatWorkspace.value && workspaceComposable) {
@@ -867,7 +746,7 @@ const contextMenuOptions = computed(() => {
options.push({ label: t("chat.setWorkspace"), key: "workspace" })
if (contextSession.value?.source === "cli" || contextSession.value?.source === "coding_agent") {
if (contextSession.value?.source === "cli") {
options.push({ label: t("chat.setModel"), key: "model" })
}
@@ -1041,7 +920,6 @@ async function handleWorkspaceConfirm() {
}
const showSessionModelModal = ref(false);
const showSessionModelModeModal = ref(false);
const sessionModelSessionId = ref<string | null>(null);
const sessionModelSearch = ref("");
const sessionModelCollapsedGroups = ref<Record<string, boolean>>({});
@@ -1049,44 +927,14 @@ const sessionModelValue = ref("");
const sessionModelProvider = ref("");
const sessionModelCustomInput = ref("");
const sessionModelCustomProvider = ref("");
const sessionModelApiMode = ref<CodingAgentApiMode>("codex_responses");
const pendingSessionModelSwitch = ref<{ model: string; provider: string } | null>(null);
const sessionModelProfile = computed<string | null>(() => {
const session = chatStore.sessions.find((s) => s.id === sessionModelSessionId.value);
return session?.profile || null;
});
const sessionModelSession = computed(() =>
chatStore.sessions.find((s) => s.id === sessionModelSessionId.value) ||
(chatStore.activeSession?.id === sessionModelSessionId.value ? chatStore.activeSession : undefined),
);
const isSessionModelScopedCodingAgent = computed(() =>
sessionModelSession.value?.source === "coding_agent" &&
sessionModelSession.value?.codingAgentMode !== "global",
);
const sessionModelCodingAgentId = computed<ChatCodingAgentId | undefined>(() =>
sessionModelSession.value?.codingAgentId ||
(sessionModelSession.value?.agent === "claude"
? "claude-code"
: sessionModelSession.value?.agent === "codex"
? "codex"
: sessionModelSession.value?.agent === "ekko-agent"
? "ekko-agent"
: undefined),
);
const isSessionModelExternalCodingAgent = computed(() =>
isSessionModelScopedCodingAgent.value &&
(sessionModelCodingAgentId.value === "claude-code" || sessionModelCodingAgentId.value === "codex"),
);
const sessionModelBaseGroups = computed(() =>
sessionModelProfile.value
? getModelGroupsForProfile(sessionModelProfile.value).filter((group) => (
!isSessionModelScopedCodingAgent.value || !isCodingAgentAuthProvider(group.provider)
))
: [],
sessionModelProfile.value ? getModelGroupsForProfile(sessionModelProfile.value) : [],
);
const sessionModelProviderOptions = computed(() =>
@@ -1176,25 +1024,12 @@ function sessionModelAlias(model: string, provider: string) {
return appStore.getModelAlias(model, provider);
}
function defaultSessionModelApiMode(provider: string): CodingAgentApiMode {
const group = sessionModelBaseGroups.value.find((item) => item.provider === provider);
const providerKey = String(group?.provider || provider || "").toLowerCase();
const baseUrl = String(group?.base_url || "").toLowerCase();
return normalizeCodingAgentApiMode(
group?.api_mode,
inferCodingAgentApiMode(providerKey, baseUrl),
);
}
async function applySessionModelSwitch(model: string, provider: string, apiMode?: CodingAgentApiMode) {
async function applySessionModelSwitch(model: string, provider: string) {
if (!sessionModelSessionId.value) return;
const ok = await chatStore.switchSessionModel(model, provider, sessionModelSessionId.value, apiMode);
const ok = await chatStore.switchSessionModel(model, provider, sessionModelSessionId.value);
if (ok) {
sessionModelValue.value = model;
sessionModelProvider.value = provider;
if (apiMode) sessionModelApiMode.value = apiMode;
pendingSessionModelSwitch.value = null;
showSessionModelModeModal.value = false;
showSessionModelModal.value = false;
message.success(t("chat.modelSet"));
} else {
@@ -1205,28 +1040,9 @@ async function applySessionModelSwitch(model: string, provider: string, apiMode?
async function selectSessionModel(model: string, provider: string) {
const meta = sessionModelBaseGroups.value.find((group) => group.provider === provider)?.model_meta?.[model];
if (meta?.disabled || !sessionModelSessionId.value) return;
if (isSessionModelExternalCodingAgent.value) {
pendingSessionModelSwitch.value = { model, provider };
sessionModelApiMode.value = sessionModelSession.value?.provider === provider && sessionModelSession.value.apiMode
? normalizeCodingAgentApiMode(sessionModelSession.value.apiMode, defaultSessionModelApiMode(provider))
: defaultSessionModelApiMode(provider);
showSessionModelModeModal.value = true;
return;
}
await applySessionModelSwitch(model, provider);
}
async function confirmSessionModelMode() {
const pending = pendingSessionModelSwitch.value;
if (!pending) return;
await applySessionModelSwitch(pending.model, pending.provider, sessionModelApiMode.value);
}
function cancelSessionModelMode() {
pendingSessionModelSwitch.value = null;
showSessionModelModeModal.value = false;
}
async function handleSessionModelCustomSubmit() {
const model = sessionModelCustomInput.value.trim();
const provider = sessionModelCustomProvider.value;
@@ -1589,27 +1405,6 @@ async function handleSessionModelCustomSubmit() {
</div>
</NModal>
<NModal
v-model:show="showSessionModelModeModal"
preset="dialog"
:title="t('codingAgents.protocolScope')"
:mask-closable="true"
style="width: min(420px, calc(100vw - 32px))"
>
<NSelect
v-model:value="sessionModelApiMode"
:options="newChatApiModeOptions"
/>
<template #action>
<NButton size="small" @click="cancelSessionModelMode">
{{ t('common.cancel') }}
</NButton>
<NButton size="small" type="primary" @click="confirmSessionModelMode">
{{ t('common.confirm') }}
</NButton>
</template>
</NModal>
<NDrawer
v-model:show="showNewChatModal"
class="new-chat-drawer"
@@ -1619,26 +1414,6 @@ async function handleSessionModelCustomSubmit() {
>
<NDrawerContent :title="t('chat.newChat')" closable>
<div class="new-chat-form">
<label class="new-chat-field">
<span class="new-chat-label">{{ t("chat.agent") }}</span>
<NSelect
v-model:value="newChatAgent"
:options="newChatAgentOptions"
:disabled="newChatLoading"
/>
</label>
<label v-if="isNewChatExternalCodingAgent" class="new-chat-field">
<span class="new-chat-label">{{ t("codingAgents.launchModeScope") }}</span>
<NRadioGroup v-model:value="newChatAgentMode" name="new-chat-coding-agent-mode">
<NRadioButton
v-for="option in newChatAgentModeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</NRadioButton>
</NRadioGroup>
</label>
<label class="new-chat-field">
<span class="new-chat-label">{{ t("sidebar.profiles") }}</span>
<NSelect
@@ -1648,7 +1423,7 @@ async function handleSessionModelCustomSubmit() {
@update:value="handleNewChatProfileChange"
/>
</label>
<label v-if="newChatUsesProviderModel" class="new-chat-field">
<label class="new-chat-field">
<span class="new-chat-label">{{ t("models.provider") }}</span>
<NSelect
:value="newChatProvider"
@@ -1657,7 +1432,7 @@ async function handleSessionModelCustomSubmit() {
@update:value="handleNewChatProviderChange"
/>
</label>
<label v-if="newChatUsesProviderModel" class="new-chat-field">
<label class="new-chat-field">
<span class="new-chat-label">{{ t("models.models") }}</span>
<NSelect
v-model:value="newChatModel"
@@ -1666,30 +1441,6 @@ async function handleSessionModelCustomSubmit() {
filterable
/>
</label>
<label v-if="isNewChatExternalCodingAgent && effectiveNewChatAgentMode === 'scoped'" class="new-chat-field">
<span class="new-chat-label">{{ t("codingAgents.protocolScope") }}</span>
<NSelect
v-model:value="newChatApiMode"
:options="newChatApiModeOptions"
:disabled="newChatLoading"
/>
</label>
<label v-if="newChatNeedsBaseUrl" class="new-chat-field">
<span class="new-chat-label">{{ t("models.baseUrl") }}</span>
<NInput
v-model:value="newChatBaseUrl"
:placeholder="t('models.baseUrlPlaceholder')"
/>
</label>
<label v-if="newChatNeedsApiKey" class="new-chat-field">
<span class="new-chat-label">{{ t("models.apiKey") }}</span>
<NInput
v-model:value="newChatApiKey"
type="password"
show-password-on="click"
:placeholder="t('models.apiKeyPlaceholder')"
/>
</label>
<div class="new-chat-field">
<span class="new-chat-label">
{{ t("chat.workspace") }}
@@ -93,36 +93,11 @@ const visibleToolCalls = computed(() =>
currentToolCalls.value.filter((tool) => !!tool.toolName),
);
const emptyState = computed(() => {
const session = chatStore.activeSession;
const codingAgentId = session?.codingAgentId || (session?.agent === "codex" ? "codex" : session?.agent === "claude" ? "claude-code" : session?.agent === "ekko-agent" ? "ekko-agent" : undefined);
if (codingAgentId === "codex") {
return {
logo: "/coding-agents/codex-openai.png",
alt: "Codex",
text: t("chat.emptyStateAgent", { agent: "Codex" }),
};
}
if (codingAgentId === "claude-code") {
return {
logo: "/coding-agents/claude-code.svg",
alt: "Claude Code",
text: t("chat.emptyStateAgent", { agent: "Claude Code" }),
};
}
if (codingAgentId === "ekko-agent") {
return {
logo: "/coding-agents/ekko-agent.png",
alt: "Ekko Agent",
text: t("chat.emptyStateAgent", { agent: "Ekko Agent" }),
};
}
return {
logo: "/coding-agents/hermes.png",
alt: "Hermes",
text: t("chat.emptyState"),
};
});
const emptyState = computed(() => ({
logo: "/logo.png",
alt: "Hermes",
text: t("chat.emptyState"),
}));
const displayMessages = computed(() => {
const currentToolIds = new Set(currentToolCalls.value.map((tool) => tool.id));
@@ -174,7 +149,7 @@ const displayMessagesWithForkDivider = computed<Message[]>(() => {
const canForkActiveSession = computed(() => {
const session = chatStore.activeSession;
const hasConversation = displayMessages.value.some((message) => message.role === "user" || message.role === "assistant");
return !!session && session.source !== "coding_agent" && !chatStore.isStreaming && !chatStore.isForkPending && hasConversation;
return !!session && !chatStore.isStreaming && !chatStore.isForkPending && hasConversation;
});
const lastForkActionMessageId = computed(() => {
@@ -43,18 +43,7 @@ const profileModelsMissing = computed(() =>
appStore.profileModelGroups.length > 0 && !profileHasModels.value,
)
const isGlobalAgentSession = computed(() => props.session.source === 'global_agent')
const sessionAgentLogo = computed(() => {
if (props.session.source === 'coding_agent') {
if (props.session.codingAgentId === 'codex' || props.session.agent === 'codex') {
return { label: 'Codex', src: '/coding-agents/codex-openai.png' }
}
if (props.session.codingAgentId === 'ekko-agent' || props.session.agent === 'ekko-agent') {
return { label: 'Ekko Agent', src: '/coding-agents/ekko-agent.png' }
}
return { label: 'Claude Code', src: '/coding-agents/claude-code.svg' }
}
return { label: 'Hermes', src: '/coding-agents/hermes.png' }
})
const sessionAgentLogo = computed(() => ({ label: 'Hermes', src: '/logo.png' }))
let longPressTimer: ReturnType<typeof setTimeout> | null = null
const longPressTriggered = ref(false)
@@ -9,9 +9,6 @@ const maxAgentTokens = computed(() => Math.max(...usageStore.agentUsage.map(agen
const agentLabels: Record<string, string> = {
hermes: 'hermes',
claude_code: 'claudeCode',
codex: 'codex',
ekko_agent: 'ekkoAgent',
}
function agentName(agent: string): string {
@@ -6,8 +6,6 @@ import { NInput, NSelect, NSwitch, NTooltip, useMessage } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import WorkflowModelSelector from './WorkflowModelSelector.vue'
import type { WorkflowAgentNodeData, WorkflowAgentNodeEditableData } from './types'
import type { CodingAgentApiMode } from '@/api/coding-agents'
import type { ProviderApiMode } from '@/api/hermes/system'
import { getFileDownloadUrl } from '@/api/hermes/files'
import '@vue-flow/node-resizer/dist/style.css'
@@ -27,12 +25,6 @@ const statusTip = computed(() => (
? props.data.statusError.trim()
: ''
))
const isCodingAgent = computed(() => props.data.agent !== 'hermes')
const apiModeOptions = computed(() => [
{ label: t('codingAgents.protocolOpenAiChat'), value: 'chat_completions' },
{ label: t('codingAgents.protocolOpenAiResponses'), value: 'codex_responses' },
{ label: t('codingAgents.protocolAnthropicMessages'), value: 'anthropic_messages' },
])
const imageAttachments = computed(() => props.data.images.filter(isImagePath))
const fileAttachments = computed(() => props.data.images.filter(path => !isImagePath(path)))
@@ -40,19 +32,11 @@ function updateField<K extends keyof WorkflowAgentNodeEditableData>(key: K, valu
props.data.onUpdate(props.id, { [key]: value } as Partial<WorkflowAgentNodeEditableData>)
}
function handleModelSelect(selection: { provider: string; model: string; apiMode?: ProviderApiMode }) {
const patch: Partial<WorkflowAgentNodeEditableData> = {
function handleModelSelect(selection: { provider: string; model: string }) {
props.data.onUpdate(props.id, {
provider: selection.provider,
model: selection.model,
}
if (
selection.apiMode === 'chat_completions' ||
selection.apiMode === 'codex_responses' ||
selection.apiMode === 'anthropic_messages'
) {
patch.apiMode = selection.apiMode
}
props.data.onUpdate(props.id, patch)
})
}
function handleControlEvent(event: Event) {
@@ -167,15 +151,6 @@ async function uploadImages(files: File[]) {
:disabled="data.readonly"
@select="handleModelSelect"
/>
<NSelect
v-if="isCodingAgent"
:value="data.apiMode"
:options="apiModeOptions"
size="small"
:disabled="data.readonly"
:placeholder="t('workflow.node.apiMode')"
@update:value="value => updateField('apiMode', value as CodingAgentApiMode)"
/>
<label class="node-toggle-row">
<span>{{ t('workflow.node.approvalRequired') }}</span>
<NSwitch
@@ -1,6 +1,5 @@
import type { SelectOption } from 'naive-ui'
import type { AvailableModelGroup } from '@/api/hermes/system'
import type { CodingAgentApiMode } from '@/api/coding-agents'
export interface WorkflowSelectOption extends SelectOption {
label: string
@@ -14,7 +13,6 @@ export interface WorkflowAgentNodeData {
agent: string
provider: string
model: string
apiMode: CodingAgentApiMode
input: string
skills: string[]
images: string[]
@@ -30,4 +28,4 @@ export interface WorkflowAgentNodeData {
onUploadImages: (id: string, files: File[]) => Promise<string[]>
}
export type WorkflowAgentNodeEditableData = Pick<WorkflowAgentNodeData, 'title' | 'agent' | 'provider' | 'model' | 'apiMode' | 'input' | 'skills' | 'images' | 'approvalRequired'>
export type WorkflowAgentNodeEditableData = Pick<WorkflowAgentNodeData, 'title' | 'agent' | 'provider' | 'model' | 'input' | 'skills' | 'images' | 'approvalRequired'>