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:
@@ -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'>
|
||||
|
||||
Reference in New Issue
Block a user