Merge branch 'main' into fix-router-issue

This commit is contained in:
Wendong-Fan 2025-11-06 03:30:56 +08:00 committed by GitHub
commit 27b5ecc3ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 110 additions and 48 deletions

View file

@ -50,7 +50,7 @@ async def post(data: Chat, request: Request):
os.environ["CAMEL_MODEL_LOG_ENABLED"] = "true"
email_sanitized = re.sub(r'[\\/*?:"<>|\s]', "_", data.email.split("@")[0]).strip(".")
camel_log = Path.home() / ".eigent" / email_sanitized / ("task_" + data.project_id) / "camel_logs"
camel_log = Path.home() / ".eigent" / email_sanitized / ("project_" + data.project_id) / ("task_" + data.task_id) / "camel_logs"
camel_log.mkdir(parents=True, exist_ok=True)
os.environ["CAMEL_LOG_DIR"] = str(camel_log)

View file

@ -1 +0,0 @@
# Utils package

View file

@ -21,7 +21,7 @@ RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/*
# Copy dependency files first
COPY pyproject.toml uv.lock ./
COPY server/pyproject.toml server/uv.lock ./
# Install the project's dependencies
RUN --mount=type=cache,target=/root/.cache/uv \
@ -29,7 +29,11 @@ RUN --mount=type=cache,target=/root/.cache/uv \
# Then, add the rest of the project source code and install it
# Installing separately from its dependencies allows optimal layer caching
COPY . /app
COPY server/ /app
# Copy the utils directory from the parent project
COPY utils /app/utils
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --no-dev
@ -45,7 +49,7 @@ RUN apt-get update && apt-get install -y curl netcat-openbsd && rm -rf /var/lib/
ENV PATH="/app/.venv/bin:$PATH"
# Copy and make the start script executable
COPY start.sh /app/start.sh
COPY server/start.sh /app/start.sh
RUN sed -i 's/\r$//' /app/start.sh && chmod +x /app/start.sh
# Reset the entrypoint, don't invoke `uv`

View file

@ -1,4 +1,11 @@
from logging.config import fileConfig
import sys
import pathlib
# Add project root to Python path to import shared utils
_project_root = pathlib.Path(__file__).parent.parent.parent
if str(_project_root) not in sys.path:
sys.path.insert(0, str(_project_root))
from sqlalchemy import engine_from_config, pool
from alembic import context

View file

@ -124,10 +124,10 @@ class ConfigInfo:
"env_vars": [],
"toolkit": "google_drive_mcp_toolkit",
},
# ConfigGroup.GOOGLE_GMAIL_MCP.value: {
# "env_vars": [],
# "toolkit": "google_gmail_mcp_toolkit",
# },
ConfigGroup.GOOGLE_GMAIL_MCP.value: {
"env_vars": ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET", "GOOGLE_REFRESH_TOKEN"],
"toolkit": "google_gmail_native_toolkit",
},
ConfigGroup.IMAGE_ANALYSIS.value: {
"env_vars": [],
"toolkit": "image_analysis_toolkit",

View file

@ -21,7 +21,7 @@ class ConfigGroup(str, Enum):
GITHUB = "Github"
GOOGLE_CALENDAR = "Google Calendar"
GOOGLE_DRIVE_MCP = "Google Drive MCP"
GOOGLE_GMAIL_MCP = "Google Gmail MCP"
GOOGLE_GMAIL_MCP = "Google Gmail"
IMAGE_ANALYSIS = "Image Analysis"
MCP_SEARCH = "MCP Search"
PPTX = "PPTX"

View file

@ -25,8 +25,8 @@ services:
# FastAPI Application
api:
build:
context: .
dockerfile: Dockerfile
context: ..
dockerfile: server/Dockerfile
args:
database_url: postgresql://postgres:123456@postgres:5432/eigent
container_name: eigent_api

View file

@ -1,7 +1,6 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogContentSection,
DialogFooter,
@ -11,12 +10,10 @@ import {
import { Input } from "@/components/ui/input";
import {
Bot,
CircleAlert,
Plus,
RefreshCw,
ChevronLeft,
ArrowRight,
Edit,
Eye,
EyeOff,
} from "lucide-react";
import ToolSelect from "./ToolSelect";
import { Textarea } from "@/components/ui/textarea";
@ -25,7 +22,6 @@ import githubIcon from "@/assets/github.svg";
import { fetchPost } from "@/api/http";
import { useAuthStore, useWorkerList } from "@/store/authStore";
import { useTranslation } from "react-i18next";
import { TooltipSimple } from "../ui/tooltip";
import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
interface EnvValue {
@ -68,6 +64,7 @@ export function AddWorker({
const [showEnvConfig, setShowEnvConfig] = useState(false);
const [activeMcp, setActiveMcp] = useState<McpItem | null>(null);
const [envValues, setEnvValues] = useState<{ [key: string]: EnvValue }>({});
const [secretVisible, setSecretVisible] = useState<{ [key: string]: boolean }>({});
const toolSelectRef = useRef<{
installMcp: (id: number, env?: any, activeMcp?: any) => Promise<void>;
} | null>(null);
@ -86,6 +83,7 @@ export function AddWorker({
console.log(mcp);
if (mcp?.install_command?.env) {
const initialValues: { [key: string]: EnvValue } = {};
const initialVisibility: { [key: string]: boolean } = {};
for(const key of Object.keys(mcp.install_command.env)) {
initialValues[key] = {
value: "",
@ -95,8 +93,10 @@ export function AddWorker({
?.replace(/{{/g, "")
?.replace(/}}/g, "") || "",
};
initialVisibility[key] = false;
}
setEnvValues(initialValues);
setSecretVisible(initialVisibility);
}
};
@ -136,12 +136,14 @@ export function AddWorker({
// clean status
setActiveMcp(null);
setEnvValues({});
setSecretVisible({});
};
const handleCloseMcpEnvSetting = () => {
setShowEnvConfig(false);
setActiveMcp(null);
setEnvValues({});
setSecretVisible({});
};
const handleShowEnvConfig = (mcp: McpItem) => {
@ -150,6 +152,11 @@ export function AddWorker({
setShowEnvConfig(true);
};
const isSensitiveKey = (key: string) => /token|key|secret|password|id/i.test(key);
const toggleSecretVisibility = (key: string) => {
setSecretVisible((prev) => ({ ...prev, [key]: !prev[key] }));
};
const handleSelectedToolsChange = (tools: McpItem[]) => {
setSelectedTools(tools);
};
@ -161,6 +168,7 @@ export function AddWorker({
setShowEnvConfig(false);
setActiveMcp(null);
setEnvValues({});
setSecretVisible({});
setNameError("");
};
@ -204,9 +212,11 @@ export function AddWorker({
}
});
console.log("mcpLocal.mcpServers", mcpLocal.mcpServers);
for(const key of Object.keys(mcpLocal.mcpServers)) {
if (!mcpList.includes(key)) {
delete mcpLocal.mcpServers[key];
if (mcpLocal.mcpServers && typeof mcpLocal.mcpServers === 'object') {
for(const key of Object.keys(mcpLocal.mcpServers)) {
if (!mcpList.includes(key)) {
delete mcpLocal.mcpServers[key];
}
}
}
if (edit) {
@ -319,7 +329,12 @@ export function AddWorker({
</Button>
)}
</DialogTrigger>
<DialogContent size="sm" className="p-0 gap-0">
<DialogContent
size="sm"
className="p-0 gap-0"
onInteractOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
>
<DialogHeader
title={showEnvConfig ? t("workforce.configure-mcp-server") : t("workforce.add-your-agent")}
tooltip={t("layout.configure-your-mcp-worker-node-here")}
@ -364,18 +379,24 @@ export function AddWorker({
{Object.keys(activeMcp?.install_command?.env || {}).map(
(key) => (
<div key={key}>
<div className="text-text-body text-sm leading-normal font-bold">
{key}*
</div>
<Input
placeholder=""
className="h-7 rounded-sm border border-solid border-input-border-default bg-input-bg-default !shadow-none text-sm leading-normal !ring-0 !ring-offset-0 resize-none"
size="default"
title={key}
required
placeholder={envValues[key]?.tip || `Enter ${key}`}
type={isSensitiveKey(key) && !secretVisible[key] ? "password" : "text"}
value={envValues[key]?.value || ""}
onChange={(e) => updateEnvValue(key, e.target.value)}
note={envValues[key]?.tip}
backIcon={isSensitiveKey(key) ? (
secretVisible[key] ? (
<EyeOff size={16} className="text-button-transparent-icon-disabled" />
) : (
<Eye size={16} className="text-button-transparent-icon-disabled" />
)
) : undefined}
onBackIconClick={isSensitiveKey(key) ? () => toggleSecretVisibility(key) : undefined}
/>
<div className="text-input-label-default text-xs leading-normal">
{envValues[key]?.tip}
</div>
</div>
)
)}
@ -392,7 +413,6 @@ export function AddWorker({
cancelButtonVariant="ghost"
confirmButtonVariant="primary"
>
<ArrowRight size={16} />
</DialogFooter>
{/* hidden but keep rendering ToolSelect component */}
<div style={{ display: "none" }}>
@ -425,11 +445,6 @@ export function AddWorker({
}}
state={nameError ? "error" : "default"}
note={nameError || ""}
backIcon={<RefreshCw size={16} className="text-button-transparent-icon-disabled" />}
onBackIconClick={() => {
// Handle refresh/regenerate logic here
console.log("Refresh agent name");
}}
required
/>
</div>

