myagent-ai 1.13.5 → 1.13.7

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.
@@ -43,7 +43,7 @@ class MainAgent(BaseAgent):
43
43
  <output>
44
44
  <response>直接回复用户的内容。这是一段友好、自然的话语,用于向用户说明你正在做什么,或者回应用户的问题/问候。要求简洁、有礼貌、符合对话场景。如果用户只是问候,简单回应即可;如果用户有具体任务,要说明你的计划。</response>
45
45
  <usersays_correct>根据用户输入的"usersays"内容,结合上下文优化为新的用户输入,如果"usersays"为空,这里输出为空。</usersays_correct>
46
- <task_plan>如"context"包含非空"task_plan",则更新它,变为当前输出。否则,根据"context", 以MD 的格式,制定新任务列表。</task_plan>
46
+ <task_plan>任务计划(仅复杂任务使用):如"context"包含非空"task_plan",则更新它。否则,先评估任务复杂度——如果预计操作步骤不超过3步(如:单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务),则<task_plan>输出为空,不要创建任务列表;只有当任务较复杂(预计超过3步操作,如:多文件修改、需要调研+实现+测试、涉及多个模块联动等),才以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" "- [x] 已完成任务",含完成状态标记。</task_plan>
47
47
 
48
48
  <toolstocal>
49
49
  <tool><beforecalltext>连接词,介绍调用什么工具,达到什么目的。</beforecalltext><toolname>工具名</toolname><parms>JSON格式的参数对象,例如: {"query": "搜索关键词", "num": 5}</parms><timeout>预估超时时限(秒)</timeout><callback>true/false,要求解析器在该工具执行完后是否要回调llm大模型,将所有工具输出结果+新构造的"context"输入给llm</callback></tool>
@@ -63,7 +63,7 @@ class MainAgent(BaseAgent):
63
63
  1. 你必须且只能输出 <output> XML 结构,不要输出任何其他文本
64
64
  2. <response>: 必须输出一段直接回复用户的话语(这是用户实际看到的回复),要求简洁友好、自然流畅。不要只输出任务计划而不说话!
65
65
  3. <usersays_correct>: 如果 context 中 usersays 非空,则根据对话语境优化为更准确的用户意图表达
66
- 4. <task_plan>: 使用 Markdown 列表格式,每项包含任务描述和完成状态标记 [x]/[ ]
66
+ 4. <task_plan>: 仅用于复杂任务(预计超过3步操作)。简单任务(≤3步)输出为空。复杂任务使用 Markdown 列表格式,每项包含任务描述和完成状态标记 [x]/[ ]
67
67
  5. <toolstocal>: 列出所有需要执行的工具调用,每个工具包含完整的参数说明
68
68
  6. <parms>: **必须使用严格合法的JSON格式**,例如 {"query": "关键词", "num": 10},不要使用其他格式
69
69
  7. <timeout>: 预估超时秒数(简单操作10-30s,文件操作30-60s,网络请求60-120s,数据处理120-300s)
@@ -17,7 +17,6 @@ core/deps_checker.py - 自动依赖检测与安装
17
17
  托盘功能: pystray, PIL
18
18
  语音合成: edge_tts
19
19
  浏览器自动化: chrome-devtools-mcp (Node.js, Chrome DevTools Protocol)
20
- (备用) playwright (+ chromium 浏览器二进制)
21
20
  桌面GUI自动化: mss, pynput, pygetwindow
