myagent-ai 1.23.23 → 1.23.25
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/memory/manager.py +11 -5
- package/package.json +1 -1
- package/web/api_server.py +4 -4
- package/web/ui/admin/admin-sessions.js +49 -8
- package/web/ui/chat/chat_main.js +28 -2
- package/web/ui/chat/flow_engine.js +24 -5
package/memory/manager.py
CHANGED
|
@@ -318,12 +318,18 @@ class MemoryManager:
|
|
|
318
318
|
return entries
|
|
319
319
|
|
|
320
320
|
def get_conversation_all(self, session_id, limit=5000) -> List[MemoryEntry]:
|
|
321
|
-
"""获取全量对话历史(包含所有内部条目),用于完整回溯。"""
|
|
321
|
+
"""获取全量对话历史(包含所有内部条目),用于完整回溯。limit=0 表示无限制。"""
|
|
322
322
|
conn = self._get_conn()
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
if limit and limit > 0:
|
|
324
|
+
sql = """SELECT * FROM memories
|
|
325
|
+
WHERE session_id = ? AND category = 'session' AND role != ''
|
|
326
|
+
ORDER BY created_at ASC, rowid ASC LIMIT ?"""
|
|
327
|
+
rows = conn.execute(sql, (session_id, limit)).fetchall()
|
|
328
|
+
else:
|
|
329
|
+
sql = """SELECT * FROM memories
|
|
330
|
+
WHERE session_id = ? AND category = 'session' AND role != ''
|
|
331
|
+
ORDER BY created_at ASC, rowid ASC"""
|
|
332
|
+
rows = conn.execute(sql, (session_id,)).fetchall()
|
|
327
333
|
return [MemoryEntry.from_row(row) for row in rows]
|
|
328
334
|
|
|
329
335
|
def search_by_time_range(
|
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -4269,9 +4269,9 @@ window.addEventListener('beforeunload', function() {{
|
|
|
4269
4269
|
sid = request.match_info["sid"]
|
|
4270
4270
|
if not self.core.memory:
|
|
4271
4271
|
return web.json_response([])
|
|
4272
|
-
limit =
|
|
4272
|
+
limit = int(request.query.get("limit", 0))
|
|
4273
4273
|
offset = int(request.query.get("offset", 0))
|
|
4274
|
-
entries = self.core.memory.get_conversation_all(sid, limit=limit + offset)
|
|
4274
|
+
entries = self.core.memory.get_conversation_all(sid, limit=limit + offset if limit else 0)
|
|
4275
4275
|
entries = entries[offset:]
|
|
4276
4276
|
return web.json_response([{
|
|
4277
4277
|
"role": e.role, "content": e.content, "time": e.created_at,
|
|
@@ -4285,9 +4285,9 @@ window.addEventListener('beforeunload', function() {{
|
|
|
4285
4285
|
return web.json_response([])
|
|
4286
4286
|
if not self.core.memory:
|
|
4287
4287
|
return web.json_response([])
|
|
4288
|
-
limit =
|
|
4288
|
+
limit = int(request.query.get("limit", 0))
|
|
4289
4289
|
offset = int(request.query.get("offset", 0))
|
|
4290
|
-
entries = self.core.memory.get_conversation_all(sid, limit=limit + offset)
|
|
4290
|
+
entries = self.core.memory.get_conversation_all(sid, limit=limit + offset if limit else 0)
|
|
4291
4291
|
entries = entries[offset:]
|
|
4292
4292
|
return web.json_response([{
|
|
4293
4293
|
"role": e.role, "content": e.content, "time": e.created_at,
|
|
@@ -67,24 +67,28 @@ async function _loadSessionMessages(){
|
|
|
67
67
|
$('content').innerHTML=html;
|
|
68
68
|
}
|
|
69
69
|
// ========== Raw 原始消息查看 ==========
|
|
70
|
+
// [v1.23.23] 缓存全量消息供最大化使用
|
|
71
|
+
var _rawMsgsCache=[];
|
|
70
72
|
async function viewSessionRaw(sid){
|
|
71
73
|
window._viewSessionSid=sid;
|
|
72
74
|
_navSubState='raw:'+sid;
|
|
73
|
-
// 不用 navigateTo 因为需要先 fetch 数据再渲染,直接记录历史后继续
|
|
74
75
|
_navHistory.push({page:currentPage,sub:window._navSubState});
|
|
75
76
|
if(_navHistory.length>6)_navHistory.shift();
|
|
76
77
|
var hash='sessions'+'~raw:'+sid;
|
|
77
78
|
history.pushState({page:'sessions',sub:'raw:'+sid},'','#'+hash);
|
|
78
79
|
|
|
79
|
-
const msgs=await api(`/api/session/raw?sid=${encodeURIComponent(sid)}
|
|
80
|
+
const msgs=await api(`/api/session/raw?sid=${encodeURIComponent(sid)}`);
|
|
80
81
|
if(!Array.isArray(msgs)){showToast('加载失败','danger');return}
|
|
81
|
-
|
|
82
|
+
_rawMsgsCache=msgs;
|
|
83
|
+
_renderRawView(msgs,sid);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function _renderRawView(msgs,sid){
|
|
82
87
|
const keyLabelMap={'llm_output':'LLM 输出','llm_input':'LLM 输入','tool_call':'工具调用','tool_result':'工具结果','tool_result_raw':'工具原始数据','reasoning':'推理过程','conversation_insight':'会话洞察','':'对话','llm_request':'LLM 请求'};
|
|
83
88
|
let html=`<h3 style="margin-bottom:12px">Raw: ${escHtml(sid)} <span class="badge badge-blue">${msgs.length} 条</span></h3>`;
|
|
84
89
|
// 筛选按钮
|
|
85
90
|
html+=`<div style="margin-bottom:10px;display:flex;gap:6px;flex-wrap:wrap" id="rawFilterBar">`;
|
|
86
91
|
html+=`<button class="btn btn-sm" style="background:var(--accent);color:#fff" data-filter="all" onclick="rawFilter('all',this)">全部</button>`;
|
|
87
|
-
// 收集所有 key 类型
|
|
88
92
|
const keys=[...new Set(msgs.map(m=>m.key||''))];
|
|
89
93
|
for(const k of keys){
|
|
90
94
|
const label=keyLabelMap[k]||k||'对话';
|
|
@@ -120,20 +124,57 @@ async function viewSessionRaw(sid){
|
|
|
120
124
|
html+=`<span><span style="color:var(--text);font-weight:600">${escHtml(role)}</span>`;
|
|
121
125
|
if(keyLabel)html+=` <span class="badge badge-blue" style="font-size:10px">${escHtml(keyLabel)}</span>`;
|
|
122
126
|
html+=`</span>`;
|
|
123
|
-
html+=`<span>${escHtml(time)} <span style="margin-left:4px;opacity:0.5">${content.length}字符</span
|
|
127
|
+
html+=`<span>${escHtml(time)} <span style="margin-left:4px;opacity:0.5">${content.length}字符</span>`;
|
|
128
|
+
html+=` <span style="margin-left:8px;opacity:0.4;cursor:pointer" onclick="event.stopPropagation();rawMaximize(${i})" title="最大化查看">⤡</span>`;
|
|
129
|
+
html+=`</span>`;
|
|
124
130
|
html+=`</div>`;
|
|
125
131
|
html+=`<div id="${mid}" class="raw-body" style="padding:4px 10px 8px;font-size:12px;white-space:pre-wrap;word-break:break-all;color:var(--text2);max-height:400px;overflow-y:auto;transition:max-height 0.2s">`;
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
// [v1.23.23] 不截断,全量显示
|
|
133
|
+
html+=escHtml(content);
|
|
128
134
|
html+=`</div></div>`;
|
|
129
135
|
}
|
|
130
136
|
html+=`</div>`;
|
|
131
137
|
html+=`<div class="flex gap-8 mt-8"><button class="btn btn-ghost" onclick="goBack()">返回查看</button>`;
|
|
132
138
|
html+=`<button class="btn btn-ghost" onclick="goBack()">返回会话列表</button></div>`;
|
|
133
139
|
$('content').innerHTML=html;
|
|
134
|
-
// 生成时间索引
|
|
135
140
|
_buildTimeNav(msgs);
|
|
136
141
|
}
|
|
142
|
+
|
|
143
|
+
// [v1.23.23] 最大化查看单条 raw 消息(全屏覆盖,ESC 关闭)
|
|
144
|
+
function rawMaximize(idx){
|
|
145
|
+
if(!_rawMsgsCache||!_rawMsgsCache[idx])return;
|
|
146
|
+
const m=_rawMsgsCache[idx];
|
|
147
|
+
const content=m.content||'';
|
|
148
|
+
const role=m.role||'?';
|
|
149
|
+
const key=m.key||'';
|
|
150
|
+
const time=(m.time||'').slice(0,19);
|
|
151
|
+
const keyLabelMap={'llm_output':'LLM 输出','llm_input':'LLM 输入','tool_call':'工具调用','tool_result':'工具结果','tool_result_raw':'工具原始数据','reasoning':'推理过程','conversation_insight':'会话洞察','':'对话','llm_request':'LLM 请求'};
|
|
152
|
+
const keyLabel=keyLabelMap[key]||key||'对话';
|
|
153
|
+
const overlay=document.createElement('div');
|
|
154
|
+
overlay.id='rawMaxOverlay';
|
|
155
|
+
overlay.style.cssText='position:fixed;inset:0;z-index:300;background:rgba(0,0,0,.75);display:flex;flex-direction:column;padding:12px';
|
|
156
|
+
overlay.innerHTML=`<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 16px;background:var(--surface);border:1px solid var(--border);border-radius:8px 8px 0 0;flex-shrink:0">
|
|
157
|
+
<div><span style="font-weight:700;color:var(--text)">${escHtml(role)}</span> <span class="badge badge-blue" style="font-size:10px">${escHtml(keyLabel)}</span> <span style="font-size:12px;color:var(--text3);margin-left:8px">${escHtml(time)}</span> <span style="font-size:11px;color:var(--text3);margin-left:4px">${content.length} 字符</span></div>
|
|
158
|
+
<div style="display:flex;gap:8px">
|
|
159
|
+
<button class="btn btn-sm btn-ghost" onclick="rawMaxCopy(${idx})">复制</button>
|
|
160
|
+
<button class="btn btn-sm btn-ghost" onclick="rawMaxClose()">关闭 (ESC)</button>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
<pre id="rawMaxContent" style="flex:1;overflow:auto;background:var(--bg);border:1px solid var(--border);border-top:none;border-radius:0 0 8px 8px;padding:16px;font-size:13px;line-height:1.6;white-space:pre-wrap;word-break:break-all;color:var(--text);margin:0;font-family:'Cascadia Code',Consolas,monospace">${escHtml(content)}</pre>`;
|
|
164
|
+
document.body.appendChild(overlay);
|
|
165
|
+
overlay.addEventListener('keydown',function(e){if(e.key==='Escape')rawMaxClose()});
|
|
166
|
+
overlay.focus();
|
|
167
|
+
}
|
|
168
|
+
// 全局 ESC 监听(overlay 可能没获得焦点)
|
|
169
|
+
document.addEventListener('keydown',function(e){if(e.key==='Escape'&&document.getElementById('rawMaxOverlay'))rawMaxClose()});
|
|
170
|
+
function rawMaxClose(){
|
|
171
|
+
var el=document.getElementById('rawMaxOverlay');
|
|
172
|
+
if(el)el.remove();
|
|
173
|
+
}
|
|
174
|
+
function rawMaxCopy(idx){
|
|
175
|
+
if(!_rawMsgsCache||!_rawMsgsCache[idx])return;
|
|
176
|
+
navigator.clipboard.writeText(_rawMsgsCache[idx].content||'').then(()=>showToast('已复制','success')).catch(()=>showToast('复制失败','danger'));
|
|
177
|
+
}
|
|
137
178
|
function rawFilter(key,btn){
|
|
138
179
|
const items=document.querySelectorAll('.raw-item');
|
|
139
180
|
for(const item of items){
|
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);
|