myagent-ai 1.23.24 → 1.23.26
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/tool_dispatcher.py +9 -4
- package/package.json +1 -1
- package/web/api_server.py +51 -43
- package/web/ui/chat/chat_main.js +28 -2
- package/web/ui/chat/flow_engine.js +24 -5
package/core/tool_dispatcher.py
CHANGED
|
@@ -76,7 +76,9 @@ class ToolDispatcher:
|
|
|
76
76
|
if stream_callback is None:
|
|
77
77
|
return
|
|
78
78
|
import asyncio
|
|
79
|
-
|
|
79
|
+
# [v1.23.25] 用 "data" 键包裹 payload,与 file_send.py 的格式一致
|
|
80
|
+
# 前端 flow_engine.js 通过 evt.data 访问 payload
|
|
81
|
+
event = {"type": event_type, "data": data}
|
|
80
82
|
try:
|
|
81
83
|
if asyncio.iscoroutinefunction(stream_callback):
|
|
82
84
|
await stream_callback(event)
|
|
@@ -117,8 +119,13 @@ class ToolDispatcher:
|
|
|
117
119
|
# ── [v1.23.0] 已迁移为内部服务/CLI 子命令的工具 ──
|
|
118
120
|
elif tool_name == "recall_memory":
|
|
119
121
|
return {"success": False, "error": "'recall_memory' 已迁移,请通过 <recall> 标签或 CLI 调用: command {\"command\": \"myagent-ai memory --keyword xxx\"}"}
|
|
122
|
+
|
|
123
|
+
# ── [v1.23.25] 恢复 playaudio/playvideo/file_send 直接工具调用 ──
|
|
124
|
+
# 原因: CLI 迁移后 LLM 不稳定地使用 command 包裹,导致工具经常失败
|
|
120
125
|
elif tool_name in ("playaudio", "playvideo"):
|
|
121
|
-
return
|
|
126
|
+
return await self._exec_media(tool_name, params, task_id, stream_callback, sent_files)
|
|
127
|
+
elif tool_name == "file_send":
|
|
128
|
+
return await self._exec_file_send(params, task_id, stream_callback, sent_files)
|
|
122
129
|
|
|
123
130
|
# ── [v1.23.0] 已迁移为 CLI 子命令的工具 — 提示使用 command 调用 ──
|
|
124
131
|
elif tool_name in ("image_ocr", "ocr"):
|
|
@@ -127,8 +134,6 @@ class ToolDispatcher:
|
|
|
127
134
|
return {"success": False, "error": f"'{tool_name}' 已迁移为 CLI 命令,请使用: command {{\"command\": \"myagent-ai analyze-image <image_path>\"}}"}
|
|
128
135
|
elif tool_name in ("audio_transcribe", "transcribe"):
|
|
129
136
|
return {"success": False, "error": f"'{tool_name}' 已迁移为 CLI 命令,请使用: command {{\"command\": \"myagent-ai transcribe <audio_path>\"}}"}
|
|
130
|
-
elif tool_name == "file_send":
|
|
131
|
-
return {"success": False, "error": "'file_send' 已迁移为 CLI 命令,请使用: command {\"command\": \"myagent-ai send-file <path>\"}"}
|
|
132
137
|
|
|
133
138
|
# ── 兜底: SkillRegistry ──
|
|
134
139
|
if self.skills:
|
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -1033,10 +1033,14 @@ async function loadLocalNoVNC() {{
|
|
|
1033
1033
|
function stripJs(p) {{ return p.endsWith('.js') ? p.slice(0, -3) : p; }}
|
|
1034
1034
|
|
|
1035
1035
|
// 加载并注册单个模块
|
|
1036
|
+
// [v1.23.25] modId 使用完整路径(含 lib/ 前缀)保证 Babel 相对路径解析正确,
|
|
1037
|
+
// fetchPath 剥离 lib/ 前缀用于 URL 请求(basePath 已含 /vnc/lib/)
|
|
1036
1038
|
async function loadModule(filePath) {{
|
|
1037
|
-
const modId = stripJs(filePath);
|
|
1039
|
+
const modId = stripJs(filePath); // e.g. "lib/deflator" or "vendor/pako/lib/zlib/zstream"
|
|
1038
1040
|
if (_modules[modId]) return _modules[modId]; // 已加载
|
|
1039
|
-
|
|
1041
|
+
// strip leading "lib/" for fetch URL since basePath is already "/vnc/lib/"
|
|
1042
|
+
const fetchPath = filePath.startsWith('lib/') ? filePath.slice(4) : filePath;
|
|
1043
|
+
const resp = await fetch(basePath + fetchPath);
|
|
1040
1044
|
if (!resp.ok) throw new Error('Failed to fetch ' + filePath + ': ' + resp.status);
|
|
1041
1045
|
const code = await resp.text();
|
|
1042
1046
|
const modExports = {{}};
|
|
@@ -1054,6 +1058,10 @@ async function loadLocalNoVNC() {{
|
|
|
1054
1058
|
}}
|
|
1055
1059
|
|
|
1056
1060
|
// 按依赖顺序加载所有 noVNC 模块
|
|
1061
|
+
// [v1.23.25] 模块 ID 必须保留完整目录路径(lib/ 前缀),
|
|
1062
|
+
// 因为 Babel 编译的 require() 使用相对于原始 lib/ 目录的相对路径。
|
|
1063
|
+
// 例如 deflator.js 中 require("../lib/vendor/pako/...") 需要模块 ID 为 "lib/deflator"
|
|
1064
|
+
// 才能正确解析为 "vendor/pako/..." 而非 "lib/vendor/pako/..."。
|
|
1057
1065
|
const depFiles = [
|
|
1058
1066
|
// pako vendor (zlib 依赖)
|
|
1059
1067
|
'vendor/pako/lib/utils/common.js',
|
|
@@ -1067,53 +1075,53 @@ async function loadLocalNoVNC() {{
|
|
|
1067
1075
|
'vendor/pako/lib/zlib/deflate.js',
|
|
1068
1076
|
'vendor/pako/lib/zlib/gzheader.js',
|
|
1069
1077
|
'vendor/pako/lib/zlib/zstream.js',
|
|
1070
|
-
// noVNC core
|
|
1071
|
-
'util/logging.js',
|
|
1072
|
-
'util/events.js',
|
|
1073
|
-
'util/eventtarget.js',
|
|
1074
|
-
'util/strings.js',
|
|
1075
|
-
'util/browser.js',
|
|
1076
|
-
'util/int.js',
|
|
1077
|
-
'util/element.js',
|
|
1078
|
-
'util/cursor.js',
|
|
1079
|
-
'base64.js',
|
|
1080
|
-
'websock.js',
|
|
1081
|
-
'display.js',
|
|
1082
|
-
'inflator.js',
|
|
1083
|
-
'deflator.js',
|
|
1084
|
-
'decoders/copyrect.js',
|
|
1085
|
-
'decoders/raw.js',
|
|
1086
|
-
'decoders/rre.js',
|
|
1087
|
-
'decoders/hextile.js',
|
|
1088
|
-
'decoders/tight.js',
|
|
1089
|
-
'decoders/tightpng.js',
|
|
1090
|
-
'decoders/zrle.js',
|
|
1091
|
-
'decoders/jpeg.js',
|
|
1092
|
-
'encodings.js',
|
|
1093
|
-
'input/keysymdef.js',
|
|
1094
|
-
'input/keysym.js',
|
|
1095
|
-
'input/xtscancodes.js',
|
|
1096
|
-
'input/vkeys.js',
|
|
1097
|
-
'input/fixedkeys.js',
|
|
1098
|
-
'input/domkeytable.js',
|
|
1099
|
-
'input/util.js',
|
|
1100
|
-
'input/keyboard.js',
|
|
1101
|
-
'input/gesturehandler.js',
|
|
1078
|
+
// noVNC core — 使用 lib/ 前缀保持目录结构
|
|
1079
|
+
'lib/util/logging.js',
|
|
1080
|
+
'lib/util/events.js',
|
|
1081
|
+
'lib/util/eventtarget.js',
|
|
1082
|
+
'lib/util/strings.js',
|
|
1083
|
+
'lib/util/browser.js',
|
|
1084
|
+
'lib/util/int.js',
|
|
1085
|
+
'lib/util/element.js',
|
|
1086
|
+
'lib/util/cursor.js',
|
|
1087
|
+
'lib/base64.js',
|
|
1088
|
+
'lib/websock.js',
|
|
1089
|
+
'lib/display.js',
|
|
1090
|
+
'lib/inflator.js',
|
|
1091
|
+
'lib/deflator.js',
|
|
1092
|
+
'lib/decoders/copyrect.js',
|
|
1093
|
+
'lib/decoders/raw.js',
|
|
1094
|
+
'lib/decoders/rre.js',
|
|
1095
|
+
'lib/decoders/hextile.js',
|
|
1096
|
+
'lib/decoders/tight.js',
|
|
1097
|
+
'lib/decoders/tightpng.js',
|
|
1098
|
+
'lib/decoders/zrle.js',
|
|
1099
|
+
'lib/decoders/jpeg.js',
|
|
1100
|
+
'lib/encodings.js',
|
|
1101
|
+
'lib/input/keysymdef.js',
|
|
1102
|
+
'lib/input/keysym.js',
|
|
1103
|
+
'lib/input/xtscancodes.js',
|
|
1104
|
+
'lib/input/vkeys.js',
|
|
1105
|
+
'lib/input/fixedkeys.js',
|
|
1106
|
+
'lib/input/domkeytable.js',
|
|
1107
|
+
'lib/input/util.js',
|
|
1108
|
+
'lib/input/keyboard.js',
|
|
1109
|
+
'lib/input/gesturehandler.js',
|
|
1102
1110
|
// crypto (rfb.js → ra2.js → crypto/)
|
|
1103
|
-
'crypto/bigint.js',
|
|
1104
|
-
'crypto/dh.js',
|
|
1105
|
-
'crypto/md5.js',
|
|
1106
|
-
'crypto/des.js',
|
|
1107
|
-
'crypto/aes.js',
|
|
1108
|
-
'crypto/rsa.js',
|
|
1109
|
-
'crypto/crypto.js',
|
|
1110
|
-
'ra2.js',
|
|
1111
|
+
'lib/crypto/bigint.js',
|
|
1112
|
+
'lib/crypto/dh.js',
|
|
1113
|
+
'lib/crypto/md5.js',
|
|
1114
|
+
'lib/crypto/des.js',
|
|
1115
|
+
'lib/crypto/aes.js',
|
|
1116
|
+
'lib/crypto/rsa.js',
|
|
1117
|
+
'lib/crypto/crypto.js',
|
|
1118
|
+
'lib/ra2.js',
|
|
1111
1119
|
];
|
|
1112
1120
|
for (const file of depFiles) {{
|
|
1113
1121
|
await loadModule(file);
|
|
1114
1122
|
}}
|
|
1115
1123
|
// 最后加载 rfb.js
|
|
1116
|
-
const rfbExports = await loadModule('rfb.js');
|
|
1124
|
+
const rfbExports = await loadModule('lib/rfb.js');
|
|
1117
1125
|
return rfbExports.default || rfbExports;
|
|
1118
1126
|
}}
|
|
1119
1127
|
</script>
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -1943,11 +1943,37 @@ async function loadSessions() {
|
|
|
1943
1943
|
if (urlSession && state.sessions.some(s => s.id === urlSession)) {
|
|
1944
1944
|
// URL 指定了有效的 session ID,直接选中(刷新恢复)
|
|
1945
1945
|
targetSessionId = urlSession;
|
|
1946
|
-
} else if (
|
|
1946
|
+
} else if (urlSession) {
|
|
1947
|
+
// [v1.23.24] 前缀匹配 fallback:前端生成的临时 ID 与后端 canonical ID 可能不同
|
|
1948
|
+
// 例如前端 "default_web_20250101120000" 后端实际为 "default_web_20250101120000_a1b2c3"
|
|
1949
|
+
var prefixMatch = state.sessions.find(function(s) {
|
|
1950
|
+
return s.id.startsWith(urlSession) || urlSession.startsWith(s.id);
|
|
1951
|
+
});
|
|
1952
|
+
if (prefixMatch) {
|
|
1953
|
+
targetSessionId = prefixMatch.id;
|
|
1954
|
+
// 更新 URL 为 canonical ID,避免下次刷新再走 fallback
|
|
1955
|
+
try {
|
|
1956
|
+
var _fixUrl = new URL(window.location.href);
|
|
1957
|
+
_fixUrl.searchParams.set('session', prefixMatch.id);
|
|
1958
|
+
window.history.replaceState({}, '', _fixUrl.toString());
|
|
1959
|
+
} catch (_) {}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
if (!targetSessionId && state._pendingSessionRestore && state.sessions.some(s => s.id === state._pendingSessionRestore)) {
|
|
1947
1963
|
// 从 localStorage 恢复的 session(beforeunload 触发的保存)
|
|
1948
1964
|
targetSessionId = state._pendingSessionRestore;
|
|
1949
1965
|
state._pendingSessionRestore = null; // 清除,防止重复恢复
|
|
1950
|
-
} else if (!
|
|
1966
|
+
} else if (!targetSessionId && state._pendingSessionRestore) {
|
|
1967
|
+
// [v1.23.24] localStorage 恢复的 ID 也不匹配,尝试前缀匹配
|
|
1968
|
+
var lsPrefixMatch = state.sessions.find(function(s) {
|
|
1969
|
+
return s.id.startsWith(state._pendingSessionRestore) || state._pendingSessionRestore.startsWith(s.id);
|
|
1970
|
+
});
|
|
1971
|
+
if (lsPrefixMatch) {
|
|
1972
|
+
targetSessionId = lsPrefixMatch.id;
|
|
1973
|
+
}
|
|
1974
|
+
state._pendingSessionRestore = null;
|
|
1975
|
+
}
|
|
1976
|
+
if (!targetSessionId && !state.activeSessionId && state.sessions.length > 0) {
|
|
1951
1977
|
// 默认选中最新 session
|
|
1952
1978
|
targetSessionId = state.sessions[0].id;
|
|
1953
1979
|
}
|
|
@@ -1467,9 +1467,22 @@ async function sendMessage(opts) {
|
|
|
1467
1467
|
return;
|
|
1468
1468
|
}
|
|
1469
1469
|
|
|
1470
|
+
// [v1.23.24] 立即锁定发送状态,防止竞态导致重复发送
|
|
1471
|
+
// 原因:await checkLargeText() 会 yield 到事件循环,此时 isGenerating 仍为 false
|
|
1472
|
+
// 如果用户再次按 Enter 或点击发送按钮,guard 检查通过,导致重复发送
|
|
1473
|
+
state.isGenerating = true;
|
|
1474
|
+
document.getElementById('sendBtn').style.display = 'none';
|
|
1475
|
+
document.getElementById('stopBtn').style.display = '';
|
|
1476
|
+
|
|
1470
1477
|
// ── 大文本检测:超过阈值时弹出处理对话框 ──
|
|
1471
1478
|
var blocked = await checkLargeText(text);
|
|
1472
|
-
if (blocked)
|
|
1479
|
+
if (blocked) {
|
|
1480
|
+
// 用户取消了大文本发送,释放锁
|
|
1481
|
+
state.isGenerating = false;
|
|
1482
|
+
document.getElementById('sendBtn').style.display = '';
|
|
1483
|
+
document.getElementById('stopBtn').style.display = 'none';
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1473
1486
|
|
|
1474
1487
|
// Create session if needed (incorporate agent name)
|
|
1475
1488
|
let sessionId = state.activeSessionId;
|
|
@@ -1546,12 +1559,9 @@ async function sendMessage(opts) {
|
|
|
1546
1559
|
_userScrollLocked = false;
|
|
1547
1560
|
scrollToBottom(true);
|
|
1548
1561
|
|
|
1549
|
-
// Show typing
|
|
1550
|
-
state.isGenerating = true;
|
|
1562
|
+
// Show typing (isGenerating 和按钮切换已在函数顶部提前设置)
|
|
1551
1563
|
showTypingIndicator();
|
|
1552
1564
|
scrollToBottom(true);
|
|
1553
|
-
document.getElementById('sendBtn').style.display = 'none';
|
|
1554
|
-
document.getElementById('stopBtn').style.display = '';
|
|
1555
1565
|
|
|
1556
1566
|
// Start execution progress polling (in exec mode)
|
|
1557
1567
|
if (state.chatMode === 'exec') {
|
|
@@ -1640,6 +1650,15 @@ async function sendMessage(opts) {
|
|
|
1640
1650
|
sessionIdReceived = evt.session_id;
|
|
1641
1651
|
// Sync the actual session ID (backend may prefix with agent_path)
|
|
1642
1652
|
state.activeSessionId = evt.session_id;
|
|
1653
|
+
// [v1.23.24] 同步本地 sessions 列表中的 ID(前端生成的临时 ID → 后端 canonical ID)
|
|
1654
|
+
if (sessionIdOriginal && sessionIdOriginal !== evt.session_id) {
|
|
1655
|
+
var _sesIdx = state.sessions.findIndex(function(s) { return s.id === sessionIdOriginal; });
|
|
1656
|
+
if (_sesIdx >= 0) {
|
|
1657
|
+
state.sessions[_sesIdx].id = evt.session_id;
|
|
1658
|
+
state.agentSessions[state.activeAgent] = [...state.sessions];
|
|
1659
|
+
renderSessions();
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1643
1662
|
// ── 更新 URL 参数(后端返回的 session ID 可能与前端不同) ──
|
|
1644
1663
|
try {
|
|
1645
1664
|
const _url = new URL(window.location.href);
|