22
21
  """
23
22
  from __future__ import annotations
@@ -76,9 +75,9 @@ DEPENDENCIES: List[DepInfo] = [
76
75
  # ── 语音合成 ──
77
76
  DepInfo("edge_tts", "edge-tts", "6.1.0", "tts", "all"),
78
77
 
79
- # ── 浏览器自动化 (ChromeDev MCP + Playwright 备用) ──
80
- DepInfo("playwright", "playwright", "1.41.0", "browser", "all",
81
- note="备用方案。推荐使用 chrome-devtools-mcp (需 Node.js >= 20.19)"),
78
+ # ── 浏览器自动化 (ChromeDev MCP) ──
79
+ # Playwright 已移除,浏览器自动化统一使用 ChromeDevTools Protocol (MCP)
80
+ # 需要 Node.js >= 20.19,参见 skills/chromedev_mcp.py
82
81
 
83
82
  # ── 桌面 GUI 自动化 ──
84
83
  DepInfo("mss", "mss", "9.0.0", "gui", "all",
@@ -209,33 +208,6 @@ def _pip_install(pip_names: List[str], category: str = "") -> Tuple[bool, str]:
209
208
  return False, f"安装失败: {packages} - {last_error}"
210
209
 
211
210
 
212
- def _install_playwright_browsers() -> Tuple[bool, str]:
213
- """
214
- 安装 Playwright 浏览器二进制文件(Chromium)。
215
- 这是一个独立的步骤,因为 pip install playwright 只安装 Python 包,
216
- 浏览器二进制需要单独下载。
217
- """
218
- python = _get_python_executable()
219
- try:
220
- result = subprocess.run(
221
- [python, "-m", "playwright", "install", "chromium"],
222
- capture_output=True,
223
- timeout=300, # 5分钟超时(浏览器较大)
224
- )
225
- stdout = result.stdout.decode("utf-8", errors="replace") if result.stdout else ""
226
- stderr = result.stderr.decode("utf-8", errors="replace") if result.stderr else ""
227
- if result.returncode == 0:
228
- return True, "Chromium 浏览器已安装"
229
- # 检查是否已经安装
230
- if "Chromium" in stdout and "already" in stdout.lower():
231
- return True, "Chromium 浏览器已就绪"
232
- return False, f"Chromium 安装失败: {stderr[:200]}"
233
- except subprocess.TimeoutExpired:
234
- return False, "Chromium 安装超时(5分钟),请手动运行: playwright install chromium"
235
- except Exception as e:
236
- return False, f"Chromium 安装异常: {e}"
237
-
238
-
239
211
  def _check_version(import_name: str, min_version: str) -> bool:
240
212
  """检查模块版本是否满足最低要求"""
241
213
  if not min_version:
@@ -279,7 +251,7 @@ def check_and_install_deps(
279
251
  "failed": int, # 安装失败的数量
280
252
  "skipped_platform": int, # 因平台不匹配而跳过的数量
281
253
  "details": {...}, # 每个依赖的状态
282
- "playwright_browser": str, # Chromium 状态
254
+ "browser": str, # ChromeDev MCP 状态
283
255
  }
284
256
  """
