myagent-ai 1.16.17 → 1.16.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Dockerfile CHANGED
@@ -1,14 +1,38 @@
1
1
  # MyAgent - 本地桌面端执行型 AI 助手
2
2
  # Docker 构建文件 (主要用于服务器部署)
3
- FROM python:3.11-slim
3
+ FROM python:3.13-slim
4
4
 
5
5
  WORKDIR /app
6
6
 
7
7
  # 安装系统依赖
8
+ # [v1.16.18] 包含 poppler-utils (PDF提取)、Chrome 共享库 (浏览器自动化)
8
9
  RUN apt-get update && apt-get install -y --no-install-recommends \
9
10
  python3-pip \
11
+ poppler-utils \
12
+ # Chrome/Chromium 运行所需的共享库
13
+ libnss3 \
14
+ libatk1.0-0 \
15
+ libatk-bridge2.0-0 \
16
+ libcups2 \
17
+ libdrm2 \
18
+ libxkbcommon0 \
19
+ libxcomposite1 \
20
+ libxdamage1 \
21
+ libxfixes3 \
22
+ libxrandr2 \
23
+ libgbm1 \
24
+ libpango-1.0-0 \
25
+ libcairo2 \
26
+ libasound2 \
27
+ libatspi2.0-0 \
28
+ # Node.js (浏览器自动化 MCP 需要)
29
+ nodejs \
30
+ npm \
10
31
  && rm -rf /var/lib/apt/lists/*
11
32
 
33
+ # 安装 Puppeteer 的 Chrome (自包含,容器环境最可靠)
34
+ RUN npx -y puppeteer browsers install chrome@stable || true
35
+
12
36
  # 安装 Python 依赖
13
37
  COPY requirements.txt .
14
38
  RUN pip install --no-cache-dir -r requirements.txt
@@ -73,6 +73,7 @@ class MainAgent(BaseAgent):
73
73
  - **执行代码**: 用 `code` 工具(language: python/javascript/shell)
74
74
  - **执行命令**: 用 `command` 或 `command_run` 工具
75
75
  - **文件操作**: 用 `file_read` / `file_write` / `file_list` 等文件工具
76
+ - **发送文件给用户**: 用 `file_send` 工具(参数: file_path=文件路径, description=说明),当你生成或处理了文件需要返回给用户时使用
76
77
  - **主动召回记忆**: 用 `recall_memory` 工具(参数: keyword=关键字, time_point=可选时间点如"2025-01", limit=数量默认5),根据关键字和时间搜索历史记忆
77
78
  """
78
79
 
@@ -552,11 +553,18 @@ class MainAgent(BaseAgent):
552
553
 
553
554
  # 保存用户消息到会话记忆
554
555
  if self.memory:
556
+ # [v1.16.17] 附加附件元数据到用户消息
557
+ _attachment_meta = {}
558
+ if context.metadata.get("user_image_files"):
559
+ _attachment_meta["images"] = context.metadata["user_image_files"]
560
+ if context.metadata.get("user_file_files"):
561
+ _attachment_meta["files"] = context.metadata["user_file_files"]
555
562
  self.memory.add_session(
556
563
  session_id=context.session_id,
557
564
  role="user",
558
565
  content=context.user_message,
559
566
  key="user_input",
567
+ metadata=_attachment_meta if _attachment_meta else None,
560
568
  )
561
569
 
562
570
  # 加载相关记忆 (recall from previous round or initial load)
@@ -1561,6 +1569,20 @@ class MainAgent(BaseAgent):
1561
1569
  result = {"success": False, "error": f"记忆召回失败: {re_err}"}
1562
1570
  logger.warning(f"[{task_id}] recall_memory 工具异常: {re_err}")
1563
1571
 
1572
+ elif tool_name == "file_send":
1573
+ # [v1.16.17→18] 文件发送工具 — 让 Agent 向用户发送文件
1574
+ try:
1575
+ from skills.file_send import FileSendSkill
1576
+ _fskill = FileSendSkill()
1577
+ _fpath = params.get("file_path", "")
1578
+ _fdesc = params.get("description", "")
1579
+ # [v1.16.18] 使用当前作用域的 stream_callback(而非 context._stream_callback)
1580
+ _fresult = await _fskill.execute(_fpath, _fdesc, stream_callback=stream_callback)
1581
+ result = {"success": True, "output": json.dumps(_fresult, ensure_ascii=False, indent=2), "data": _fresult}
1582
+ except Exception as _fse:
1583
+ result = {"success": False, "error": f"文件发送失败: {_fse}"}
1584
+ logger.warning(f"[{task_id}] file_send 工具异常: {_fse}")
1585
+
1564
1586
  elif self.skills:
1565
1587
  exec_result = await self.skills.execute(tool_name, **params)
1566
1588
  if exec_result is None:
@@ -13,7 +13,8 @@ core/deps_checker.py - 自动依赖检测与安装
13
13
  依赖映射:
14
14
  核心功能: openai, aiohttp, requests
15
15
  搜索技能: bs4
16
- 系统技能: psutil
16
+ PDF 处理: PyPDF2
17
+ 系统技能: psutil, chardet
17
18
  托盘功能: pystray, PIL
18
19
  语音合成: edge_tts
19
20
  浏览器自动化: chrome-devtools-mcp (Node.js, Chrome DevTools Protocol)
@@ -64,6 +65,24 @@ DEPENDENCIES: List[DepInfo] = [
64
65
  # ── 搜索技能 ──
65
66
  DepInfo("bs4", "beautifulsoup4", "4.12.0", "search", "all"),
66
67
 
68
+ # ── PDF 处理 ──
69
+ DepInfo("PyPDF2", "PyPDF2", "3.0.0", "pdf", "all",
70
+ note="纯 Python PDF 文本提取(pdftotext 失败时的备用方案)"),
71
+
72
+ # ── 文档处理 (Excel/Word/PPT) ──
73
+ DepInfo("openpyxl", "openpyxl", "3.1.0", "doc", "all",
74
+ note="Excel (.xlsx) 文件读取"),
75
+ DepInfo("docx", "python-docx", "1.1.0", "doc", "all",
76
+ note="Word (.docx) 文件读取"),
77
+ DepInfo("pptx", "python-pptx", "0.6.21", "doc", "all",
78
+ note="PowerPoint (.pptx) 文件读取"),
79
+ DepInfo("xlrd", "xlrd", "2.0.0", "doc", "all",
80
+ note="旧版 Excel (.xls) 文件读取"),
81
+
82
+ # ── 文件编码检测 ──
83
+ DepInfo("chardet", "chardet", "5.0.0", "system", "all",
84
+ note="文件编码自动检测"),
85
+
67
86
  # ── 系统技能 ──
68
87
  DepInfo("psutil", "psutil", "5.9.0", "system", "all"),
69
88
 
@@ -404,6 +423,8 @@ def ensure_skill_deps(skill_category: str) -> bool:
404
423
  "tts": {"tts"},
405
424
  "stt": {"stt"},
406
425
  "tray": {"tray"},
426
+ "pdf": {"pdf"},
427
+ "doc": {"doc"},
407
428
  }
408
429
 
409
430
  cats = category_map.get(skill_category, {skill_category})
@@ -0,0 +1 @@
1
+ %PDF-1.4 test content
@@ -231,11 +231,16 @@ class ExecutionEngine:
231
231
 
232
232
  # 沙盒模式: 检查 Docker 可用性
233
233
  self._docker_available = False
234
+ self._docker_warned = False # [v1.16.19] 标记是否已警告过 Docker 不可用
234
235
  if self.execution_mode == "sandbox":
235
236
  self._docker_available = self._check_docker()
236
237
  if not self._docker_available:
237
- logger.warning("沙盒模式: Docker 不可用,将回退到本机执行")
238
- self.execution_mode = "local"
238
+ # [v1.16.19] Docker 不可用时,使用轻量级进程沙盒(而非回退到本机模式)
239
+ # 轻量级沙盒: 临时工作目录 + subprocess 隔离,无全局锁,支持多 agent 并发
240
+ logger.info("沙盒模式: Docker 不可用,使用轻量级进程沙盒(临时工作目录隔离)")
241
+ self._docker_warned = True
242
+ # 不再修改 self.execution_mode,保持 "sandbox"
243
+ # 轻量级沙盒仍然标记为 sandbox 模式,不走全局执行锁
239
244
 
240
245
  # ── 启动时缓存: 检测平台 & Shell ────────────────────────────────────
241
246
  self._platform = detect_platform()
@@ -525,13 +530,17 @@ class ExecutionEngine:
525
530
  self._execution_count += 1
526
531
  exec_id = f"exec_{self._execution_count}"
527
532
 
528
- mode_label = "本地" if self.execution_mode == "local" else "沙盒(Docker)"
533
+ mode_label = "本地" if self.execution_mode == "local" else ("沙盒" if self._docker_available else "沙盒")
529
534
  logger.info(f"[{exec_id}] 开始执行 ({language}, mode={mode_label}, timeout={exec_timeout}s)")
530
535
  logger.debug(f"[{exec_id}] 代码:\n{code[:500]}")
531
536
 
532
537
  # 根据执行模式选择执行方式
533
- if self.execution_mode == "sandbox" and self._docker_available:
534
- result = await self._execute_sandbox(language, code, exec_timeout, work_dir, env, exec_id)
538
+ if self.execution_mode == "sandbox":
539
+ if self._docker_available:
540
+ result = await self._execute_sandbox(language, code, exec_timeout, work_dir, env, exec_id)
541
+ else:
542
+ # [v1.16.19] Docker 不可用时,使用轻量级进程沙盒
543
+ result = await self._execute_lightweight_sandbox(language, code, exec_timeout, work_dir, env, exec_id)
535
544
  elif language == "python":
536
545
  result = await self._execute_python(code, exec_timeout, work_dir, env, exec_id)
537
546
  else:
@@ -539,7 +548,11 @@ class ExecutionEngine:
539
548
 
540
549
  result.language = language
541
550
  result.code = code
542
- result.metadata = metadata
551
+ # [v1.16.19] 合并 metadata,不覆盖执行模式等内部字段
552
+ if result.metadata:
553
+ result.metadata.update(metadata)
554
+ else:
555
+ result.metadata = metadata
543
556
 
544
557
  if result.success:
545
558
  logger.info(f"[{exec_id}] 执行成功 (耗时: {result.execution_time:.2f}s)")
@@ -823,6 +836,65 @@ class ExecutionEngine:
823
836
  execution_time=elapsed,
824
837
  )
825
838
 
839
+ # ======================================================================
840
+ # [v1.16.19] 轻量级进程沙盒(Docker 不可用时的替代方案)
841
+ # ======================================================================
842
+ # 设计目标:
843
+ # - 使用 agent 自身的固定工作目录(不是临时目录)
844
+ # - 不使用全局执行锁,多 agent 可并发
845
+ # - 与本机模式的唯一区别:不走全局锁,允许多 agent 同时执行
846
+ # - 有 Docker 时用 Docker 容器提供真正隔离,无 Docker 时直接在
847
+ # agent 工作目录执行(各 agent 有自己的工作目录,天然隔离)
848
+ # ======================================================================
849
+
850
+ async def _execute_lightweight_sandbox(
851
+ self,
852
+ language: str,
853
+ code: str,
854
+ timeout: int,
855
+ work_dir: str,
856
+ env: Optional[Dict[str, str]],
857
+ exec_id: str,
858
+ ) -> ExecResult:
859
+ """直接在 agent 工作目录中执行代码(轻量级沙盒,无需 Docker)。
860
+
861
+ 与本机模式的区别:
862
+ - 不使用全局执行锁,多 agent 可并发执行
863
+ - 每个 agent 使用自己的固定工作目录,天然隔离
864
+ - 执行方式与本机模式完全相同(subprocess)
865
+ """
866
+ start_time = asyncio.get_event_loop().time()
867
+
868
+ try:
869
+ # 直接在 agent 工作目录中执行(不创建临时目录)
870
+ if language == "python":
871
+ result = await self._execute_python(code, timeout, work_dir, env, exec_id)
872
+ elif language in ("powershell",):
873
+ result = ExecResult(
874
+ success=False,
875
+ error="PowerShell 不支持沙盒模式",
876
+ execution_time=asyncio.get_event_loop().time() - start_time,
877
+ metadata={"mode": "sandbox"},
878
+ )
879
+ else:
880
+ result = await self._execute_shell(language, code, timeout, work_dir, env, exec_id)
881
+
882
+ # 标记为沙盒模式
883
+ if result.metadata is None:
884
+ result.metadata = {}
885
+ result.metadata["mode"] = "sandbox"
886
+
887
+ return result
888
+
889
+ except Exception as e:
890
+ elapsed = asyncio.get_event_loop().time() - start_time
891
+ return ExecResult(
892
+ success=False,
893
+ error=f"沙盒执行异常: {e}",
894
+ execution_time=elapsed,
895
+ metadata={"mode": "sandbox"},
896
+ )
897
+
826
898
  # ======================================================================
827
899
  # 沙盒执行 (Docker 容器)
828
900
  # ======================================================================
@@ -902,13 +974,12 @@ class ExecutionEngine:
902
974
  metadata={"mode": "sandbox"},
903
975
  )
904
976
  except FileNotFoundError:
905
- # Docker 不存在,回退到本地执行
906
- logger.warning(f"[{exec_id}] Docker 不可用,回退到本地执行")
907
- self.execution_mode = "local"
908
- if language == "python":
909
- return await self._execute_python(code, timeout, work_dir, env, exec_id)
910
- else:
911
- return await self._execute_shell(language, code, timeout, work_dir, env, exec_id)
977
+ # Docker 不存在,回退到轻量级沙盒
978
+ if not self._docker_warned:
979
+ logger.info(f"[{exec_id}] Docker 不可用,使用轻量级沙盒")
980
+ self._docker_warned = True
981
+ self._docker_available = False
982
+ return await self._execute_lightweight_sandbox(language, code, timeout, work_dir, env, exec_id)
912
983
  except Exception as e:
913
984
  elapsed = asyncio.get_event_loop().time() - start_time
914
985
  return ExecResult(
@@ -923,19 +994,27 @@ class ExecutionEngine:
923
994
  if mode not in ("local", "sandbox"):
924
995
  return False
925
996
  if mode == "sandbox":
926
- if not self._check_docker():
927
- logger.warning("沙盒模式: Docker 不可用")
928
- return False
929
- self._docker_available = True
997
+ self._docker_available = self._check_docker()
998
+ if not self._docker_available:
999
+ # [v1.16.19] Docker 不可用时,使用轻量级进程沙盒
1000
+ # 不再回退到本机模式,保持 sandbox 避免全局锁
1001
+ if not self._docker_warned:
1002
+ logger.info("沙盒模式: Docker 不可用,使用轻量级进程沙盒(临时工作目录隔离,无全局锁)")
1003
+ self._docker_warned = True
1004
+ # 不 return False,允许切换到 sandbox
1005
+ else:
1006
+ self._docker_available = True
930
1007
  self.execution_mode = mode
931
- logger.info(f"执行模式已切换: {mode}")
1008
+ logger.info(f"执行模式已切换: {mode}" + (" (Docker)" if mode == "sandbox" and self._docker_available else " (轻量级进程沙盒)" if mode == "sandbox" else ""))
932
1009
  return True
933
1010
 
934
1011
  def get_execution_info(self) -> Dict[str, Any]:
935
1012
  """获取当前执行模式信息。"""
1013
+ sandbox_type = "docker" if self._docker_available else ("lightweight" if self.execution_mode == "sandbox" else None)
936
1014
  return {
937
1015
  "mode": self.execution_mode,
938
1016
  "docker_available": self._docker_available,
1017
+ "sandbox_type": sandbox_type, # [v1.16.19] "docker" | "lightweight" | None
939
1018
  "sandbox_image": self.sandbox_image,
940
1019
  "sandbox_network": self.sandbox_network,
941
1020
  "sandbox_memory": self.sandbox_memory,
@@ -171,6 +171,27 @@ install_node() {
171
171
  exit 1
172
172
  }
173
173
 
174
+ # ── Linux 系统依赖 ──────────────────────────────────────────
175
+ install_linux_system_deps() {
176
+ if [ "$OS" != "linux" ] || ! command -v apt-get &>/dev/null; then return; fi
177
+ step "Installing system dependencies (poppler-utils, Chrome libs) ..."
178
+ sudo apt-get update -qq 2>/dev/null || true
179
+ # poppler-utils: PDF 文本提取 (pdftotext)
180
+ # Chrome/Chromium 共享库: 浏览器自动化必需
181
+ sudo apt-get install -y --no-install-recommends \
182
+ poppler-utils \
183
+ libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \
184
+ libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
185
+ libgbm1 libpango-1.0-0 libcairo2 libasound2 libatspi2.0-0 \
186
+ 2>/dev/null || true
187
+ # 尝试安装 Chromium (如果尚未安装)
188
+ if ! command -v google-chrome &>/dev/null && ! command -v chromium &>/dev/null && ! command -v chromium-browser &>/dev/null; then
189
+ sudo apt-get install -y --no-install-recommends chromium-browser 2>/dev/null || \
190
+ sudo apt-get install -y --no-install-recommends chromium 2>/dev/null || true
191
+ fi
192
+ success "系统依赖安装完成"
193
+ }
194
+
174
195
  # ── 找到 myagent-ai 命令路径 ──────────────────────────────
175
196
  find_myagent_cmd() {
176
197
  if command -v myagent-ai &>/dev/null; then
@@ -192,6 +213,7 @@ find_myagent_cmd() {
192
213
  if ! $NO_DEPS; then
193
214
  check_python || install_python
194
215
  check_node || install_node
216
+ install_linux_system_deps
195
217
  fi
196
218
 
197
219
  step "Installing $PKG_NAME via npm ..."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.16.17",
3
+ "version": "1.16.19",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/requirements.txt CHANGED
@@ -14,6 +14,19 @@ requests>=2.31.0
14
14
  beautifulsoup4>=4.12.0
15
15
  psutil>=5.9.0
16
16
 
17
+ # ============================================================
18
+ # 技能系统 - 文档处理 (PDF/Excel/Word/PPT)
19
+ # ============================================================
20
+ # PyPDF2: 纯 Python PDF 文本提取(无需系统依赖,保证开箱即用)
21
+ # 系统有 poppler-utils 时优先使用 pdftotext(更快更准),无时自动 fallback
22
+ PyPDF2>=3.0.0
23
+ # openpyxl: Excel (.xlsx) 文件读取(上传 Excel 自动提取内容)
24
+ openpyxl>=3.1.0
25
+ # python-docx: Word (.docx) 文件读取
26
+ python-docx>=1.1.0
27
+ # python-pptx: PowerPoint (.pptx) 文件读取
28
+ python-pptx>=0.6.21
29
+
17
30
  # ============================================================
18
31
  # 技能系统 - 浏览器自动化 (ChromeDev MCP)
19
32
  # ============================================================
@@ -48,6 +61,14 @@ discord.py>=2.3.0
48
61
  # ============================================================
49
62
  edge-tts>=6.1.0
50
63
 
64
+ # ============================================================
65
+ # 系统工具增强 (预装,避免运行时缺失)
66
+ # ============================================================
67
+ # chardet: 文件编码检测(上传文件时自动识别编码)
68
+ chardet>=5.0.0
69
+ # xlrd: 旧版 Excel (.xls) 文件读取
70
+ xlrd>=2.0.0
71
+
51
72
  # ============================================================
52
73
  # 语音识别 (本地 STT,默认启用)
53
74
  # ============================================================