mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-22 11:15:47 +00:00
Merge branch 'main' into fix/logger
This commit is contained in:
commit
e14dc1070c
27 changed files with 487 additions and 168 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -12,7 +12,7 @@ body:
|
|||
id: version
|
||||
attributes:
|
||||
label: What version of eigent are you using?
|
||||
placeholder: E.g., 0.0.72
|
||||
placeholder: E.g., 0.0.73
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
|
|
|||
103
.github/workflows/codeql.yml
vendored
Normal file
103
.github/workflows/codeql.yml
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '42 22 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: python
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||
# or others). This is typically only required for manual builds.
|
||||
# - name: Setup runtime (example)
|
||||
# uses: actions/setup-example@v1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- name: Run manual build steps
|
||||
if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
|
@ -37,5 +37,6 @@ def create_agent(
|
|||
system_message="You are a helpful assistant that must use the tool get_website_content to get the content of a website.",
|
||||
model=model,
|
||||
tools=[get_website_content],
|
||||
step_timeout=900,
|
||||
)
|
||||
return agent
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ class ListenChatAgent(ChatAgent):
|
|||
pause_event: asyncio.Event | None = None,
|
||||
prune_tool_calls_from_memory: bool = False,
|
||||
enable_snapshot_clean: bool = False,
|
||||
step_timeout: float | None = 900,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
system_message=system_message,
|
||||
|
|
@ -128,6 +129,7 @@ class ListenChatAgent(ChatAgent):
|
|||
pause_event=pause_event,
|
||||
prune_tool_calls_from_memory=prune_tool_calls_from_memory,
|
||||
enable_snapshot_clean=enable_snapshot_clean,
|
||||
step_timeout=step_timeout,
|
||||
)
|
||||
self.api_task_id = api_task_id
|
||||
self.agent_name = agent_name
|
||||
|
|
@ -287,19 +289,25 @@ class ListenChatAgent(ChatAgent):
|
|||
if asyncio.iscoroutinefunction(tool.func):
|
||||
# For async functions, we need to use the async execution path
|
||||
return asyncio.run(self._aexecute_tool(tool_call_request))
|
||||
elif hasattr(tool.func, "__wrapped__"):
|
||||
with set_process_task(self.process_task_id):
|
||||
return super()._execute_tool(tool_call_request)
|
||||
else:
|
||||
args = tool_call_request.args
|
||||
tool_call_id = tool_call_request.tool_call_id
|
||||
try:
|
||||
task_lock = get_task_lock(self.api_task_id)
|
||||
|
||||
toolkit_name = getattr(tool, "_toolkit_name") if hasattr(tool, "_toolkit_name") else "mcp_toolkit"
|
||||
traceroot_logger.debug(
|
||||
f"Agent {self.agent_name} executing tool: {func_name} from toolkit: {toolkit_name} with args: {json.dumps(args, ensure_ascii=False)}"
|
||||
)
|
||||
# Handle all sync tools ourselves to maintain ContextVar context
|
||||
args = tool_call_request.args
|
||||
tool_call_id = tool_call_request.tool_call_id
|
||||
|
||||
# Check if tool is wrapped by @listen_toolkit decorator
|
||||
# If so, the decorator will handle activate/deactivate events
|
||||
has_listen_decorator = hasattr(tool.func, "__wrapped__")
|
||||
|
||||
try:
|
||||
task_lock = get_task_lock(self.api_task_id)
|
||||
|
||||
toolkit_name = getattr(tool, "_toolkit_name") if hasattr(tool, "_toolkit_name") else "mcp_toolkit"
|
||||
traceroot_logger.debug(
|
||||
f"Agent {self.agent_name} executing tool: {func_name} from toolkit: {toolkit_name} with args: {json.dumps(args, ensure_ascii=False)}"
|
||||
)
|
||||
|
||||
# Only send activate event if tool is NOT wrapped by @listen_toolkit
|
||||
if not has_listen_decorator:
|
||||
asyncio.create_task(
|
||||
task_lock.put_queue(
|
||||
ActionActivateToolkitData(
|
||||
|
|
@ -313,29 +321,33 @@ class ListenChatAgent(ChatAgent):
|
|||
)
|
||||
)
|
||||
)
|
||||
# Set process_task context for all tool executions
|
||||
with set_process_task(self.process_task_id):
|
||||
raw_result = tool(**args)
|
||||
traceroot_logger.debug(f"Tool {func_name} executed successfully")
|
||||
if self.mask_tool_output:
|
||||
self._secure_result_store[tool_call_id] = raw_result
|
||||
result = (
|
||||
"[The tool has been executed successfully, but the output"
|
||||
" from the tool is masked. You can move forward]"
|
||||
)
|
||||
mask_flag = True
|
||||
traceroot_logger.debug(f"Tool {func_name} executed successfully")
|
||||
if self.mask_tool_output:
|
||||
self._secure_result_store[tool_call_id] = raw_result
|
||||
result = (
|
||||
"[The tool has been executed successfully, but the output"
|
||||
" from the tool is masked. You can move forward]"
|
||||
)
|
||||
mask_flag = True
|
||||
else:
|
||||
result = raw_result
|
||||
mask_flag = False
|
||||
# Prepare result message with truncation
|
||||
if isinstance(result, str):
|
||||
result_msg = result
|
||||
else:
|
||||
result_str = repr(result)
|
||||
MAX_RESULT_LENGTH = 500
|
||||
if len(result_str) > MAX_RESULT_LENGTH:
|
||||
result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)"
|
||||
else:
|
||||
result = raw_result
|
||||
mask_flag = False
|
||||
# Prepare result message with truncation
|
||||
if isinstance(result, str):
|
||||
result_msg = result
|
||||
else:
|
||||
result_str = repr(result)
|
||||
MAX_RESULT_LENGTH = 500
|
||||
if len(result_str) > MAX_RESULT_LENGTH:
|
||||
result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)"
|
||||
else:
|
||||
result_msg = result_str
|
||||
result_msg = result_str
|
||||
|
||||
# Only send deactivate event if tool is NOT wrapped by @listen_toolkit
|
||||
if not has_listen_decorator:
|
||||
asyncio.create_task(
|
||||
task_lock.put_queue(
|
||||
ActionDeactivateToolkitData(
|
||||
|
|
@ -349,31 +361,40 @@ class ListenChatAgent(ChatAgent):
|
|||
)
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
# Capture the error message to prevent framework crash
|
||||
error_msg = f"Error executing tool '{func_name}': {e!s}"
|
||||
result = f"Tool execution failed: {error_msg}"
|
||||
mask_flag = False
|
||||
traceroot_logger.error(f"Tool execution failed for {func_name}: {e}", exc_info=True)
|
||||
except Exception as e:
|
||||
# Capture the error message to prevent framework crash
|
||||
error_msg = f"Error executing tool '{func_name}': {e!s}"
|
||||
result = f"Tool execution failed: {error_msg}"
|
||||
mask_flag = False
|
||||
traceroot_logger.error(f"Tool execution failed for {func_name}: {e}", exc_info=True)
|
||||
|
||||
return self._record_tool_calling(func_name, args, result, tool_call_id, mask_output=mask_flag)
|
||||
return self._record_tool_calling(
|
||||
func_name, args, result, tool_call_id,
|
||||
mask_output=mask_flag,
|
||||
extra_content=tool_call_request.extra_content,
|
||||
)
|
||||
|
||||
@traceroot.trace()
|
||||
async def _aexecute_tool(self, tool_call_request: ToolCallRequest) -> ToolCallingRecord:
|
||||
func_name = tool_call_request.tool_name
|
||||
tool: FunctionTool = self._internal_tools[func_name]
|
||||
if hasattr(tool.func, "__wrapped__"):
|
||||
with set_process_task(self.process_task_id):
|
||||
return await super()._aexecute_tool(tool_call_request)
|
||||
else:
|
||||
args = tool_call_request.args
|
||||
tool_call_id = tool_call_request.tool_call_id
|
||||
task_lock = get_task_lock(self.api_task_id)
|
||||
|
||||
toolkit_name = getattr(tool, "_toolkit_name") if hasattr(tool, "_toolkit_name") else "mcp_toolkit"
|
||||
traceroot_logger.info(
|
||||
f"Agent {self.agent_name} executing async tool: {func_name} from toolkit: {toolkit_name} with args: {json.dumps(args, ensure_ascii=False)}"
|
||||
)
|
||||
# Always handle tool execution ourselves to maintain ContextVar context
|
||||
args = tool_call_request.args
|
||||
tool_call_id = tool_call_request.tool_call_id
|
||||
task_lock = get_task_lock(self.api_task_id)
|
||||
|
||||
# Check if tool is wrapped by @listen_toolkit decorator
|
||||
# If so, the decorator will handle activate/deactivate events
|
||||
has_listen_decorator = hasattr(tool.func, "__wrapped__")
|
||||
|
||||
toolkit_name = getattr(tool, "_toolkit_name") if hasattr(tool, "_toolkit_name") else "mcp_toolkit"
|
||||
traceroot_logger.info(
|
||||
f"Agent {self.agent_name} executing async tool: {func_name} from toolkit: {toolkit_name} with args: {json.dumps(args, ensure_ascii=False)}"
|
||||
)
|
||||
|
||||
# Only send activate event if tool is NOT wrapped by @listen_toolkit
|
||||
if not has_listen_decorator:
|
||||
await task_lock.put_queue(
|
||||
ActionActivateToolkitData(
|
||||
data={
|
||||
|
|
@ -385,7 +406,9 @@ class ListenChatAgent(ChatAgent):
|
|||
},
|
||||
)
|
||||
)
|
||||
try:
|
||||
try:
|
||||
# Set process_task context for all tool executions
|
||||
with set_process_task(self.process_task_id):
|
||||
# Try different invocation paths in order of preference
|
||||
if hasattr(tool, "func") and hasattr(tool.func, "async_call"):
|
||||
# Case: FunctionTool wrapping an MCP tool
|
||||
|
|
@ -404,29 +427,32 @@ class ListenChatAgent(ChatAgent):
|
|||
result = await tool(**args)
|
||||
|
||||
else:
|
||||
# Fallback: synchronous call
|
||||
# Fallback: synchronous call - call directly in current context
|
||||
# DO NOT use run_in_executor to preserve ContextVar
|
||||
result = tool(**args)
|
||||
# Handle case where synchronous call returns a coroutine
|
||||
if asyncio.iscoroutine(result):
|
||||
result = await result
|
||||
|
||||
except Exception as e:
|
||||
# Capture the error message to prevent framework crash
|
||||
error_msg = f"Error executing async tool '{func_name}': {e!s}"
|
||||
result = {"error": error_msg}
|
||||
traceroot_logger.error(f"Async tool execution failed for {func_name}: {e}", exc_info=True)
|
||||
except Exception as e:
|
||||
# Capture the error message to prevent framework crash
|
||||
error_msg = f"Error executing async tool '{func_name}': {e!s}"
|
||||
result = {"error": error_msg}
|
||||
traceroot_logger.error(f"Async tool execution failed for {func_name}: {e}", exc_info=True)
|
||||
|
||||
# Prepare result message with truncation
|
||||
if isinstance(result, str):
|
||||
result_msg = result
|
||||
# Prepare result message with truncation
|
||||
if isinstance(result, str):
|
||||
result_msg = result
|
||||
else:
|
||||
result_str = repr(result)
|
||||
MAX_RESULT_LENGTH = 500
|
||||
if len(result_str) > MAX_RESULT_LENGTH:
|
||||
result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)"
|
||||
else:
|
||||
result_str = repr(result)
|
||||
MAX_RESULT_LENGTH = 500
|
||||
if len(result_str) > MAX_RESULT_LENGTH:
|
||||
result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)"
|
||||
else:
|
||||
result_msg = result_str
|
||||
result_msg = result_str
|
||||
|
||||
# Only send deactivate event if tool is NOT wrapped by @listen_toolkit
|
||||
if not has_listen_decorator:
|
||||
await task_lock.put_queue(
|
||||
ActionDeactivateToolkitData(
|
||||
data={
|
||||
|
|
@ -438,7 +464,10 @@ class ListenChatAgent(ChatAgent):
|
|||
},
|
||||
)
|
||||
)
|
||||
return self._record_tool_calling(func_name, args, result, tool_call_id)
|
||||
return self._record_tool_calling(
|
||||
func_name, args, result, tool_call_id,
|
||||
extra_content=tool_call_request.extra_content,
|
||||
)
|
||||
|
||||
@traceroot.trace()
|
||||
def clone(self, with_memory: bool = False) -> ChatAgent:
|
||||
|
|
@ -468,6 +497,7 @@ class ListenChatAgent(ChatAgent):
|
|||
mask_tool_output=self.mask_tool_output,
|
||||
pause_event=self.pause_event,
|
||||
prune_tool_calls_from_memory=self.prune_tool_calls_from_memory,
|
||||
step_timeout=self.step_timeout,
|
||||
)
|
||||
|
||||
new_agent.process_task_id = self.process_task_id
|
||||
|
|
@ -746,7 +776,7 @@ def search_agent(options: Chat):
|
|||
"browser_enter",
|
||||
"browser_visit_page",
|
||||
"browser_scroll",
|
||||
"browser_get_som_screenshot",
|
||||
# "browser_get_som_screenshot",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
@ -869,9 +899,6 @@ Your approach depends on available search tools:
|
|||
interactive elements, not the full page text. To see more content on
|
||||
long pages, Navigate with `browser_click`, `browser_back`, and
|
||||
`browser_forward`. Manage multiple pages with `browser_switch_tab`.
|
||||
- **Analysis**: Use `browser_get_som_screenshot` to understand the page
|
||||
layout and identify interactive elements. Since this is a heavy
|
||||
operation, only use it when visual analysis is necessary.
|
||||
- **Interaction**: Use `browser_type` to fill out forms and
|
||||
`browser_enter` to submit or confirm search.
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,13 @@ def _safe_put_queue(task_lock, data):
|
|||
task = asyncio.create_task(task_lock.put_queue(data))
|
||||
if hasattr(task_lock, "add_background_task"):
|
||||
task_lock.add_background_task(task)
|
||||
# Add done callback to handle any exceptions and prevent warnings
|
||||
def handle_task_result(t):
|
||||
try:
|
||||
t.result() # This will raise any exception that occurred
|
||||
except Exception as e:
|
||||
logger.error(f"[listen_toolkit] Background task failed: {e}")
|
||||
task.add_done_callback(handle_task_result)
|
||||
except RuntimeError:
|
||||
# No running event loop, we need to handle this differently
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from camel.toolkits import FileToolkit as BaseFileToolkit
|
|||
from app.component.environment import env
|
||||
from app.service.task import process_task
|
||||
from app.service.task import ActionWriteFileData, Agents, get_task_lock
|
||||
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit
|
||||
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit, _safe_put_queue
|
||||
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
|
||||
|
||||
|
||||
|
|
@ -46,12 +46,15 @@ class FileToolkit(BaseFileToolkit, AbstractToolkit):
|
|||
res = super().write_to_file(title, content, filename, encoding, use_latex)
|
||||
if "Content successfully written to file: " in res:
|
||||
task_lock = get_task_lock(self.api_task_id)
|
||||
asyncio.create_task(
|
||||
task_lock.put_queue(
|
||||
ActionWriteFileData(
|
||||
process_task_id=process_task.get(),
|
||||
data=res.replace("Content successfully written to file: ", ""),
|
||||
)
|
||||
# Capture ContextVar value before creating async task
|
||||
current_process_task_id = process_task.get("")
|
||||
|
||||
# Use _safe_put_queue to handle both sync and async contexts
|
||||
_safe_put_queue(
|
||||
task_lock,
|
||||
ActionWriteFileData(
|
||||
process_task_id=current_process_task_id,
|
||||
data=res.replace("Content successfully written to file: ", ""),
|
||||
)
|
||||
)
|
||||
return res
|
||||
|
|
|
|||
|
|
@ -107,12 +107,16 @@ class HumanToolkit(BaseToolkit, AbstractToolkit):
|
|||
print(message_attachment)
|
||||
logger.info(f"\nAgent Message:\n{message_title} {message_description} {message_attachment}")
|
||||
task_lock = get_task_lock(self.api_task_id)
|
||||
asyncio.create_task(
|
||||
task_lock.put_queue(
|
||||
ActionNoticeData(
|
||||
process_task_id=process_task.get(""),
|
||||
data=f"{message_description}",
|
||||
)
|
||||
# Capture ContextVar value before creating async task
|
||||
current_process_task_id = process_task.get("")
|
||||
|
||||
# Use _safe_put_queue to handle both sync and async contexts
|
||||
from app.utils.listen.toolkit_listen import _safe_put_queue
|
||||
_safe_put_queue(
|
||||
task_lock,
|
||||
ActionNoticeData(
|
||||
process_task_id=current_process_task_id,
|
||||
data=f"{message_description}",
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from camel.toolkits import PPTXToolkit as BasePPTXToolkit
|
|||
|
||||
from app.component.environment import env
|
||||
from app.service.task import ActionWriteFileData, Agents, get_task_lock
|
||||
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit
|
||||
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit, _safe_put_queue
|
||||
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
|
||||
from app.service.task import process_task
|
||||
|
||||
|
|
@ -39,7 +39,12 @@ class PPTXToolkit(BasePPTXToolkit, AbstractToolkit):
|
|||
res = super().create_presentation(content, filename, template)
|
||||
if "PowerPoint presentation successfully created" in res:
|
||||
task_lock = get_task_lock(self.api_task_id)
|
||||
asyncio.create_task(
|
||||
task_lock.put_queue(ActionWriteFileData(process_task_id=process_task.get(), data=str(file_path)))
|
||||
# Capture ContextVar value before creating async task
|
||||
current_process_task_id = process_task.get("")
|
||||
|
||||
# Use _safe_put_queue to handle both sync and async contexts
|
||||
_safe_put_queue(
|
||||
task_lock,
|
||||
ActionWriteFileData(process_task_id=current_process_task_id, data=str(file_path))
|
||||
)
|
||||
return res
|
||||
|
|
|
|||
|
|
@ -134,6 +134,27 @@ class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit):
|
|||
exc_info=True
|
||||
)
|
||||
|
||||
def shell_exec(self, id: str, command: str, block: bool = True) -> str:
|
||||
r"""Executes a shell command in blocking or non-blocking mode.
|
||||
|
||||
Args:
|
||||
id (str): A unique identifier for the command's session.
|
||||
command (str): The shell command to execute.
|
||||
block (bool, optional): Determines the execution mode. Defaults to True.
|
||||
|
||||
Returns:
|
||||
str: The output of the command execution.
|
||||
"""
|
||||
result = super().shell_exec(id, command, block)
|
||||
|
||||
# If the command executed successfully but returned empty output,
|
||||
# provide a clear success message to help the AI agent understand
|
||||
# that the command completed without error.
|
||||
if block and result == "":
|
||||
return "Command executed successfully (no output)."
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def shutdown(cls):
|
||||
if cls._thread_pool:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from camel.societies.workforce.workforce import (
|
|||
WorkforceState,
|
||||
DEFAULT_WORKER_POOL_SIZE,
|
||||
)
|
||||
from camel.societies.workforce.utils import FailureHandlingConfig
|
||||
from camel.societies.workforce.task_channel import TaskChannel
|
||||
from camel.societies.workforce.base import BaseNode
|
||||
from camel.societies.workforce.utils import TaskAssignResult
|
||||
|
|
@ -58,6 +59,9 @@ class Workforce(BaseWorkforce):
|
|||
graceful_shutdown_timeout=graceful_shutdown_timeout,
|
||||
share_memory=share_memory,
|
||||
use_structured_output_handler=use_structured_output_handler,
|
||||
failure_handling_config=FailureHandlingConfig(
|
||||
enabled_strategies=["retry", "replan"],
|
||||
),
|
||||
)
|
||||
logger.info(f"[WF-LIFECYCLE] ✅ Workforce.__init__ COMPLETED, id={id(self)}")
|
||||
|
||||
|
|
@ -217,8 +221,8 @@ class Workforce(BaseWorkforce):
|
|||
return subtasks
|
||||
|
||||
async def _find_assignee(self, tasks: List[Task]) -> TaskAssignResult:
|
||||
# Task assignment phase: send "waiting for execution" notification
|
||||
# to the frontend, and send "start execution" notification when the
|
||||
# Task assignment phase: send "waiting for execution" notification
|
||||
# to the frontend, and send "start execution" notification when the
|
||||
# task actually begins execution
|
||||
assigned = await super()._find_assignee(tasks)
|
||||
|
||||
|
|
@ -238,6 +242,18 @@ class Workforce(BaseWorkforce):
|
|||
content = ""
|
||||
else:
|
||||
content = task_obj.content
|
||||
|
||||
# Skip sending notification if this is a retry/replan for an already assigned task
|
||||
# This prevents the frontend from showing "Reassigned" when a task is being retried
|
||||
# with the same or different worker due to failure recovery
|
||||
if task_obj and task_obj.assigned_worker_id:
|
||||
logger.debug(
|
||||
f"[WF] ASSIGN Skip notification for task {item.task_id}: "
|
||||
f"already has assigned_worker_id={task_obj.assigned_worker_id}, "
|
||||
f"new assignee={item.assignee_id} (retry/replan scenario)"
|
||||
)
|
||||
continue
|
||||
|
||||
# Asynchronously send waiting notification
|
||||
task = asyncio.create_task(
|
||||
task_lock.put_queue(
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ name = "backend"
|
|||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = "==3.10.16"
|
||||
requires-python = ">=3.10,<3.11"
|
||||
dependencies = [
|
||||
"camel-ai[eigent]==0.2.80a3",
|
||||
"camel-ai[eigent] @ git+https://github.com/camel-ai/camel.git@feature/workforce-configurable-fail-handling",
|
||||
"fastapi>=0.115.12",
|
||||
"fastapi-babel>=1.0.0",
|
||||
"uvicorn[standard]>=0.34.2",
|
||||
|
|
|
|||
12
backend/uv.lock
generated
12
backend/uv.lock
generated
|
|
@ -1,6 +1,6 @@
|
|||
version = 1
|
||||
revision = 2
|
||||
requires-python = "==3.10.16"
|
||||
requires-python = "==3.10.*"
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
|
|
@ -249,7 +249,7 @@ dev = [
|
|||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "aiofiles", specifier = ">=24.1.0" },
|
||||
{ name = "camel-ai", extras = ["eigent"], specifier = "==0.2.80a3" },
|
||||
{ name = "camel-ai", extras = ["eigent"], git = "https://github.com/camel-ai/camel.git?rev=feature%2Fworkforce-configurable-fail-handling" },
|
||||
{ name = "debugpy", specifier = ">=1.8.17" },
|
||||
{ name = "fastapi", specifier = ">=0.115.12" },
|
||||
{ name = "fastapi-babel", specifier = ">=1.0.0" },
|
||||
|
|
@ -333,8 +333,8 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "camel-ai"
|
||||
version = "0.2.80a3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
version = "0.2.82.dev1"
|
||||
source = { git = "https://github.com/camel-ai/camel.git?rev=feature%2Fworkforce-configurable-fail-handling#d84dbec44ecb26add2638857059e2f92d896d308" }
|
||||
dependencies = [
|
||||
{ name = "astor" },
|
||||
{ name = "colorama" },
|
||||
|
|
@ -349,10 +349,6 @@ dependencies = [
|
|||
{ name = "tiktoken" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/72/691a6126e062b5c5a24b7c5a116f690be1b50127a359c7d53d13435d27ef/camel_ai-0.2.80a3.tar.gz", hash = "sha256:edda7cb0466a63c4d8f92ae4ea2d11ee6946b69146336176346db3227de747d9", size = 1013184, upload-time = "2025-11-20T18:52:25.016Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/2c/a1203a2fa8e432deb4e6a7740a4dff532b79e0129bbf2e60626fca8f4271/camel_ai-0.2.80a3-py3-none-any.whl", hash = "sha256:2f398e648edae57bf23372a9cc812c82bf64f007178cdaf7ba113f2fde6761a6", size = 1473469, upload-time = "2025-11-20T18:52:22.853Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
eigent = [
|
||||
|
|
|
|||
48
docs/core/models/gemini.md
Normal file
48
docs/core/models/gemini.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
title: "Gemini"
|
||||
description: "This guide walks you through setting up your Google Gemini API key within Eigent to enable the Gemini model for your AI workforce."
|
||||
---
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Get your API Key:** If you haven't already, generate a key at [Google AI Studio](https://aistudio.google.com/).
|
||||
- **Copy the Key:** Keep your API key ready to paste.
|
||||
|
||||
### Configuration Steps
|
||||
|
||||
**1. Access Application Settings**
|
||||
|
||||
- Launch Eigent and navigate to the **Home Page**.
|
||||
- Click on the **Settings** tab (usually located in the sidebar or top navigation).
|
||||
|
||||

|
||||
|
||||
**2. Locate Model Configuration**
|
||||
|
||||
- In the Settings menu, find and select the **Models** section.
|
||||
- Scroll down to the **Custom Model** area.
|
||||
- Look for the **Gemini Config** card.
|
||||
-
|
||||
|
||||

|
||||
|
||||
**3. Enter API Details** Click on the Gemini Config card and fill in the following fields:
|
||||
|
||||
- **API Key:** Paste the key you generated from Google AI Studio.
|
||||
- **API Host:** Enter the appropriate API endpoint host (e.g., `generativelanguage.googleapis.com`).
|
||||
- **Model Type:** Enter the specific model version you wish to use.
|
||||
- _Example:_ `gemini-3-pro-preview`
|
||||
- **Save:** Click the **Save** button to apply your changes.
|
||||
|
||||

|
||||
|
||||
**4. Set as Default & Verify**
|
||||
|
||||
- Once saved, the **"Set as Default"** button on the Gemini Config card will be selected/active.
|
||||
- **You are ready to go.** Your Eigent agents can now utilize the Gemini model.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
> **Video Tutorial:** Prefer a visual guide? **<u>Watch the full configuration video here</u>**
|
||||
|
|
@ -1,33 +1,9 @@
|
|||
---
|
||||
title: Models
|
||||
description: Configure and deploy your preferred LLM models with Eigent.
|
||||
icon: server
|
||||
title: "Models (Local Model)"
|
||||
description: "Configure and deploy your preferred LLM models with Eigent."
|
||||
icon: "server"
|
||||
---
|
||||
|
||||
Eigent supports flexible integration and deployment of top LLMs and multimodal models. You can follow the steps below to set up your preferred LLM models.
|
||||
|
||||
1. Click Settings
|
||||
|
||||

|
||||
|
||||
2. Close Eigent Cloud Version
|
||||
|
||||

|
||||
|
||||
3. Configure your APIKEY and Model Type
|
||||
|
||||

|
||||
|
||||
4. Configure the Google Search toolkit
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
You can refer to the following document for detailed information on how to configure **GOOGLE_API_KEY** and **SEARCH_ENGINE_ID :** https://developers.google.com/custom-search/v1/overview
|
||||
|
||||
Now,start enjoying Eigent!
|
||||
|
||||
## **Self-Host Model**
|
||||
|
||||
1. Configure your self-host model
|
||||
|
|
@ -71,8 +47,7 @@ ollama pull qwen2.5:7b
|
|||
|
||||

|
||||
|
||||

|
||||
You can refer to the following document for detailed information on how to configure **GOOGLE_API_KEY** and **SEARCH_ENGINE_ID :** https://developers.google.com/custom-search/v1/overview
|
||||
<img src="/docs/images/models_configure_tools_key.png" alt="configure_searchtoolsapi" /> You can refer to the following document for detailed information on how to configure **GOOGLE_API_KEY** and **SEARCH_ENGINE_ID :** https://developers.google.com/custom-search/v1/overview
|
||||
|
||||
## **API KEY Reference**
|
||||
|
||||
|
|
@ -47,24 +47,35 @@
|
|||
"groups": [
|
||||
{
|
||||
"group": "Get Started",
|
||||
"icon": "rocket",
|
||||
"pages": [
|
||||
"/get_started/welcome",
|
||||
"/get_started/installation",
|
||||
"/get_started/installation",
|
||||
"/get_started/quick_start"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Core",
|
||||
"icon": "key",
|
||||
"pages": [
|
||||
"/core/concepts",
|
||||
"/core/workforce",
|
||||
"/core/models",
|
||||
{
|
||||
"group": "Models",
|
||||
"icon": "brain",
|
||||
"expanded": true,
|
||||
"pages": [
|
||||
"/core/models/gemini",
|
||||
"/core/models/local-model"
|
||||
]
|
||||
},
|
||||
"/core/tools",
|
||||
"/core/workers"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Troubleshooting",
|
||||
"icon": "question-circle",
|
||||
"pages": [
|
||||
"/troubleshooting/support",
|
||||
"/troubleshooting/bug"
|
||||
|
|
|
|||
BIN
docs/images/gemini_1.png
Normal file
BIN
docs/images/gemini_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/images/gemini_2.png
Normal file
BIN
docs/images/gemini_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2 MiB |
BIN
docs/images/gemini_3.png
Normal file
BIN
docs/images/gemini_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/images/gemini_4.png
Normal file
BIN
docs/images/gemini_4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
|
|
@ -1316,14 +1316,6 @@ async function createWindow() {
|
|||
});
|
||||
});
|
||||
} else {
|
||||
// REMOVED: Previously this block would directly set initState='done' when installation
|
||||
// was already complete, bypassing the backend readiness check.
|
||||
//
|
||||
// This caused a critical bug where:
|
||||
// 1. Frontend would show immediately (initState='done')
|
||||
// 2. Backend would still be starting (10-15 seconds)
|
||||
// 3. Users could interact before backend was ready, causing connection errors
|
||||
//
|
||||
// The proper flow is now handled by useInstallationSetup.ts with dual-check mechanism:
|
||||
// 1. Installation complete event → installationCompleted.current = true
|
||||
// 2. Backend ready event → backendReady.current = true
|
||||
|
|
@ -1354,6 +1346,9 @@ async function createWindow() {
|
|||
log.info('Window is ready, processing queued protocol URLs...');
|
||||
processQueuedProtocolUrls();
|
||||
|
||||
// Wait for React components to mount and register event listeners
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Now check and install dependencies
|
||||
let res:PromiseReturnType = await checkAndInstallDepsOnUpdate({ win });
|
||||
if (!res.success) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getBackendPath, getBinaryPath, getCachePath, getVenvPath, isBinaryExists, runInstallScript } from "./utils/process";
|
||||
import { getBackendPath, getBinaryPath, getCachePath, getVenvPath, getUvEnv, isBinaryExists, runInstallScript, killProcessByName } from "./utils/process";
|
||||
import { spawn, exec } from 'child_process'
|
||||
import log from 'electron-log'
|
||||
import fs from 'fs'
|
||||
|
|
@ -21,16 +21,16 @@ export function getMainWindow(): BrowserWindow | null {
|
|||
export async function checkToolInstalled() {
|
||||
return new Promise<PromiseReturnType>(async (resolve, reject) => {
|
||||
if (!(await isBinaryExists('uv'))) {
|
||||
resolve({success: false, message: "uv doesn't exist"})
|
||||
resolve({ success: false, message: "uv doesn't exist" })
|
||||
return
|
||||
}
|
||||
|
||||
if (!(await isBinaryExists('bun'))) {
|
||||
resolve({success: false, message: "Bun doesn't exist"})
|
||||
resolve({ success: false, message: "Bun doesn't exist" })
|
||||
return
|
||||
}
|
||||
|
||||
resolve({success: true, message: "Tools exist already"})
|
||||
resolve({ success: true, message: "Tools exist already" })
|
||||
})
|
||||
|
||||
}
|
||||
|
|
@ -159,12 +159,13 @@ export async function startBackend(setPort?: (port: number) => void): Promise<an
|
|||
fs.mkdirSync(npmCacheDir, { recursive: true });
|
||||
}
|
||||
|
||||
const uvEnv = getUvEnv(currentVersion);
|
||||
const env = {
|
||||
...process.env,
|
||||
...uvEnv,
|
||||
SERVER_URL: "https://dev.eigent.ai/api",
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
PYTHONUNBUFFERED: '1',
|
||||
UV_PROJECT_ENVIRONMENT: venvPath,
|
||||
npm_config_cache: npmCacheDir,
|
||||
}
|
||||
|
||||
|
|
@ -202,10 +203,79 @@ export async function startBackend(setPort?: (port: number) => void): Promise<an
|
|||
{ cwd: backendPath, env: env }
|
||||
);
|
||||
log.info(`Python test output: ${pythonTest.trim()}`);
|
||||
} catch (testErr) {
|
||||
log.error(`Pre-flight check failed: ${testErr}`);
|
||||
reject(new Error(`Backend environment check failed: ${testErr}`));
|
||||
return;
|
||||
} catch (testErr: any) {
|
||||
log.warn(`Pre-flight check failed, attempting repair: ${testErr}`);
|
||||
|
||||
try {
|
||||
// Attempt to repair the environment
|
||||
log.info("Attempting to repair environment...");
|
||||
|
||||
// Cleanup stale processes and locks
|
||||
log.info("Cleaning up stale processes and locks...");
|
||||
await killProcessByName('uv');
|
||||
await killProcessByName('python');
|
||||
|
||||
// Try to remove the lock file explicitly if it exists
|
||||
try {
|
||||
const lockFile = path.join(getCachePath('uv_python'), '.lock');
|
||||
if (fs.existsSync(lockFile)) {
|
||||
fs.unlinkSync(lockFile);
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn(`Failed to remove lock file: ${e}`);
|
||||
}
|
||||
|
||||
// Cleanup corrupted python cache
|
||||
try {
|
||||
const pythonCacheDir = getCachePath('uv_python');
|
||||
if (fs.existsSync(pythonCacheDir)) {
|
||||
log.info(`Removing potentially corrupted Python cache: ${pythonCacheDir}`);
|
||||
fs.rmSync(pythonCacheDir, { recursive: true, force: true });
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn(`Failed to remove Python cache: ${e}`);
|
||||
}
|
||||
|
||||
// Cleanup corrupted venv (pyvenv.cfg may reference non-existent Python version)
|
||||
try {
|
||||
if (fs.existsSync(venvPath)) {
|
||||
log.info(`Removing potentially corrupted venv: ${venvPath}`);
|
||||
fs.rmSync(venvPath, { recursive: true, force: true });
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn(`Failed to remove venv: ${e}`);
|
||||
}
|
||||
|
||||
// Use proxy if in China (simple check based on timezone)
|
||||
// Add official PyPI as fallback for packages not available on mirror
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const proxyArgs = timezone === 'Asia/Shanghai'
|
||||
? [
|
||||
'--default-index', 'https://mirrors.aliyun.com/pypi/simple/',
|
||||
'--index', 'https://pypi.org/simple/'
|
||||
]
|
||||
: [];
|
||||
|
||||
// Step 1: Ensure Python is installed (fixes corrupted/missing Python)
|
||||
log.info("Step 1: Ensuring Python is installed...");
|
||||
await execAsync(`${uv_path} python install 3.10`, { cwd: backendPath, env: env });
|
||||
|
||||
// Step 2: Sync dependencies
|
||||
log.info("Step 2: Syncing dependencies...");
|
||||
const syncArgs = ['sync', '--no-dev', ...proxyArgs];
|
||||
await execAsync(`${uv_path} ${syncArgs.join(' ')}`, { cwd: backendPath, env: env });
|
||||
|
||||
// Retry the check
|
||||
const { stdout: pythonTest } = await execAsync(
|
||||
`${uv_path} run python -c "print('Python OK')"`,
|
||||
{ cwd: backendPath, env: env }
|
||||
);
|
||||
log.info(`Python test output after repair: ${pythonTest.trim()}`);
|
||||
} catch (repairErr) {
|
||||
log.error(`Repair failed: ${repairErr}`);
|
||||
reject(new Error(`Backend environment check failed: ${testErr}\nRepair failed: ${repairErr}`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const node_process = spawn(
|
||||
|
|
@ -265,7 +335,7 @@ export async function startBackend(setPort?: (port: number) => void): Promise<an
|
|||
setTimeout(() => {
|
||||
try {
|
||||
process.kill(-proc.pid, 'SIGKILL');
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
}, 1000);
|
||||
} catch (e) {
|
||||
log.error(`Failed to kill process group: ${e}`);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import path from 'node:path'
|
|||
import log from 'electron-log'
|
||||
import { getMainWindow } from './init'
|
||||
import fs from 'node:fs'
|
||||
import { getBackendPath, getBinaryPath, getCachePath, getVenvPath, cleanupOldVenvs, isBinaryExists, runInstallScript } from './utils/process'
|
||||
import { getBackendPath, getBinaryPath, getCachePath, getVenvPath, getUvEnv, cleanupOldVenvs, isBinaryExists, runInstallScript } from './utils/process'
|
||||
import { spawn } from 'child_process'
|
||||
import { safeMainWindowSend } from './utils/safeWebContentsSend'
|
||||
import os from 'node:os'
|
||||
|
|
@ -242,7 +242,6 @@ class InstallLogs {
|
|||
|
||||
constructor(extraArgs:string[], version: string) {
|
||||
console.log('start install dependencies', extraArgs, 'version:', version)
|
||||
const venvPath = getVenvPath(version);
|
||||
this.version = version;
|
||||
|
||||
this.node_process = spawn(uv_path, [
|
||||
|
|
@ -253,10 +252,7 @@ class InstallLogs {
|
|||
cwd: backendPath,
|
||||
env: {
|
||||
...process.env,
|
||||
UV_TOOL_DIR: getCachePath('uv_tool'),
|
||||
UV_PYTHON_INSTALL_DIR: getCachePath('uv_python'),
|
||||
UV_PROJECT_ENVIRONMENT: venvPath,
|
||||
UV_HTTP_TIMEOUT: '180', // 3 minutes timeout
|
||||
...getUvEnv(version),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,3 +142,49 @@ export async function isBinaryExists(name: string): Promise<boolean> {
|
|||
|
||||
return await fs.existsSync(cmd)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unified UV environment variables for consistent Python environment management.
|
||||
* This ensures both installation and runtime use the same paths.
|
||||
* @param version - The app version for venv path
|
||||
* @returns Environment variables for UV commands
|
||||
*/
|
||||
export function getUvEnv(version: string): Record<string, string> {
|
||||
return {
|
||||
UV_PYTHON_INSTALL_DIR: getCachePath('uv_python'),
|
||||
UV_TOOL_DIR: getCachePath('uv_tool'),
|
||||
UV_PROJECT_ENVIRONMENT: getVenvPath(version),
|
||||
UV_HTTP_TIMEOUT: '300',
|
||||
}
|
||||
}
|
||||
|
||||
export async function killProcessByName(name: string): Promise<void> {
|
||||
const platform = process.platform
|
||||
try {
|
||||
if (platform === 'win32') {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// /F = force, /IM = image name
|
||||
const cmd = spawn('taskkill', ['/F', '/IM', `${name}.exe`])
|
||||
cmd.on('close', (code) => {
|
||||
// code 0 = success, code 128 = process not found (which is fine)
|
||||
if (code === 0 || code === 128) resolve()
|
||||
else reject(new Error(`taskkill exited with code ${code}`))
|
||||
})
|
||||
cmd.on('error', reject)
|
||||
})
|
||||
} else {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const cmd = spawn('pkill', ['-9', name])
|
||||
cmd.on('close', (code) => {
|
||||
// code 0 = success, code 1 = no process found (which is fine)
|
||||
if (code === 0 || code === 1) resolve()
|
||||
else reject(new Error(`pkill exited with code ${code}`))
|
||||
})
|
||||
cmd.on('error', reject)
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore errors, just best effort
|
||||
log.warn(`Failed to kill process ${name}:`, err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "eigent",
|
||||
"version": "0.0.72",
|
||||
"version": "0.0.73",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"description": "Eigent",
|
||||
"author": "Eigent.AI",
|
||||
|
|
@ -133,4 +133,4 @@
|
|||
"engines": {
|
||||
"node": ">=18.0.0 <23.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +53,9 @@ async function downloadBunBinary(bun_download_url,platform, arch, version = DEFA
|
|||
const downloadUrl = `${bun_download_url}/bun-v${version}/${packageName}`
|
||||
const tempdir = os.tmpdir()
|
||||
// Create a temporary file for the downloaded binary
|
||||
const tempFilename = path.join(tempdir, packageName)
|
||||
// Use unique temp filename to avoid race conditions when multiple processes install simultaneously
|
||||
const uniqueSuffix = `${process.pid}-${Date.now()}`
|
||||
const tempFilename = path.join(tempdir, `${uniqueSuffix}-${packageName}`)
|
||||
|
||||
try {
|
||||
console.log(`Downloading bun ${version} for ${platformKey}...`)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ async function downloadUvBinary(
|
|||
// Download URL for the specific binary
|
||||
const downloadUrl = `${uv_download_url}/${version}/${packageName}`;
|
||||
const tempdir = os.tmpdir();
|
||||
const tempFilename = path.join(tempdir, packageName);
|
||||
// Use unique temp filename to avoid race conditions when multiple processes install simultaneously
|
||||
const uniqueSuffix = `${process.pid}-${Date.now()}`;
|
||||
const tempFilename = path.join(tempdir, `${uniqueSuffix}-${packageName}`);
|
||||
|
||||
try {
|
||||
console.log(`Downloading uv ${version} for ${platformKey}...`);
|
||||
|
|
|
|||
|
|
@ -155,15 +155,6 @@ export const useInstallationSetup = () => {
|
|||
|
||||
if (installationStatus.success && installationStatus.isInstalling) {
|
||||
startInstallation();
|
||||
} else if (initState !== 'done' && toolResult) {
|
||||
if (toolResult.success && !toolResult.isInstalled) {
|
||||
console.log('[useInstallationSetup] Tools missing and not installing. Starting installation...');
|
||||
try {
|
||||
await performInstallation();
|
||||
} catch (installError) {
|
||||
console.error('[useInstallationSetup] Installation failed:', installError);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useInstallationSetup] Failed to check installation status:', err);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue