mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-11 13:11:23 +00:00
Merge remote-tracking branch 'origin/fix/change_async_in_terminal_toolkit' into fix/change_async_in_terminal_toolkit
This commit is contained in:
commit
47bf341cc7
13 changed files with 150 additions and 48 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -47,3 +47,10 @@ public/
|
|||
# Testing
|
||||
coverage/
|
||||
.traceroot-config.yaml
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ def build_conversation_context(task_lock: TaskLock, header: str = "=== CONVERSAT
|
|||
Formatted context string with task history and files listed once at the end
|
||||
"""
|
||||
context = ""
|
||||
working_directory = None
|
||||
working_directories = set() # Collect all unique working directories
|
||||
|
||||
if task_lock.conversation_history:
|
||||
context = f"{header}\n"
|
||||
|
|
@ -190,37 +190,35 @@ def build_conversation_context(task_lock: TaskLock, header: str = "=== CONVERSAT
|
|||
for entry in task_lock.conversation_history:
|
||||
if entry['role'] == 'task_result':
|
||||
if isinstance(entry['content'], dict):
|
||||
# Format without file listing
|
||||
formatted_context = format_task_context(entry['content'], skip_files=True)
|
||||
context += formatted_context + "\n\n"
|
||||
# Remember the working directory from the last task
|
||||
if entry['content'].get('working_directory'):
|
||||
working_directory = entry['content']['working_directory']
|
||||
working_directories.add(entry['content']['working_directory'])
|
||||
else:
|
||||
context += entry['content'] + "\n"
|
||||
elif entry['role'] == 'assistant':
|
||||
context += f"Assistant: {entry['content']}\n\n"
|
||||
|
||||
# Add all generated files at the end, only once
|
||||
if working_directory:
|
||||
try:
|
||||
if os.path.exists(working_directory):
|
||||
generated_files = []
|
||||
for root, dirs, files in os.walk(working_directory):
|
||||
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['node_modules', '__pycache__', 'venv']]
|
||||
for file in files:
|
||||
if not file.startswith('.') and not file.endswith(('.pyc', '.tmp')):
|
||||
file_path = os.path.join(root, file)
|
||||
absolute_path = os.path.abspath(file_path)
|
||||
generated_files.append(absolute_path)
|
||||
if working_directories:
|
||||
all_generated_files = set() # Use set to avoid duplicates
|
||||
for working_directory in working_directories:
|
||||
try:
|
||||
if os.path.exists(working_directory):
|
||||
for root, dirs, files in os.walk(working_directory):
|
||||
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['node_modules', '__pycache__', 'venv']]
|
||||
for file in files:
|
||||
if not file.startswith('.') and not file.endswith(('.pyc', '.tmp')):
|
||||
file_path = os.path.join(root, file)
|
||||
absolute_path = os.path.abspath(file_path)
|
||||
all_generated_files.add(absolute_path)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to collect generated files from {working_directory}: {e}")
|
||||
|
||||
if generated_files:
|
||||
context += "Generated Files from Previous Tasks:\n"
|
||||
for file_path in sorted(generated_files):
|
||||
context += f" - {file_path}\n"
|
||||
context += "\n"
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to collect generated files: {e}")
|
||||
if all_generated_files:
|
||||
context += "Generated Files from Previous Tasks:\n"
|
||||
for file_path in sorted(all_generated_files):
|
||||
context += f" - {file_path}\n"
|
||||
context += "\n"
|
||||
|
||||
context += "\n"
|
||||
|
||||
|
|
|
|||
|
|
@ -285,11 +285,17 @@ def auto_listen_toolkit(base_toolkit_class: Type[T]) -> Callable[[Type[T]], Type
|
|||
for method_name, base_method in base_methods.items():
|
||||
if method_name in cls.__dict__:
|
||||
continue
|
||||
|
||||
|
||||
sig = signature(base_method)
|
||||
|
||||
|
||||
def create_wrapper(method_name: str, base_method: Callable) -> Callable:
|
||||
if iscoroutinefunction(base_method):
|
||||
# Unwrap decorators to check the actual function
|
||||
unwrapped_method = base_method
|
||||
while hasattr(unwrapped_method, '__wrapped__'):
|
||||
unwrapped_method = unwrapped_method.__wrapped__
|
||||
|
||||
# Check if the unwrapped method is a coroutine function
|
||||
if iscoroutinefunction(unwrapped_method):
|
||||
async def async_method_wrapper(self, *args, **kwargs):
|
||||
return await getattr(super(cls, self), method_name)(*args, **kwargs)
|
||||
async_method_wrapper.__name__ = method_name
|
||||
|
|
@ -301,12 +307,12 @@ def auto_listen_toolkit(base_toolkit_class: Type[T]) -> Callable[[Type[T]], Type
|
|||
sync_method_wrapper.__name__ = method_name
|
||||
sync_method_wrapper.__signature__ = sig
|
||||
return sync_method_wrapper
|
||||
|
||||
|
||||
wrapper = create_wrapper(method_name, base_method)
|
||||
decorated_method = listen_toolkit(base_method)(wrapper)
|
||||
|
||||
|
||||
setattr(cls, method_name, decorated_method)
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
return class_decorator
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import fs from 'node:fs'
|
|||
import { getBackendPath, getBinaryPath, getCachePath, getVenvPath, cleanupOldVenvs, isBinaryExists, runInstallScript } from './utils/process'
|
||||
import { spawn } from 'child_process'
|
||||
import { safeMainWindowSend } from './utils/safeWebContentsSend'
|
||||
import os from 'node:os'
|
||||
|
||||
const userData = app.getPath('userData');
|
||||
const versionFile = path.join(userData, 'version.txt');
|
||||
|
|
@ -57,6 +58,13 @@ Promise<PromiseReturnType> => {
|
|||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
// Clean up cache in production environment BEFORE any checks
|
||||
// This ensures users always get fresh dependencies in production
|
||||
if (app.isPackaged) {
|
||||
log.info('[CACHE CLEANUP] Production environment detected, cleaning cache before dependency check...');
|
||||
cleanupCacheInProduction();
|
||||
}
|
||||
|
||||
const versionExists:boolean = checkInstallOperations.getSavedVersion();
|
||||
|
||||
// Check if command tools are installed
|
||||
|
|
@ -280,6 +288,34 @@ class InstallLogs {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up cache directory
|
||||
* This ensures users get fresh dependencies
|
||||
* Note: Only call this in production environment (caller should check app.isPackaged)
|
||||
*/
|
||||
function cleanupCacheInProduction(): void {
|
||||
try {
|
||||
const cacheBaseDir = path.join(os.homedir(), '.eigent', 'cache');
|
||||
|
||||
if (!fs.existsSync(cacheBaseDir)) {
|
||||
log.info('[CACHE CLEANUP] Cache directory does not exist, nothing to clean');
|
||||
return;
|
||||
}
|
||||
|
||||
log.info('[CACHE CLEANUP] Cleaning cache directory:', cacheBaseDir);
|
||||
|
||||
fs.rmSync(cacheBaseDir, { recursive: true, force: true });
|
||||
|
||||
log.info('[CACHE CLEANUP] Cache directory cleaned successfully');
|
||||
|
||||
fs.mkdirSync(cacheBaseDir, { recursive: true });
|
||||
log.info('[CACHE CLEANUP] Empty cache directory recreated');
|
||||
|
||||
} catch (error) {
|
||||
log.error('[CACHE CLEANUP] Failed to clean cache directory:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const runInstall = (extraArgs: string[], version: string) => {
|
||||
const installLogs = new InstallLogs(extraArgs, version);
|
||||
return new Promise<PromiseReturnType>((resolveInner, rejectInner) => {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ interface TaskCardProps {
|
|||
onUpdateTask: (taskIndex: number, content: string) => void;
|
||||
onDeleteTask: (taskIndex: number) => void;
|
||||
clickable?: boolean;
|
||||
chatId?: string;
|
||||
}
|
||||
|
||||
export function TaskCard({
|
||||
|
|
@ -47,14 +48,15 @@ export function TaskCard({
|
|||
onUpdateTask,
|
||||
onDeleteTask,
|
||||
clickable = true,
|
||||
chatId,
|
||||
}: TaskCardProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [contentHeight, setContentHeight] = useState<number | "auto">("auto");
|
||||
|
||||
//Get Chatstore for the active project's task
|
||||
const { chatStore } = useChatStoreAdapter();
|
||||
//Get Chatstore and ProjectStore for the active project's task
|
||||
const { chatStore, projectStore } = useChatStoreAdapter();
|
||||
if (!chatStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
|
@ -329,6 +331,20 @@ export function TaskCard({
|
|||
<div
|
||||
onClick={() => {
|
||||
if (task.agent) {
|
||||
// Switch to the chatStore that owns this task card (for multi-turn conversations)
|
||||
if (chatId && projectStore.activeProjectId) {
|
||||
const activeChatStore = projectStore.getActiveChatStore();
|
||||
const currentChatId = activeChatStore ? Object.keys(projectStore.projects[projectStore.activeProjectId].chatStores).find(
|
||||
id => projectStore.projects[projectStore.activeProjectId].chatStores[id] === activeChatStore
|
||||
) : null;
|
||||
|
||||
// Only switch if this is a different chat
|
||||
if (currentChatId !== chatId) {
|
||||
projectStore.setActiveChatStore(projectStore.activeProjectId, chatId);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the active workspace and agent
|
||||
chatStore.setActiveWorkSpace(
|
||||
chatStore.activeTaskId as string,
|
||||
"workflow"
|
||||
|
|
|
|||
|
|
@ -38,14 +38,14 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
|
||||
// Show task if this query group has a task message OR if it's the most recent user query during splitting
|
||||
// During splitting phase (no to_sub_tasks yet), show task for the most recent query only
|
||||
const isLastUserQuery = !queryGroup.taskMessage &&
|
||||
activeTaskId &&
|
||||
const isLastUserQuery = !queryGroup.taskMessage &&
|
||||
activeTaskId &&
|
||||
chatState.tasks[activeTaskId] &&
|
||||
queryGroup.userMessage &&
|
||||
queryGroup.userMessage &&
|
||||
queryGroup.userMessage.id === chatState.tasks[activeTaskId].messages.filter((m: any) => m.role === 'user').pop()?.id &&
|
||||
// Only show during active phases (not finished)
|
||||
chatState.tasks[activeTaskId].status !== 'finished';
|
||||
|
||||
|
||||
const task = (queryGroup.taskMessage || isLastUserQuery) && activeTaskId ? chatState.tasks[activeTaskId] : null;
|
||||
|
||||
// Set up intersection observer for this query group
|
||||
|
|
@ -185,6 +185,7 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
|
|||
>
|
||||
<TaskCard
|
||||
key={`task-${activeTaskId}-${queryGroup.queryId}`}
|
||||
chatId={chatId}
|
||||
taskInfo={task?.taskInfo || []}
|
||||
taskType={queryGroup.taskMessage?.taskType || 1}
|
||||
taskAssigning={task?.taskAssigning || []}
|
||||
|
|
|
|||
|
|
@ -224,6 +224,19 @@ function HeaderWin() {
|
|||
<img className="w-6 h-6" src={folderIcon} alt="folder-icon" />
|
||||
</Button>
|
||||
</div>
|
||||
{location.pathname === "/history" && (
|
||||
<div className="flex items-center mr-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="no-drag"
|
||||
onClick={() => navigate("/")}
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
{t("layout.back")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{location.pathname !== "/history" && (
|
||||
<div className="flex items-center mr-1">
|
||||
<TooltipSimple content={t("layout.home")} side="bottom" align="center">
|
||||
|
|
@ -282,7 +295,7 @@ function HeaderWin() {
|
|||
<div
|
||||
className={`${
|
||||
platform === "darwin" && "pr-2"
|
||||
} flex h-full items-center space-x-1 z-50 relative no-drag gap-1`}
|
||||
} flex h-full items-center z-50 relative no-drag gap-1`}
|
||||
>
|
||||
{chatStore.activeTaskId && chatStore.tasks[chatStore.activeTaskId as string] && (
|
||||
<>
|
||||
|
|
@ -314,6 +327,17 @@ function HeaderWin() {
|
|||
{t("layout.refer-friends")}
|
||||
</Button>
|
||||
</TooltipSimple>
|
||||
<TooltipSimple content={t("layout.settings")} side="bottom" align="center">
|
||||
<Button
|
||||
onClick={() => navigate("/history?tab=settings")}
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="no-drag"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
{t("layout.settings")}
|
||||
</Button>
|
||||
</TooltipSimple>
|
||||
{chatStore.activeTaskId &&
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.status === 'finished' && (
|
||||
<TooltipSimple content={t("layout.share")} side="bottom" align="end">
|
||||
|
|
@ -330,9 +354,9 @@ function HeaderWin() {
|
|||
{chatStore.activeTaskId &&
|
||||
chatStore.tasks[chatStore.activeTaskId as string] &&
|
||||
(
|
||||
chatStore.tasks[chatStore.activeTaskId as string].messages.length > 0 ||
|
||||
chatStore.tasks[chatStore.activeTaskId as string].hasMessages ||
|
||||
chatStore.tasks[chatStore.activeTaskId as string].status !== 'pending'
|
||||
(chatStore.tasks[chatStore.activeTaskId as string]?.messages?.length || 0) > 0 ||
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.hasMessages ||
|
||||
chatStore.tasks[chatStore.activeTaskId as string]?.status !== 'pending'
|
||||
) && (
|
||||
<TooltipSimple content={t("layout.end-project")} side="bottom" align="end">
|
||||
<Button
|
||||
|
|
@ -352,7 +376,7 @@ function HeaderWin() {
|
|||
<div
|
||||
className={`${
|
||||
platform === "darwin" && "pr-2"
|
||||
} flex h-full items-center space-x-1 z-50 relative no-drag`}
|
||||
} flex h-full items-center z-50 relative no-drag gap-1`}
|
||||
>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -392,4 +416,4 @@ function HeaderWin() {
|
|||
);
|
||||
}
|
||||
|
||||
export default HeaderWin;
|
||||
export default HeaderWin;
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@
|
|||
"are-you-sure-you-want-to-delete": "Are you sure you want to delete this task? This action cannot be undone.",
|
||||
"share": "Share",
|
||||
"home": "Home",
|
||||
"back": "Back",
|
||||
"developer-agent": "Developer Agent",
|
||||
"search-agent": "Search Agent",
|
||||
"document-agent": "Document Agent",
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@
|
|||
"are-you-sure-you-want-to-delete": "您确定要删除此任务吗?此操作无法撤销。",
|
||||
"share": "分享",
|
||||
"home": "首页",
|
||||
"back": "返回",
|
||||
"developer-agent": "开发者智能体",
|
||||
"search-agent": "搜索智能体",
|
||||
"document-agent": "文档智能体",
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@
|
|||
"are-you-sure-you-want-to-delete": "您確定要刪除此任務嗎?此操作無法撤銷。",
|
||||
"share": "分享",
|
||||
"home": "首頁",
|
||||
"back": "返回",
|
||||
"developer-agent": "開發者智能體",
|
||||
"search-agent": "搜尋智能體",
|
||||
"document-agent": "文件智能體",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
|
||||
import { Plus } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useUser } from "@stackframe/react";
|
||||
|
|
@ -28,11 +28,13 @@ import WordCarousel from "@/components/ui/WordCarousel";
|
|||
export default function Home() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { chatStore, projectStore } = useChatStoreAdapter();
|
||||
if (!chatStore || !projectStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
const [activeTab, setActiveTab] = useState<"projects" | "workers" | "trigger" | "settings" | "mcp_tools">("projects");
|
||||
const tabParam = searchParams.get("tab") as "projects" | "workers" | "trigger" | "settings" | "mcp_tools" | null;
|
||||
const [activeTab, setActiveTab] = useState<"projects" | "workers" | "trigger" | "settings" | "mcp_tools">(tabParam || "projects");
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const HAS_STACK_KEYS = hasStackKeys();
|
||||
|
|
@ -40,6 +42,14 @@ export default function Home() {
|
|||
const { username, email } = useAuthStore();
|
||||
const displayName = stackUser?.displayName ?? stackUser?.primaryEmail ?? username ?? email ?? "";
|
||||
|
||||
// Sync activeTab with URL changes
|
||||
useEffect(() => {
|
||||
const tab = searchParams.get("tab") as "projects" | "workers" | "trigger" | "settings" | "mcp_tools" | null;
|
||||
if (tab) {
|
||||
setActiveTab(tab);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const formatWelcomeName = (raw: string): string => {
|
||||
if (!raw) return "";
|
||||
if (/^[^@]+@gmail\.com$/i.test(raw)) {
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ export default function Home() {
|
|||
<div className="w-full h-full flex-1 flex flex-col animate-in fade-in-0 pr-2 slide-in-from-right-2 duration-300">
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning.find(
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
|
|
@ -231,7 +231,7 @@ export default function Home() {
|
|||
)}
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning.find(
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
|
|
@ -256,7 +256,7 @@ export default function Home() {
|
|||
)}
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning.find(
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
|
|
@ -270,7 +270,7 @@ export default function Home() {
|
|||
<Folder
|
||||
data={chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning.find(
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
|
|
|
|||
|
|
@ -551,7 +551,8 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
|||
taskType: type ? 2 : 1,
|
||||
showType: "list",
|
||||
// Don't auto-confirm for multi-turn complex tasks - show workforce splitting panel
|
||||
isConfirm: shouldAutoConfirm
|
||||
isConfirm: shouldAutoConfirm,
|
||||
task_id: currentTaskId
|
||||
};
|
||||
addMessages(currentTaskId, newMessage)
|
||||
const newTaskInfo = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue