myagent-ai 1.19.4 → 1.19.6
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/core/context_builder.py +49 -2
- package/core/logger.py +29 -2
- package/core/utils.py +3 -1
- package/core/vnc_manager.py +0 -0
- package/data/uploads/2026-04/32c9e9d3-8cf_test.pdf.pdf +0 -0
- package/install.ps1 +11 -3
- package/main.py +1 -1
- package/package.json +1 -1
- package/requirements-optional.txt +0 -0
- package/requirements.txt +2 -0
- package/skills/__init__.py +0 -0
- package/skills/base.py +0 -0
- package/skills/browser_skill.py +0 -0
- package/skills/chromedev_mcp.py +0 -0
- package/skills/docx_skill.py +0 -0
- package/skills/file_send.py +0 -0
- package/skills/file_skill.py +0 -0
- package/skills/frontend-dev/SKILL.md +0 -0
- package/skills/frontend-dev/references/asset-prompt-guide.md +0 -0
- package/skills/frontend-dev/references/env-setup.md +0 -0
- package/skills/frontend-dev/references/minimax-cli-reference.md +0 -0
- package/skills/frontend-dev/references/minimax-image-guide.md +0 -0
- package/skills/frontend-dev/references/minimax-music-guide.md +0 -0
- package/skills/frontend-dev/references/minimax-tts-guide.md +0 -0
- package/skills/frontend-dev/references/minimax-video-guide.md +0 -0
- package/skills/frontend-dev/references/minimax-voice-catalog.md +0 -0
- package/skills/frontend-dev/references/motion-recipes.md +0 -0
- package/skills/frontend-dev/references/troubleshooting.md +0 -0
- package/skills/frontend-dev/scripts/minimax_image.py +0 -0
- package/skills/frontend-dev/scripts/minimax_music.py +0 -0
- package/skills/frontend-dev/scripts/minimax_tts.py +0 -0
- package/skills/frontend-dev/scripts/minimax_video.py +0 -0
- package/skills/frontend-dev/templates/generator_template.js +0 -0
- package/skills/frontend-dev/templates/viewer.html +0 -0
- package/skills/fullstack-dev/SKILL.md +0 -0
- package/skills/fullstack-dev/references/api-design.md +0 -0
- package/skills/fullstack-dev/references/auth-flow.md +0 -0
- package/skills/fullstack-dev/references/db-schema.md +0 -0
- package/skills/fullstack-dev/references/django-best-practices.md +0 -0
- package/skills/fullstack-dev/references/environment-management.md +0 -0
- package/skills/fullstack-dev/references/release-checklist.md +0 -0
- package/skills/fullstack-dev/references/technology-selection.md +0 -0
- package/skills/fullstack-dev/references/testing-strategy.md +0 -0
- package/skills/gui_skill.py +0 -0
- package/skills/pdf_skill.py +0 -0
- package/skills/ppt_skill.py +0 -0
- package/skills/registry.py +15 -2
- package/skills/search_skill.py +0 -0
- package/skills/system_skill.py +0 -0
- package/skills/xlsx_skill.py +0 -0
- package/web/ui/index.html +4 -4
package/core/context_builder.py
CHANGED
|
@@ -13,6 +13,7 @@ core/context_builder.py - 上下文构建器
|
|
|
13
13
|
<usersays> - 用户语音转文本输入(键盘输入时为空)
|
|
14
14
|
<task_plan> - 当前任务计划(Markdown 格式,上轮已完成时为空)
|
|
15
15
|
<tools> - 可用工具列表(名称、描述、参数格式)
|
|
16
|
+
<skill_prompts> - SKILL.md 格式技能的完整指令(Prompt 类型技能注入)
|
|
16
17
|
|
|
17
18
|
使用示例:
|
|
18
19
|
builder = ContextBuilder(memory_manager=mm, skill_registry=sr)
|
|
@@ -169,6 +170,7 @@ class ContextBuilder:
|
|
|
169
170
|
self._build_user_input(user_typed_text, user_voice_text),
|
|
170
171
|
self._build_task_plan(task_plan),
|
|
171
172
|
self._build_tools(self.skill_registry),
|
|
173
|
+
self._build_skill_prompts(self.skill_registry),
|
|
172
174
|
]
|
|
173
175
|
|
|
174
176
|
context_body = "\n".join(sections)
|
|
@@ -766,6 +768,51 @@ class ContextBuilder:
|
|
|
766
768
|
lines.append("</tools>")
|
|
767
769
|
return "\n".join(lines)
|
|
768
770
|
|
|
771
|
+
def _build_skill_prompts(
|
|
772
|
+
self,
|
|
773
|
+
skill_registry: Optional["SkillRegistry"],
|
|
774
|
+
) -> str:
|
|
775
|
+
"""
|
|
776
|
+
构建 <skill_prompts> 段落 —— SKILL.md 格式技能的完整指令内容。
|
|
777
|
+
|
|
778
|
+
对于 SKILL.md 目录格式的 Prompt 类型技能,将完整的 markdown body
|
|
779
|
+
注入到 LLM 上下文中,使 LLM 能按照技能指令执行任务。
|
|
780
|
+
普通 Python 技能(有 execute 实现)不在此处注入。
|
|
781
|
+
|
|
782
|
+
Returns:
|
|
783
|
+
<skill_prompts> XML 段落字符串,无 Prompt 技能时返回空字符串
|
|
784
|
+
"""
|
|
785
|
+
if not skill_registry:
|
|
786
|
+
return ""
|
|
787
|
+
|
|
788
|
+
# 收集所有 markdown 类型且未禁用的技能的 body
|
|
789
|
+
prompt_skills = []
|
|
790
|
+
try:
|
|
791
|
+
for skill in skill_registry._skills.values():
|
|
792
|
+
if skill_registry._is_disabled(skill.name):
|
|
793
|
+
continue
|
|
794
|
+
# 检查是否为 SKILL.md 格式技能
|
|
795
|
+
body = getattr(skill, 'get_body', None)
|
|
796
|
+
if body and callable(body):
|
|
797
|
+
content = body()
|
|
798
|
+
if content:
|
|
799
|
+
prompt_skills.append((skill.name, content))
|
|
800
|
+
except Exception as e:
|
|
801
|
+
logger.warning(f"获取 skill prompts 失败: {e}")
|
|
802
|
+
return ""
|
|
803
|
+
|
|
804
|
+
if not prompt_skills:
|
|
805
|
+
return ""
|
|
806
|
+
|
|
807
|
+
lines: List[str] = ["<skill_prompts>"]
|
|
808
|
+
for skill_name, content in prompt_skills:
|
|
809
|
+
safe_name = _xml_escape(skill_name)
|
|
810
|
+
lines.append(f"<{safe_name}>")
|
|
811
|
+
lines.append(content)
|
|
812
|
+
lines.append(f"</{safe_name}>")
|
|
813
|
+
lines.append("</skill_prompts>")
|
|
814
|
+
return "\n".join(lines)
|
|
815
|
+
|
|
769
816
|
# =========================================================================
|
|
770
817
|
# Token 预算管理
|
|
771
818
|
# =========================================================================
|
|
@@ -818,8 +865,8 @@ class ContextBuilder:
|
|
|
818
865
|
return re.sub(pattern, replacement, xml, count=1, flags=re.DOTALL)
|
|
819
866
|
|
|
820
867
|
# 按优先级从低到高裁剪(记忆最优先保留,知识库和任务计划优先裁剪)
|
|
821
|
-
# recent_summary
|
|
822
|
-
for tag in ['recent_summary', 'knowledge', 'task_plan', 'recall_memory', 'automemory']:
|
|
868
|
+
# recent_summary 是兜底机制,最先裁剪;skill_prompts 其次
|
|
869
|
+
for tag in ['recent_summary', 'skill_prompts', 'knowledge', 'task_plan', 'recall_memory', 'automemory']:
|
|
823
870
|
if estimated <= budget:
|
|
824
871
|
break
|
|
825
872
|
if f'<{tag}>' in context_xml:
|
package/core/logger.py
CHANGED
|
@@ -18,6 +18,8 @@ from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Optional, Dict, Any
|
|
20
20
|
from datetime import datetime
|
|
21
|
+
from zoneinfo import ZoneInfo
|
|
22
|
+
import time as _time
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
# ANSI 颜色码
|
|
@@ -31,8 +33,21 @@ COLORS = {
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
def _tz_converter(timestamp):
|
|
37
|
+
"""logging.Formatter.converter — 使用配置时区替代系统时区"""
|
|
38
|
+
try:
|
|
39
|
+
from config import ConfigManager
|
|
40
|
+
tz_name = ConfigManager().get("timezone", "Asia/Shanghai")
|
|
41
|
+
tz = ZoneInfo(tz_name)
|
|
42
|
+
except Exception:
|
|
43
|
+
tz = ZoneInfo("Asia/Shanghai")
|
|
44
|
+
dt = datetime.fromtimestamp(timestamp, tz=tz)
|
|
45
|
+
return dt.timetuple()
|
|
46
|
+
|
|
47
|
+
|
|
34
48
|
class ColorFormatter(logging.Formatter):
|
|
35
49
|
"""带颜色的控制台日志格式化器"""
|
|
50
|
+
converter = staticmethod(_tz_converter)
|
|
36
51
|
|
|
37
52
|
def format(self, record):
|
|
38
53
|
color = COLORS.get(record.levelname, COLORS["RESET"])
|
|
@@ -42,6 +57,7 @@ class ColorFormatter(logging.Formatter):
|
|
|
42
57
|
|
|
43
58
|
class FileFormatter(logging.Formatter):
|
|
44
59
|
"""文件日志格式化器(无颜色)"""
|
|
60
|
+
converter = staticmethod(_tz_converter)
|
|
45
61
|
|
|
46
62
|
def format(self, record):
|
|
47
63
|
# 去除 ANSI 颜色码
|
|
@@ -60,8 +76,14 @@ class JsonFormatter(logging.Formatter):
|
|
|
60
76
|
"""
|
|
61
77
|
|
|
62
78
|
def format(self, record):
|
|
79
|
+
try:
|
|
80
|
+
from config import ConfigManager
|
|
81
|
+
tz_name = ConfigManager().get("timezone", "Asia/Shanghai")
|
|
82
|
+
tz = ZoneInfo(tz_name)
|
|
83
|
+
except Exception:
|
|
84
|
+
tz = ZoneInfo("Asia/Shanghai")
|
|
63
85
|
log_entry: Dict[str, Any] = {
|
|
64
|
-
"timestamp": datetime.
|
|
86
|
+
"timestamp": datetime.now(tz).isoformat(),
|
|
65
87
|
"level": record.levelname,
|
|
66
88
|
"logger": record.name,
|
|
67
89
|
"message": record.getMessage(),
|
|
@@ -197,7 +219,12 @@ def setup_logger(
|
|
|
197
219
|
|
|
198
220
|
else:
|
|
199
221
|
# 不轮转,按日期命名
|
|
200
|
-
|
|
222
|
+
try:
|
|
223
|
+
from config import ConfigManager
|
|
224
|
+
_tz = ZoneInfo(ConfigManager().get("timezone", "Asia/Shanghai"))
|
|
225
|
+
except Exception:
|
|
226
|
+
_tz = ZoneInfo("Asia/Shanghai")
|
|
227
|
+
log_file = log_path / f"{name}_{datetime.now(_tz).strftime('%Y%m%d')}.log"
|
|
201
228
|
fh = logging.FileHandler(log_file, encoding="utf-8")
|
|
202
229
|
fh.setFormatter(
|
|
203
230
|
JsonFormatter() if json_format
|
package/core/utils.py
CHANGED
|
@@ -24,7 +24,9 @@ def get_config_tz() -> ZoneInfo:
|
|
|
24
24
|
return _tz_cache
|
|
25
25
|
try:
|
|
26
26
|
from config import ConfigManager
|
|
27
|
-
|
|
27
|
+
cfg = ConfigManager()
|
|
28
|
+
cfg.load()
|
|
29
|
+
tz_name = cfg.config.timezone
|
|
28
30
|
_tz_cache = ZoneInfo(tz_name)
|
|
29
31
|
except Exception:
|
|
30
32
|
_tz_cache = ZoneInfo("Asia/Shanghai")
|
package/core/vnc_manager.py
CHANGED
|
File without changes
|
|
File without changes
|
package/install.ps1
CHANGED
|
@@ -48,8 +48,6 @@ if (-not (Test-Path $VENV_DIR)) {
|
|
|
48
48
|
Write-Host "[✓] 虚拟环境已创建。" -ForegroundColor Green
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
$PIP = "$VENV_DIR\Scripts\python.exe -m pip"
|
|
52
|
-
|
|
53
51
|
# 3. 升级 pip
|
|
54
52
|
Write-Host "[*] 正在升级 pip..." -ForegroundColor Cyan
|
|
55
53
|
& $VENV_DIR\Scripts\python.exe -m pip install --upgrade pip --quiet
|
|
@@ -66,7 +64,17 @@ if ($LASTEXITCODE -ne 0) {
|
|
|
66
64
|
}
|
|
67
65
|
Write-Host "[✓] 核心依赖安装成功。" -ForegroundColor Green
|
|
68
66
|
|
|
69
|
-
# 5.
|
|
67
|
+
# 5. 安装 Windows 桌面依赖 (托盘图标、窗口管理等)
|
|
68
|
+
Write-Host "[*] 正在安装 Windows 桌面依赖..." -ForegroundColor Cyan
|
|
69
|
+
$winPkgs = @("pystray", "pygetwindow")
|
|
70
|
+
& $VENV_DIR\Scripts\python.exe -m pip install $winPkgs
|
|
71
|
+
if ($LASTEXITCODE -eq 0) {
|
|
72
|
+
Write-Host "[✓] Windows 桌面依赖安装成功。" -ForegroundColor Green
|
|
73
|
+
} else {
|
|
74
|
+
Write-Host "[!] Windows 桌面依赖安装失败(系统托盘可能不可用,但不影响核心功能)。" -ForegroundColor Yellow
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# 6. 安装可选依赖 (语音识别等,失败不影响核心功能)
|
|
70
78
|
if (Test-Path "requirements-optional.txt") {
|
|
71
79
|
Write-Host "`n[*] 正在安装可选依赖 (语音识别等)..." -ForegroundColor Cyan
|
|
72
80
|
Write-Host " (如果编译失败,不影响核心功能)" -ForegroundColor DarkGray
|
package/main.py
CHANGED
|
@@ -692,7 +692,7 @@ def create_tray_icon(app: MyAgentApp, web_port: int = 8767):
|
|
|
692
692
|
from PIL import Image, ImageDraw, ImageFont
|
|
693
693
|
except Exception:
|
|
694
694
|
# pystray 可能因缺少 GUI 环境 (X11/Wayland) 或未安装而失败
|
|
695
|
-
logger.info("pystray 不可用(未安装或缺少 GUI 环境),跳过系统托盘")
|
|
695
|
+
app.logger.info("pystray 不可用(未安装或缺少 GUI 环境),跳过系统托盘")
|
|
696
696
|
return None
|
|
697
697
|
|
|
698
698
|
# ── 图标颜色主题 ──
|
package/package.json
CHANGED
|
File without changes
|
package/requirements.txt
CHANGED
package/skills/__init__.py
CHANGED
|
File without changes
|
package/skills/base.py
CHANGED
|
File without changes
|
package/skills/browser_skill.py
CHANGED
|
File without changes
|
package/skills/chromedev_mcp.py
CHANGED
|
File without changes
|
package/skills/docx_skill.py
CHANGED
|
File without changes
|
package/skills/file_send.py
CHANGED
|
File without changes
|
package/skills/file_skill.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/skills/gui_skill.py
CHANGED
|
File without changes
|
package/skills/pdf_skill.py
CHANGED
|
File without changes
|
package/skills/ppt_skill.py
CHANGED
|
File without changes
|
package/skills/registry.py
CHANGED
|
@@ -215,6 +215,11 @@ def _load_skill_from_md(skill_dir: Path) -> Optional[Skill]:
|
|
|
215
215
|
for ref_file in sorted(ref_dir.glob("*.md")):
|
|
216
216
|
references.append(ref_file.name)
|
|
217
217
|
|
|
218
|
+
# 提取 SKILL.md body(YAML front matter 之后的内容)
|
|
219
|
+
body = ""
|
|
220
|
+
if len(parts) >= 3:
|
|
221
|
+
body = parts[2].strip()
|
|
222
|
+
|
|
218
223
|
class MarkdownSkill(Skill):
|
|
219
224
|
"""SKILL.md 格式的技能包装器"""
|
|
220
225
|
def __init__(self):
|
|
@@ -230,13 +235,21 @@ def _load_skill_from_md(skill_dir: Path) -> Optional[Skill]:
|
|
|
230
235
|
self._references = references
|
|
231
236
|
self._has_templates = (skill_dir / "templates").exists()
|
|
232
237
|
self._has_scripts = (skill_dir / "scripts").exists()
|
|
238
|
+
self._body = body
|
|
233
239
|
|
|
234
240
|
async def execute(self, **kwargs) -> SkillResult:
|
|
241
|
+
# SKILL.md 技能的指令已通过 <skill_prompts> 注入 system prompt
|
|
242
|
+
# 如果 LLM 仍然调用了此工具,返回确认信息
|
|
235
243
|
return SkillResult(
|
|
236
|
-
success=
|
|
237
|
-
|
|
244
|
+
success=True,
|
|
245
|
+
output=f"[{self.name}] 技能指令已激活,请按照 <skill_prompts> 中 {self.name} 的完整指令执行任务。",
|
|
246
|
+
message=f"技能 {self.name} 已激活",
|
|
238
247
|
)
|
|
239
248
|
|
|
249
|
+
def get_body(self) -> str:
|
|
250
|
+
"""返回 SKILL.md 的 body 内容"""
|
|
251
|
+
return self._body
|
|
252
|
+
|
|
240
253
|
def to_openclaw_format(self) -> Dict[str, Any]:
|
|
241
254
|
info = super().to_openclaw_format()
|
|
242
255
|
info["skill_type"] = "markdown"
|
package/skills/search_skill.py
CHANGED
|
File without changes
|
package/skills/system_skill.py
CHANGED
|
File without changes
|
package/skills/xlsx_skill.py
CHANGED
|
File without changes
|
package/web/ui/index.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="zh-CN">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
6
6
|
<title>MyAgent 管理后台</title>
|
|
7
7
|
<style>
|
|
8
8
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
--ok:#22c55e;--success:#22c55e;--warn:#f59e0b;--danger:#ef4444;--info:#3b82f6;
|
|
35
35
|
--border:#2e3347;--border-light:#232733;
|
|
36
36
|
}
|
|
37
|
-
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--bg);color:var(--text);height:100vh;display:flex}
|
|
37
|
+
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--bg);color:var(--text);height:100vh;height:100dvh;display:flex}
|
|
38
38
|
.sidebar{width:220px;background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
|
|
39
39
|
.sidebar .logo{padding:20px;font-size:18px;font-weight:700;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px}
|
|
40
40
|
.sidebar .logo span{color:var(--primary)}
|
|
@@ -178,7 +178,7 @@ tr:hover{background:var(--surface2)}
|
|
|
178
178
|
/* ── Mobile Responsive ── */
|
|
179
179
|
@media(max-width:768px){
|
|
180
180
|
/* [v1.18.10] 移动端侧边栏:收起时与内容同平面(flex行布局),展开时才浮层覆盖 */
|
|
181
|
-
body{flex-direction:row;height:100vh;overflow:hidden}
|
|
181
|
+
body{flex-direction:row;height:100vh;height:100dvh;overflow:hidden}
|
|
182
182
|
/* 默认收起状态:同一平面,56px窄栏,不浮动,不溢出 */
|
|
183
183
|
.sidebar{position:relative;left:auto;top:auto;height:auto;width:56px;z-index:auto;flex-shrink:0;transition:width .3s ease;overflow:hidden}
|
|
184
184
|
.sidebar .nav-item{justify-content:center;padding:10px 0;position:relative}
|
|
@@ -210,7 +210,7 @@ tr:hover{background:var(--surface2)}
|
|
|
210
210
|
.main{flex:1;min-width:0;min-height:0;overflow:hidden}
|
|
211
211
|
.header{padding:12px 16px;padding-left:12px}
|
|
212
212
|
.header h2{font-size:16px}
|
|
213
|
-
.content{padding:16px}
|
|
213
|
+
.content{padding:16px;padding-bottom:max(16px,env(safe-area-inset-bottom))}
|
|
214
214
|
.grid{grid-template-columns:1fr}
|
|
215
215
|
.form-row{grid-template-columns:1fr}
|
|
216
216
|
.table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
|