2026-03-12 20:42:12 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""
|
|
|
|
|
|
会话管理 API
|
|
|
|
|
|
作者:小白 🐶
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-03-12 21:01:59 +08:00
|
|
|
|
import os
|
2026-03-12 20:56:13 +08:00
|
|
|
|
import json
|
2026-03-12 21:01:59 +08:00
|
|
|
|
import glob
|
|
|
|
|
|
from datetime import datetime
|
2026-03-12 20:56:13 +08:00
|
|
|
|
from flask import jsonify, request
|
2026-03-12 20:42:12 +08:00
|
|
|
|
from . import api
|
|
|
|
|
|
|
2026-03-12 21:01:59 +08:00
|
|
|
|
SESSIONS_DIR = '/root/.openclaw/agents/main/sessions'
|
|
|
|
|
|
|
2026-03-12 20:42:12 +08:00
|
|
|
|
@api.route('/sessions')
|
|
|
|
|
|
def get_sessions():
|
|
|
|
|
|
"""获取会话列表"""
|
2026-03-12 20:56:13 +08:00
|
|
|
|
try:
|
2026-03-12 21:01:59 +08:00
|
|
|
|
sessions = []
|
2026-03-12 20:56:13 +08:00
|
|
|
|
|
2026-03-12 21:01:59 +08:00
|
|
|
|
# 遍历会话目录
|
|
|
|
|
|
for filepath in glob.glob(os.path.join(SESSIONS_DIR, '*.jsonl')):
|
|
|
|
|
|
if filepath.endswith('.lock'):
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
session_id = os.path.basename(filepath).replace('.jsonl', '')
|
|
|
|
|
|
|
|
|
|
|
|
# 获取文件信息
|
|
|
|
|
|
stat = os.stat(filepath)
|
|
|
|
|
|
updated_at = stat.st_mtime
|
|
|
|
|
|
size = stat.st_size
|
|
|
|
|
|
|
|
|
|
|
|
# 读取最后几行获取会话信息
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(filepath, 'r') as f:
|
|
|
|
|
|
lines = f.readlines()
|
|
|
|
|
|
total_tokens = 0
|
|
|
|
|
|
channel = 'unknown'
|
|
|
|
|
|
|
|
|
|
|
|
# 解析最后一条消息获取信息
|
|
|
|
|
|
if lines:
|
|
|
|
|
|
try:
|
|
|
|
|
|
last_line = lines[-1].strip()
|
|
|
|
|
|
if last_line:
|
|
|
|
|
|
data = json.loads(last_line)
|
|
|
|
|
|
if 'usage' in data:
|
|
|
|
|
|
total_tokens = data['usage'].get('total_tokens', 0)
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
messages_count = len([l for l in lines if l.strip()])
|
|
|
|
|
|
except:
|
|
|
|
|
|
messages_count = 0
|
|
|
|
|
|
total_tokens = 0
|
|
|
|
|
|
|
|
|
|
|
|
# 判断是否活跃(5分钟内更新)
|
|
|
|
|
|
is_active = (datetime.now().timestamp() - updated_at) < 300
|
|
|
|
|
|
|
|
|
|
|
|
sessions.append({
|
|
|
|
|
|
'id': session_id,
|
|
|
|
|
|
'key': f'agent:main:session:{session_id}',
|
|
|
|
|
|
'updatedAt': int(updated_at * 1000),
|
|
|
|
|
|
'updatedTime': datetime.fromtimestamp(updated_at).strftime('%Y-%m-%d %H:%M:%S'),
|
|
|
|
|
|
'size': size,
|
|
|
|
|
|
'messagesCount': messages_count,
|
|
|
|
|
|
'totalTokens': total_tokens,
|
|
|
|
|
|
'status': 'active' if is_active else 'inactive'
|
2026-03-12 20:56:13 +08:00
|
|
|
|
})
|
2026-03-12 21:01:59 +08:00
|
|
|
|
|
|
|
|
|
|
# 按更新时间排序
|
|
|
|
|
|
sessions.sort(key=lambda x: x['updatedAt'], reverse=True)
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'data': {
|
|
|
|
|
|
'count': len(sessions),
|
|
|
|
|
|
'sessions': sessions
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-03-12 20:56:13 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'error': str(e)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-03-12 21:01:59 +08:00
|
|
|
|
@api.route('/sessions/<session_id>')
|
|
|
|
|
|
def get_session_detail(session_id):
|
2026-03-12 20:56:13 +08:00
|
|
|
|
"""获取会话详情"""
|
|
|
|
|
|
try:
|
2026-03-12 21:01:59 +08:00
|
|
|
|
filepath = os.path.join(SESSIONS_DIR, f'{session_id}.jsonl')
|
2026-03-12 20:56:13 +08:00
|
|
|
|
|
2026-03-12 21:01:59 +08:00
|
|
|
|
if not os.path.exists(filepath):
|
2026-03-12 20:56:13 +08:00
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': False,
|
2026-03-12 21:01:59 +08:00
|
|
|
|
'error': '会话不存在'
|
2026-03-12 20:56:13 +08:00
|
|
|
|
})
|
2026-03-12 21:01:59 +08:00
|
|
|
|
|
|
|
|
|
|
messages = []
|
|
|
|
|
|
|
|
|
|
|
|
with open(filepath, 'r') as f:
|
|
|
|
|
|
for line in f:
|
|
|
|
|
|
line = line.strip()
|
|
|
|
|
|
if not line:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = json.loads(line)
|
|
|
|
|
|
|
|
|
|
|
|
# 提取消息
|
|
|
|
|
|
if 'messages' in data:
|
|
|
|
|
|
for msg in data['messages']:
|
|
|
|
|
|
messages.append({
|
|
|
|
|
|
'role': msg.get('role', 'unknown'),
|
|
|
|
|
|
'content': msg.get('content', '')[:500], # 截断内容
|
|
|
|
|
|
'timestamp': data.get('created', 0)
|
|
|
|
|
|
})
|
|
|
|
|
|
elif 'content' in data:
|
|
|
|
|
|
messages.append({
|
|
|
|
|
|
'role': data.get('role', 'unknown'),
|
|
|
|
|
|
'content': data.get('content', '')[:500]
|
|
|
|
|
|
})
|
|
|
|
|
|
except:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
# 只保留最近 20 条消息
|
|
|
|
|
|
messages = messages[-20:]
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'data': {
|
|
|
|
|
|
'id': session_id,
|
|
|
|
|
|
'messagesCount': len(messages),
|
|
|
|
|
|
'messages': messages
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-03-12 20:56:13 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'error': str(e)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-03-12 21:01:59 +08:00
|
|
|
|
@api.route('/sessions/<session_id>/kill', methods=['POST'])
|
|
|
|
|
|
def kill_session(session_id):
|
|
|
|
|
|
"""终止会话(删除会话文件)"""
|
2026-03-12 20:56:13 +08:00
|
|
|
|
try:
|
2026-03-12 21:01:59 +08:00
|
|
|
|
filepath = os.path.join(SESSIONS_DIR, f'{session_id}.jsonl')
|
|
|
|
|
|
lockfile = filepath + '.lock'
|
|
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(filepath):
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'error': '会话不存在'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# 重命名为备份文件而不是直接删除
|
|
|
|
|
|
backup_path = filepath + f'.reset.{datetime.now().isoformat()}'
|
|
|
|
|
|
os.rename(filepath, backup_path)
|
|
|
|
|
|
|
|
|
|
|
|
# 删除锁文件
|
|
|
|
|
|
if os.path.exists(lockfile):
|
|
|
|
|
|
os.remove(lockfile)
|
2026-03-12 20:56:13 +08:00
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
2026-03-12 21:01:59 +08:00
|
|
|
|
'success': True,
|
|
|
|
|
|
'message': '会话已终止'
|
2026-03-12 20:56:13 +08:00
|
|
|
|
})
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'error': str(e)
|
|
|
|
|
|
})
|