View file

@ -38,7 +38,25 @@ 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
// Exclude human-reply scenarios (when user is replying to an activeAsk)
const isHumanReply = queryGroup.userMessage &&
activeTaskId &&
chatState.tasks[activeTaskId] &&
(chatState.tasks[activeTaskId].activeAsk ||
// Check if this user message follows an 'ask' message in the message sequence
(() => {
const messages = chatState.tasks[activeTaskId].messages;
const userMessageIndex = messages.findIndex((m: any) => m.id === queryGroup.userMessage.id);
if (userMessageIndex > 0) {
// Check the previous message - if it's an agent message with step 'ask', this is a human-reply
const prevMessage = messages[userMessageIndex - 1];
return prevMessage?.role === 'agent' && prevMessage?.step === 'ask';
}
return false;
})());
const isLastUserQuery = !queryGroup.taskMessage &&
!isHumanReply &&
activeTaskId &&
chatState.tasks[activeTaskId] &&
queryGroup.userMessage &&

View file

@ -178,7 +178,7 @@ export default function HistorySidebar() {
try {
//TODO(file): rename endpoint to use project_id
//TODO(history): make sure to sync to projectId when updating endpoint
await (window as any).ipcRenderer.invoke('delete-task-files', email, history.task_id);
await (window as any).ipcRenderer.invoke('delete-task-files', email, history.task_id, history.project_id ?? undefined);
} catch (error) {
console.warn("Local file cleanup failed:", error);
}

View file

@ -145,9 +145,12 @@ function HeaderWin() {
return;
}
const projectId = projectStore.activeProjectId;
const historyId = projectId ? projectStore.getHistoryId(projectId) : null;
try {
const task = chatStore.tasks[taskId];
// Stop the task if it's running
if (task && task.status === 'running') {
await fetchPut(`/task/${taskId}/take-control`, {
@ -162,11 +165,15 @@ function HeaderWin() {
console.log("Task may not exist on backend:", error);
}
// Delete from history
try {
await proxyFetchDelete(`/api/chat/history/${taskId}`);
} catch (error) {
console.log("Task may not exist in history:", error);
// Delete from history using historyId
if (historyId) {
try {
await proxyFetchDelete(`/api/chat/history/${historyId}`);
} catch (error) {
console.log("History may not exist:", error);
}
} else {
console.warn("No historyId found for project, skipping history deletion");
}
// Remove from local store
@ -176,8 +183,8 @@ function HeaderWin() {
const newTaskId = chatStore.create();
chatStore.setActiveTaskId(newTaskId);
// Navigate to home
navigate("/");
// Navigate to home with replace to force refresh
navigate("/", { replace: true });
toast.success(t("layout.project-ended-successfully"), {
closeButton: true,

View file

@ -1 +1,3 @@
from . import traceroot_wrapper
__all__ = ['traceroot_wrapper']

View file

@ -1,9 +1,16 @@
from pathlib import Path
from typing import Callable
import logging
import traceroot
from dotenv import load_dotenv
# Try to import traceroot, but handle gracefully if not available
try:
import traceroot
TRACEROOT_AVAILABLE = True
except ImportError:
TRACEROOT_AVAILABLE = False
traceroot = None
# Auto-detect module name based on caller's path
def _get_module_name():
"""Automatically detect if this is being called from backend or server."""
@ -26,7 +33,7 @@ env_path = Path(__file__).resolve().parents[1] / '.env'
load_dotenv(env_path)
if traceroot.init():
if TRACEROOT_AVAILABLE and traceroot.init():
from traceroot.logger import get_logger as _get_traceroot_logger
trace = traceroot.trace
@ -69,7 +76,10 @@ else:
# Log fallback mode
_fallback_logger = logging.getLogger("traceroot_wrapper")
_fallback_logger.warning("TraceRoot not initialized - using Python logging as fallback")
if TRACEROOT_AVAILABLE:
_fallback_logger.warning("TraceRoot available but not initialized - using Python logging as fallback")
else:
_fallback_logger.warning("TraceRoot not available - using Python logging as fallback")
__all__ = ['trace', 'get_logger', 'is_enabled']