285
257
  stats = {
@@ -290,7 +262,7 @@ def check_and_install_deps(
290
262
  "failed": 0,
291
263
  "skipped_platform": 0,
292
264
  "details": {},
293
- "playwright_browser": "not_checked",
265
+ "browser": "not_checked",
294
266
  }
295
267
 
296
268
  # 按分类收集缺失的依赖,批量安装以减少 pip 调用次数
@@ -367,36 +339,6 @@ def check_and_install_deps(
367
339
  if not silent:
368
340
  logger.warning(f" ✗ 安装失败: {dep.import_name} - {message}")
369
341
 
370
- # 第三遍:如果 playwright 安装成功,还需要安装 Chromium 浏览器二进制
371
- playwright_dep = next((d for d in DEPENDENCIES if d.import_name == "playwright"), None)
372
- if playwright_dep and stats["details"].get("playwright", {}).get("status") == "installed":
373
- if not silent:
374
- logger.info("正在安装 Chromium 浏览器二进制...")
375
- success, message = _install_playwright_browsers()
376
- stats["playwright_browser"] = "installed" if success else "failed"
377
- if not silent:
378
- if success:
379
- logger.info(f" ✓ {message}")
380
- else:
381
- logger.warning(f" ✗ {message}")
382
- elif playwright_dep and stats["details"].get("playwright", {}).get("status") == "available":
383
- # playwright 已安装,检查 chromium 是否已安装
384
- try:
385
- result = subprocess.run(
386
- [_get_python_executable(), "-m", "playwright", "install", "--dry-run", "chromium"],
387
- capture_output=True, timeout=10,
388
- )
389
- if result.returncode == 0:
390
- stats["playwright_browser"] = "ready"
391
- else:
392
- # 尝试安装
393
- if not silent:
394
- logger.info("正在安装 Chromium 浏览器二进制...")
395
- success, message = _install_playwright_browsers()
396
- stats["playwright_browser"] = "installed" if success else "failed"
397
- except Exception:
398
- stats["playwright_browser"] = "unknown"
399
-
400
342
  # 汇总日志
401
343
  if not silent and (total_installed > 0 or total_failed > 0):
402
344
  logger.info(
@@ -414,7 +356,7 @@ def ensure_skill_deps(skill_category: str) -> bool:
414
356
 
415
357
  Args:
416
358
  skill_category: 技能分类名称
417
- "browser" - 浏览器自动化 (chrome-devtools-mcp / playwright)
359
+ "browser" - 浏览器自动化 (chrome-devtools-mcp)
418
360
  "gui" - 桌面GUI自动化 (mss, pynput, pygetwindow)
419
361
  "search" - 搜索技能
420
362
  "tts" - 语音合成
package/main.py CHANGED
@@ -73,55 +73,7 @@ def _get_screen_resolution() -> tuple[int, int]:
73
73
 
74
74
 
75
75
  def _open_browser_kiosk(url: str):
76
- """打开浏览器窗口(无地址栏模式),回退到系统浏览器。
77
-
78
- 优先使用 Playwright 打开 app 模式的 Chromium 窗口(无地址栏),
79
- 如果 Playwright 不可用则回退到 webbrowser.open()。
80
- """
81
- try:
82
- import asyncio
83
- from playwright.async_api import async_playwright
84
-
85
- async def _launch():
86
- pw = await async_playwright().start()
87
- # 获取实际屏幕分辨率,确保窗口占满屏幕
88
- # --app 模式下 --start-maximized 不生效,需要手动设置窗口大小
89
- screen_width, screen_height = _get_screen_resolution()
90
- browser = await pw.chromium.launch(
91
- headless=False,
92
- args=[
93
- "--no-sandbox",
94
- f"--app={url}", # app 模式: 无地址栏、无标签页
95
- f"--window-size={screen_width},{screen_height}",
96
- "--window-position=0,0",
97
- ],
98
- )
99
- # 保持进程运行,浏览器关闭后退出
100
- page = await browser.new_page()
101
- await page.goto(url)
102
- # 监听页面关闭
103
- while True:
104
- try:
105
- await asyncio.sleep(2)
106
- if not browser.is_connected():
107
- break
108
- except Exception:
109
- break
110
- try:
111
- await pw.stop()
112
- except Exception:
113
- pass
114
-
115
- # 在后台线程中运行,避免阻塞主线程
116
- thread = threading.Thread(target=lambda: asyncio.run(_launch()), daemon=True)
117
- thread.start()
118
- return
119
- except ImportError:
120
- pass
121
- except Exception:
122
- pass
123
-
124
- # 回退到系统浏览器
76
+ """打开浏览器窗口,使用系统默认浏览器。"""
125
77
  import webbrowser
126
78
  webbrowser.open(url)
127
79
 
@@ -212,10 +164,6 @@ class MyAgentApp:
212
164
  self.logger.warning(
213
165
  f"{deps_result['failed']} 个依赖安装失败,相关功能可能不可用"
214
166
  )
215
- if deps_result["playwright_browser"] in ("installed", "ready"):
216
- self.logger.info("Chromium 浏览器已就绪")
217
- elif deps_result["playwright_browser"] == "failed":
218
- self.logger.warning("Chromium 浏览器安装失败,浏览器自动化技能可能不可用")
219
167
 
220
168
  # 2. LLM 客户端
221
169
  llm_cfg = self.config.llm
@@ -431,7 +379,7 @@ class MyAgentApp:
431
379
  ]:
432
380
  self.skill_registry.register(skill_cls())
433
381
 
434
- # 浏览器自动化技能 (Playwright)
382
+ # 浏览器自动化技能 (ChromeDev MCP)
435
383
  for skill_cls in [
436
384
  BrowserOpenSkill, BrowserClickSkill, BrowserFillSkill,
437
385
  BrowserScreenshotSkill, BrowserEvalSkill, BrowserNavigateSkill, BrowserCloseSkill,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.13.5",
3
+ "version": "1.13.7",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/requirements.txt CHANGED
@@ -17,9 +17,10 @@ lxml>=5.0.0
17
17
  psutil>=5.9.0
18
18
 
19
19
  # ============================================================
20
- # 技能系统 - 浏览器自动化 (Playwright)
20
+ # 技能系统 - 浏览器自动化 (ChromeDev MCP)
21
21
  # ============================================================
22
- playwright>=1.41.0
22
+ # 浏览器自动化使用 ChromeDevTools Protocol (MCP),无需 Playwright
23
+ # 需要 Node.js >= 20.19,参见 skills/chromedev_mcp.py
23
24
 
24
25
  # ============================================================
25
26
  # 技能系统 - 桌面 GUI 自动化 (可选,无头服务器不需要)
package/setup.py CHANGED
@@ -37,8 +37,7 @@ setup(
37
37
  "Pillow>=10.0.0",
38
38
  # 语音合成
39
39
  "edge-tts>=6.1.0",
40
- # 浏览器自动化 (内置技能)
41
- "playwright>=1.41.0",
40
+ # 浏览器自动化 (ChromeDev MCP, 无需 Playwright)
42
41
  # 桌面 GUI 自动化 (内置技能)
43
42
  "pynput>=1.7.6",
44
43
  "pygetwindow>=0.0.9",
package/web/api_server.py CHANGED
@@ -1035,8 +1035,8 @@ class ApiServer:
1035
1035
  "你当前处于【执行模式】(Execution Mode)。\n\n"
1036
1036
  "## 核心规则\n"
1037
1037
  "1. **任务列表(按复杂度决定)**:\n"
1038
- " - 如果用户的需求是简单任务(预计操作步骤不超过5步,如:单次查询、简单计算、问答题、格式转换、文件读取等),【不要】使用 ```tasklist```,直接用纯文本回复并执行即可。\n"
1039
- " - 只有当任务较复杂(预计需要超过5步操作,如:多文件修改、需要调研+实现+测试、涉及多个模块联动等),才使用 ```tasklist``` 代码块来跟踪进度。\n"
1038
+ " - 如果用户的需求是简单任务(预计操作步骤不超过3步,如:单次查询、简单计算、问答题、格式转换、文件读取等),【不要】使用 ```tasklist```,直接用纯文本回复并执行即可。\n"
1039
+ " - 只有当任务较复杂(预计需要超过3步操作,如:多文件修改、需要调研+实现+测试、涉及多个模块联动等),才使用 ```tasklist``` 代码块来跟踪进度。\n"
1040
1040
  " - 格式:```tasklist\\n[{\"text\": \"步骤描述\", \"status\": \"pending\"}]\\n```\n"
1041
1041
  " - status 可选值:pending(待执行)、running(进行中)、done(已完成)、blocked(受阻)\n"
1042
1042
  " - 首次收到复杂任务时,拆分为多个步骤,全部标记为 pending\n"
@@ -1052,7 +1052,7 @@ class ApiServer:
1052
1052
  store_key = session_id or agent_path
1053
1053
  tasks = self._task_list_store.get(store_key, [])
1054
1054
  if not tasks:
1055
- return base_instruction + "\n## 当前状态\n暂无任务计划。如果是简单任务(不超过5步),直接执行即可,无需创建任务列表。如果是复杂任务(超过5步),请先分析用户需求,拆分为具体步骤,然后用 ```tasklist``` 输出计划。"
1055
+ return base_instruction + "\n## 当前状态\n暂无任务计划。如果是简单任务(不超过3步),直接执行即可,无需创建任务列表。如果是复杂任务(超过3步),请先分析用户需求,拆分为具体步骤,然后用 ```tasklist``` 输出计划。"
1056
1056
 
1057
1057
  pending = [f" - ⏳ {t['text']}" for t in tasks if t.get("status") in ("pending", "running", "blocked")]
1058
1058
  done = [f" - ✅ {t['text']}" for t in tasks if t.get("status") == "done"]
@@ -3336,8 +3336,9 @@ class ApiServer:
3336
3336
  _v2_latest_task_plan = plan_text
3337
3337
  # 解析 Markdown 任务列表为 [{text, status}] 格式
3338
3338
  parsed_tasks = self._parse_v2_task_plan(plan_text)
3339
- if parsed_tasks:
3340
- # 合并与存储
3339
+ if parsed_tasks and len(parsed_tasks) >= 3:
3340
+ # 仅当任务项 >= 3 时才同步到 store 并推送前端
3341
+ # (小任务 <3 项视为简单任务,不创建任务列表)
3341
3342
  merged = self._merge_task_list(session_id, parsed_tasks)
3342
3343
  self._task_list_store[session_id] = merged
3343
3344
  # 额外发送 task_list_update 事件(前端侧边栏 task panel 依赖此事件)
@@ -3345,6 +3346,14 @@ class ApiServer:
3345
3346
  await _write_sse({"type": "task_list_update", "tasks": merged})
3346
3347
  except Exception:
3347
3348
  pass
3349
+ elif not parsed_tasks or len(parsed_tasks) == 0:
3350
+ # LLM 输出了空的 task_plan,清空任务列表
3351
+ if session_id in self._task_list_store:
3352
+ self._task_list_store[session_id] = []
3353
+ try:
3354
+ await _write_sse({"type": "task_list_update", "tasks": []})
3355
+ except Exception:
3356
+ pass
3348
3357
 
3349
3358
  # 转发所有事件到前端
3350
3359
  await _write_sse(event)
@@ -2429,7 +2429,11 @@ function _renderMessagesInner() {
2429
2429
  ? (content ? `<div class="message-bubble msg-bubble-wrapper">${content}${ttsIndicator}</div>` : '')
2430
2430
  : '';
2431
2431
 
2432
- // Exec events panel: only for backward compat (messages without parts loaded from DB)
2432
+ // ── Task Plan (historical view only hidden during streaming, shown after completion) ──
2433
+ var taskPlanHtml = '';
2434
+ if (!msg.streaming && msg._v2TaskPlan && msg._v2TaskPlan.trim()) {
2435
+ taskPlanHtml = '<div class="v2-task-plan" style="margin-bottom:8px"><div class="v2-task-plan-header" style="font-size:12px;font-weight:600;color:var(--text3);margin-bottom:4px">📋 任务计划</div><div class="v2-task-plan-body">' + renderMarkdown(msg._v2TaskPlan) + '</div></div>';
2436
+ }
2433
2437
  const execEventsHtml = (!isUser && !hasParts && msg.exec_events && msg.exec_events.length > 0)
2434
2438
  ? renderExecEvents(msg.exec_events, i) : '';
2435
2439
  html += `
@@ -2438,6 +2442,7 @@ function _renderMessagesInner() {
2438
2442
  <div class="message-content" style="flex:1;min-width:0">
2439
2443
  ${reasoningHtml}
2440
2444
  ${thoughtHtml}
2445
+ ${taskPlanHtml}
2441
2446
  ${timelineHtml}
2442
2447
  ${singleBubbleHtml}
2443
2448
  ${streamingIndicator}
@@ -671,17 +671,13 @@ function updateStreamingMessage(msgIdx) {
671
671
  indicator.remove();
672
672
  }
673
673
 
674
- // V2 Task Plan rendering
674
+ // V2 Task Plan: NOT rendered in message bubble during streaming
675
+ // (task plan is displayed in the dedicated collapsible task panel instead)
676
+ // Only store _v2TaskPlan for historical message display
675
677
  if (msg._v2TaskPlan) {
676
- let taskPlanEl = contentArea.querySelector('.v2-task-plan');
677
- if (!taskPlanEl) {
678
- taskPlanEl = document.createElement('div');
679
- taskPlanEl.className = 'v2-task-plan';
680
- // Insert before timeline or at appropriate position
681
- var insertTarget = contentArea.querySelector('.msg-timeline') || contentArea;
682
- insertTarget.insertBefore(taskPlanEl, insertTarget.firstChild);
683
- }
684
- taskPlanEl.innerHTML = '<div class="v2-task-plan-header">📋 任务计划</div><div class="v2-task-plan-body">' + renderMarkdown(msg._v2TaskPlan) + '</div>';
678
+ // Remove any existing task plan element from bubble (in case it was added before)
679
+ const existingPlan = contentArea.querySelector('.v2-task-plan');
680
+ if (existingPlan) existingPlan.remove();
685
681
  }
686
682
 
687
683
  // V2 Ask User rendering
@@ -1378,15 +1374,20 @@ async function sendMessage() {
1378
1374
  if (evt.tasks && Array.isArray(evt.tasks) && !_finishReceived) {
1379
1375
  state.taskItems = evt.tasks;
1380
1376
  renderTaskList();
1381
- // 显示任务面板头部(不自动展开内容区)
1382
1377
  var panel = document.getElementById('taskPanel');
1383
- if (panel && state.taskItems.length > 0) {
1384
- panel.classList.remove('hidden');
1385
- // Use change detection + auto-fade (but don't force expand)
1386
- if (typeof hasTaskListChanged === 'function' && typeof triggerTaskAutoFade === 'function') {
1387
- if (hasTaskListChanged()) {
1388
- triggerTaskAutoFade();
1378
+ if (panel) {
1379
+ if (state.taskItems.length > 0) {
1380
+ // 有任务时才显示面板
1381
+ panel.classList.remove('hidden');
1382
+ // Use change detection + auto-fade (but don't force expand)
1383
+ if (typeof hasTaskListChanged === 'function' && typeof triggerTaskAutoFade === 'function') {
1384
+ if (hasTaskListChanged()) {
1385
+ triggerTaskAutoFade();
1386
+ }
1389
1387
  }
1388
+ } else {
1389
+ // 空任务列表时隐藏面板
1390
+ panel.classList.add('hidden');
1390
1391
  }
1391
1392
  }
1392
1393
  }
@@ -1405,11 +1406,14 @@ async function sendMessage() {
1405
1406
  if (evt.data && evt.data.task_plan) {
1406
1407
  state.messages[msgIdx]._v2TaskPlan = evt.data.task_plan;
1407
1408
  }
1408
- // ── finish 标签:任务完成后清空任务面板 ──
1409
+ // ── finish 标签:任务完成后清空任务面板并隐藏 ──
1409
1410
  if (evt.data && evt.data.finish) {
1410
1411
  _finishReceived = true;
1411
1412
  state.taskItems = [];
1412
1413
  if (typeof renderTaskList === 'function') renderTaskList();
1414
+ // 隐藏任务面板
1415
+ var finishPanel = document.getElementById('taskPanel');
1416
+ if (finishPanel) finishPanel.classList.add('hidden');
1413
1417
  }
1414
1418
  throttledStreamUpdate(msgIdx);
1415
1419
  } else if (evt.type === 'v2_tool_start') {