feedback-mcp 1.0.64__py3-none-any.whl
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.
Potentially problematic release.
This version of feedback-mcp might be problematic. Click here for more details.
- add_command_dialog.py +712 -0
- command.py +636 -0
- components/__init__.py +15 -0
- components/agent_popup.py +187 -0
- components/chat_history.py +281 -0
- components/command_popup.py +399 -0
- components/feedback_text_edit.py +1125 -0
- components/file_popup.py +417 -0
- components/history_popup.py +582 -0
- components/markdown_display.py +262 -0
- context_formatter.py +301 -0
- debug_logger.py +107 -0
- feedback_config.py +144 -0
- feedback_mcp-1.0.64.dist-info/METADATA +327 -0
- feedback_mcp-1.0.64.dist-info/RECORD +41 -0
- feedback_mcp-1.0.64.dist-info/WHEEL +5 -0
- feedback_mcp-1.0.64.dist-info/entry_points.txt +2 -0
- feedback_mcp-1.0.64.dist-info/top_level.txt +20 -0
- feedback_ui.py +1680 -0
- get_session_id.py +53 -0
- git_operations.py +579 -0
- ide_utils.py +313 -0
- path_config.py +89 -0
- post_task_hook.py +78 -0
- record.py +188 -0
- server.py +746 -0
- session_manager.py +368 -0
- stop_hook.py +87 -0
- tabs/__init__.py +87 -0
- tabs/base_tab.py +34 -0
- tabs/chat_history_style.qss +66 -0
- tabs/chat_history_tab.py +1000 -0
- tabs/chat_tab.py +1931 -0
- tabs/workspace_tab.py +502 -0
- ui/__init__.py +20 -0
- ui/__main__.py +16 -0
- ui/compact_feedback_ui.py +376 -0
- ui/session_list_ui.py +793 -0
- ui/styles/session_list.qss +158 -0
- window_position_manager.py +197 -0
- workspace_manager.py +253 -0
ide_utils.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IDE 操作工具模块
|
|
3
|
+
提供打开各种IDE的通用功能
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import platform
|
|
8
|
+
import time
|
|
9
|
+
import shutil
|
|
10
|
+
from typing import Optional, Dict, Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def open_project_with_ide(project_path: str, ide_name: str = None) -> bool:
|
|
14
|
+
"""
|
|
15
|
+
使用指定IDE打开项目
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
project_path: 项目路径
|
|
19
|
+
ide_name: IDE名称(cursor/kiro/vscode),如果为None则使用系统默认
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
bool: 操作是否成功
|
|
23
|
+
"""
|
|
24
|
+
if not project_path or not os.path.exists(project_path):
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
if not ide_name:
|
|
28
|
+
return False # 不设置默认IDE,必须明确指定
|
|
29
|
+
|
|
30
|
+
# 获取IDE命令
|
|
31
|
+
ide_command = get_ide_command(ide_name)
|
|
32
|
+
if not ide_command:
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
# 检查IDE是否可用
|
|
36
|
+
if not is_ide_available(ide_name):
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
system = platform.system()
|
|
41
|
+
|
|
42
|
+
if system == 'Darwin': # macOS
|
|
43
|
+
return _open_ide_macos(project_path, ide_name, ide_command)
|
|
44
|
+
elif system == 'Windows': # Windows
|
|
45
|
+
return _open_ide_windows(project_path, ide_name, ide_command)
|
|
46
|
+
else: # Linux
|
|
47
|
+
return _open_ide_linux(project_path, ide_name, ide_command)
|
|
48
|
+
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print(f"打开IDE异常: {e}")
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_ide_command(ide_name: str) -> str:
|
|
55
|
+
"""
|
|
56
|
+
获取IDE的启动命令
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
ide_name: IDE名称
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
str: IDE启动命令
|
|
63
|
+
"""
|
|
64
|
+
# 支持预设的IDE映射
|
|
65
|
+
ide_commands = {
|
|
66
|
+
"cursor": "cursor",
|
|
67
|
+
"kiro": "kiro",
|
|
68
|
+
"vscode": "code"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# 如果在预设列表中,使用映射的命令
|
|
72
|
+
if ide_name.lower() in ide_commands:
|
|
73
|
+
return ide_commands[ide_name.lower()]
|
|
74
|
+
|
|
75
|
+
# 否则直接使用IDE名称作为命令(支持动态IDE)
|
|
76
|
+
return ide_name.lower()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_ide_available(ide_name: str) -> bool:
|
|
80
|
+
"""
|
|
81
|
+
检查指定IDE是否可用
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
ide_name: IDE名称
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
bool: IDE是否可用
|
|
88
|
+
"""
|
|
89
|
+
import platform
|
|
90
|
+
import os
|
|
91
|
+
|
|
92
|
+
command = get_ide_command(ide_name)
|
|
93
|
+
|
|
94
|
+
# 首先检查命令行工具
|
|
95
|
+
if shutil.which(command) is not None:
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
# 针对不同操作系统的特殊处理
|
|
99
|
+
system = platform.system()
|
|
100
|
+
|
|
101
|
+
if system == 'Darwin': # macOS
|
|
102
|
+
app_map = {
|
|
103
|
+
"cursor": "Cursor",
|
|
104
|
+
"kiro": "Kiro",
|
|
105
|
+
"code": "Visual Studio Code",
|
|
106
|
+
"vscode": "Visual Studio Code" # 修复VS Code检测
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# 使用command而不是ide_name进行查找(修复bug)
|
|
110
|
+
if command in app_map:
|
|
111
|
+
app_name = app_map[command]
|
|
112
|
+
else:
|
|
113
|
+
# 动态IDE支持:尝试使用IDE名称的首字母大写形式
|
|
114
|
+
app_name = ide_name.capitalize() if ide_name.islower() else ide_name
|
|
115
|
+
|
|
116
|
+
# 检查应用程序包是否存在
|
|
117
|
+
app_paths = [
|
|
118
|
+
f"/Applications/{app_name}.app",
|
|
119
|
+
f"/Users/{os.environ.get('USER', 'user')}/Applications/{app_name}.app"
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
for app_path in app_paths:
|
|
123
|
+
if os.path.exists(app_path):
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
elif system == 'Windows': # Windows
|
|
127
|
+
app_map = {
|
|
128
|
+
"cursor": "Cursor",
|
|
129
|
+
"kiro": "Kiro",
|
|
130
|
+
"code": "Code",
|
|
131
|
+
"vscode": "Code" # 修复VS Code检测
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# 使用command而不是ide_name进行查找(修复bug)
|
|
135
|
+
if command in app_map:
|
|
136
|
+
app_name = app_map[command]
|
|
137
|
+
else:
|
|
138
|
+
# 动态IDE支持:尝试使用IDE名称的首字母大写形式
|
|
139
|
+
app_name = ide_name.capitalize() if ide_name.islower() else ide_name
|
|
140
|
+
|
|
141
|
+
# 检查常见的Windows安装路径
|
|
142
|
+
common_paths = [
|
|
143
|
+
f"C:\\Users\\{os.environ.get('USERNAME', 'user')}\\AppData\\Local\\Programs\\{app_name}\\{app_name}.exe",
|
|
144
|
+
f"C:\\Program Files\\{app_name}\\{app_name}.exe",
|
|
145
|
+
f"C:\\Program Files (x86)\\{app_name}\\{app_name}.exe"
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
for path in common_paths:
|
|
149
|
+
if os.path.exists(path):
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
elif system == 'Linux': # Linux
|
|
153
|
+
app_map = {
|
|
154
|
+
"cursor": "cursor",
|
|
155
|
+
"kiro": "kiro",
|
|
156
|
+
"code": "code",
|
|
157
|
+
"vscode": "code" # 修复VS Code检测
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# 使用command而不是ide_name进行查找(修复bug)
|
|
161
|
+
if command in app_map:
|
|
162
|
+
app_name = app_map[command]
|
|
163
|
+
else:
|
|
164
|
+
# 动态IDE支持:直接使用命令名
|
|
165
|
+
app_name = command
|
|
166
|
+
|
|
167
|
+
# 检查常见的Linux安装路径
|
|
168
|
+
common_paths = [
|
|
169
|
+
f"/opt/{app_name}/{app_name}",
|
|
170
|
+
f"/usr/local/{app_name}/{app_name}",
|
|
171
|
+
f"/usr/bin/{app_name}",
|
|
172
|
+
f"/snap/bin/{app_name}"
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
for path in common_paths:
|
|
176
|
+
if os.path.exists(path):
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _open_ide_macos(project_path: str, ide_name: str, ide_command: str) -> bool:
|
|
183
|
+
"""macOS平台打开IDE"""
|
|
184
|
+
try:
|
|
185
|
+
app_map = {
|
|
186
|
+
"cursor": "Cursor",
|
|
187
|
+
"kiro": "Kiro",
|
|
188
|
+
"vscode": "Visual Studio Code",
|
|
189
|
+
"code": "Visual Studio Code" # 支持code命令映射
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# 尝试根据命令名获取应用名
|
|
193
|
+
app_name = app_map.get(ide_command) or app_map.get(ide_name)
|
|
194
|
+
|
|
195
|
+
if not app_name:
|
|
196
|
+
# 动态IDE支持:尝试使用IDE名称的首字母大写形式作为应用名
|
|
197
|
+
app_name = ide_name.capitalize() if ide_name.islower() else ide_name
|
|
198
|
+
|
|
199
|
+
# 优先使用open命令打开应用
|
|
200
|
+
result = subprocess.run(['open', '-a', app_name, project_path],
|
|
201
|
+
capture_output=True, text=True, timeout=5)
|
|
202
|
+
if result.returncode == 0:
|
|
203
|
+
# 确保应用在前台
|
|
204
|
+
subprocess.run(['osascript', '-e', f'tell application "{app_name}" to activate'],
|
|
205
|
+
capture_output=True, text=True, timeout=3)
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
# 如果open命令失败,尝试命令行方式
|
|
209
|
+
if shutil.which(ide_command) is not None:
|
|
210
|
+
result = subprocess.run([ide_command, project_path],
|
|
211
|
+
capture_output=True, text=True, timeout=5)
|
|
212
|
+
return result.returncode == 0
|
|
213
|
+
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
print(f"macOS下打开{ide_name}失败: {e}")
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _open_ide_windows(project_path: str, ide_name: str, ide_command: str) -> bool:
|
|
222
|
+
"""Windows平台打开IDE"""
|
|
223
|
+
try:
|
|
224
|
+
# Windows下直接调用命令
|
|
225
|
+
result = subprocess.run([ide_command, project_path],
|
|
226
|
+
capture_output=True, text=True, timeout=5)
|
|
227
|
+
return result.returncode == 0
|
|
228
|
+
|
|
229
|
+
except Exception as e:
|
|
230
|
+
print(f"Windows下打开{ide_name}失败: {e}")
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _open_ide_linux(project_path: str, ide_name: str, ide_command: str) -> bool:
|
|
235
|
+
"""Linux平台打开IDE"""
|
|
236
|
+
try:
|
|
237
|
+
result = subprocess.run([ide_command, project_path],
|
|
238
|
+
capture_output=True, text=True, timeout=5)
|
|
239
|
+
return result.returncode == 0
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
print(f"Linux下打开{ide_name}失败: {e}")
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# 以下是为了向后兼容而保留的函数
|
|
247
|
+
def focus_cursor_to_project(project_path: str) -> bool:
|
|
248
|
+
"""
|
|
249
|
+
向后兼容:macOS平台下聚焦Cursor到指定项目路径
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
project_path: 项目路径
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
bool: 操作是否成功
|
|
256
|
+
"""
|
|
257
|
+
return open_project_with_ide(project_path, "cursor")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def is_cursor_available() -> bool:
|
|
261
|
+
"""
|
|
262
|
+
向后兼容:检查系统是否安装了Cursor
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
bool: Cursor是否可用
|
|
266
|
+
"""
|
|
267
|
+
return is_ide_available("cursor")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def get_ide_info(ide_name: str) -> Dict[str, Any]:
|
|
271
|
+
"""
|
|
272
|
+
获取IDE的详细信息
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
ide_name: IDE名称
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
dict: IDE详细信息
|
|
279
|
+
"""
|
|
280
|
+
ide_info_map = {
|
|
281
|
+
"cursor": {
|
|
282
|
+
"name": "Cursor",
|
|
283
|
+
"command": "cursor",
|
|
284
|
+
"description": "AI驱动的代码编辑器",
|
|
285
|
+
"platforms": ["Darwin", "Windows", "Linux"]
|
|
286
|
+
},
|
|
287
|
+
"kiro": {
|
|
288
|
+
"name": "Kiro",
|
|
289
|
+
"command": "kiro",
|
|
290
|
+
"description": "现代化的代码编辑器",
|
|
291
|
+
"platforms": ["Darwin", "Windows", "Linux"]
|
|
292
|
+
},
|
|
293
|
+
"vscode": {
|
|
294
|
+
"name": "VS Code",
|
|
295
|
+
"command": "code",
|
|
296
|
+
"description": "微软的轻量级代码编辑器",
|
|
297
|
+
"platforms": ["Darwin", "Windows", "Linux"]
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
info = ide_info_map.get(ide_name, {})
|
|
302
|
+
info["available"] = is_ide_available(ide_name)
|
|
303
|
+
return info
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def is_macos() -> bool:
|
|
307
|
+
"""
|
|
308
|
+
向后兼容:检查是否为macOS系统
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
bool: 是否为macOS
|
|
312
|
+
"""
|
|
313
|
+
return platform.system() == 'Darwin'
|
path_config.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
路径配置模块 - 统一管理应用程序各种文件路径
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
# 导入调试日志模块
|
|
8
|
+
try:
|
|
9
|
+
from debug_logger import get_debug_logger
|
|
10
|
+
DEBUG_LOG_AVAILABLE = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
DEBUG_LOG_AVAILABLE = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PathConfig:
|
|
16
|
+
"""路径配置管理类"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, project_path: Optional[str] = None):
|
|
19
|
+
# 获取脚本文件所在目录
|
|
20
|
+
self.script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
21
|
+
# 设置项目路径
|
|
22
|
+
self.project_path = project_path
|
|
23
|
+
|
|
24
|
+
# 记录路径配置初始化
|
|
25
|
+
if DEBUG_LOG_AVAILABLE:
|
|
26
|
+
logger = get_debug_logger()
|
|
27
|
+
logger.log_path_config(self.script_dir, "INIT")
|
|
28
|
+
|
|
29
|
+
# 已移除个人指令目录,只保留项目指令
|
|
30
|
+
|
|
31
|
+
def get_plugins_config_path(self) -> str:
|
|
32
|
+
"""获取插件配置文件路径(支持 Windows/macOS/Linux)"""
|
|
33
|
+
claude_dir = os.path.expanduser("~/.claude")
|
|
34
|
+
return os.path.join(claude_dir, "plugins", "installed_plugins.json")
|
|
35
|
+
|
|
36
|
+
def get_settings_path(self) -> str:
|
|
37
|
+
"""获取 Claude 设置文件路径(支持 Windows/macOS/Linux)"""
|
|
38
|
+
claude_dir = os.path.expanduser("~/.claude")
|
|
39
|
+
return os.path.join(claude_dir, "settings.json")
|
|
40
|
+
|
|
41
|
+
def get_project_commands_dir(self, project_path: Optional[str] = None) -> Optional[str]:
|
|
42
|
+
"""获取项目指令目录路径"""
|
|
43
|
+
target_project_path = project_path or self.project_path
|
|
44
|
+
if not target_project_path:
|
|
45
|
+
return None
|
|
46
|
+
path = os.path.join(target_project_path, ".claude", "commands")
|
|
47
|
+
return os.path.abspath(path)
|
|
48
|
+
|
|
49
|
+
def get_project_commands_display_path(self) -> str:
|
|
50
|
+
"""获取项目指令目录的显示名称"""
|
|
51
|
+
return ".claude/commands/"
|
|
52
|
+
|
|
53
|
+
def get_env_file_path(self, project_path: Optional[str] = None) -> Optional[str]:
|
|
54
|
+
"""获取项目的.agent/.env文件路径"""
|
|
55
|
+
target_project_path = project_path or self.project_path
|
|
56
|
+
if not target_project_path:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
env_path = os.path.join(target_project_path, '.agent', '.env')
|
|
60
|
+
return os.path.abspath(env_path)
|
|
61
|
+
|
|
62
|
+
# 已移除ensure_personal_commands_dir方法
|
|
63
|
+
|
|
64
|
+
def ensure_project_commands_dir(self, project_path: str) -> str:
|
|
65
|
+
"""确保项目指令目录存在,返回路径"""
|
|
66
|
+
path = self.get_project_commands_dir(project_path)
|
|
67
|
+
if path:
|
|
68
|
+
try:
|
|
69
|
+
os.makedirs(path, exist_ok=True)
|
|
70
|
+
if DEBUG_LOG_AVAILABLE:
|
|
71
|
+
logger = get_debug_logger()
|
|
72
|
+
logger.log_save_operation("项目指令目录创建", path, True)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
if DEBUG_LOG_AVAILABLE:
|
|
75
|
+
logger = get_debug_logger()
|
|
76
|
+
logger.log_save_operation("项目指令目录创建", path, False, str(e))
|
|
77
|
+
raise
|
|
78
|
+
return path
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# 全局路径配置实例
|
|
82
|
+
_path_config = None
|
|
83
|
+
|
|
84
|
+
def get_path_config(project_path: Optional[str] = None) -> PathConfig:
|
|
85
|
+
"""获取全局路径配置实例"""
|
|
86
|
+
global _path_config
|
|
87
|
+
if _path_config is None or project_path:
|
|
88
|
+
_path_config = PathConfig(project_path)
|
|
89
|
+
return _path_config
|
post_task_hook.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
try:
|
|
9
|
+
# Read input from stdin
|
|
10
|
+
input_data = json.load(sys.stdin)
|
|
11
|
+
|
|
12
|
+
# Only process Task tool calls
|
|
13
|
+
if input_data.get("tool_name") != "Task":
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
session_id = input_data.get("session_id")
|
|
17
|
+
if not session_id:
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
# Get project root directory from cwd field
|
|
21
|
+
cwd = input_data.get("cwd", ".")
|
|
22
|
+
project_root = Path(cwd)
|
|
23
|
+
|
|
24
|
+
tool_input = input_data.get("tool_input", {})
|
|
25
|
+
tool_response = input_data.get("tool_response", {})
|
|
26
|
+
|
|
27
|
+
# Extract content text
|
|
28
|
+
content_list = tool_response.get("content", [])
|
|
29
|
+
content_text = ""
|
|
30
|
+
for item in content_list:
|
|
31
|
+
if item.get("type") == "text":
|
|
32
|
+
content_text = item.get("text", "")
|
|
33
|
+
break
|
|
34
|
+
|
|
35
|
+
# Construct agent record
|
|
36
|
+
agent_record = {
|
|
37
|
+
"role": "agent",
|
|
38
|
+
"subagent_type": tool_input.get("subagent_type", ""),
|
|
39
|
+
"description": tool_input.get("description", ""),
|
|
40
|
+
"agent_id": tool_response.get("agentId", ""),
|
|
41
|
+
"content": content_text,
|
|
42
|
+
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
43
|
+
"status": tool_response.get("status", ""),
|
|
44
|
+
"duration_ms": tool_response.get("totalDurationMs", 0),
|
|
45
|
+
"tokens": tool_response.get("totalTokens", 0),
|
|
46
|
+
"tool_calls": tool_response.get("totalToolUseCount", 0)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Ensure directory exists using absolute path
|
|
50
|
+
history_dir = project_root / ".workspace/chat_history"
|
|
51
|
+
history_dir.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
|
|
53
|
+
# Read existing history or create new
|
|
54
|
+
history_file = history_dir / f"{session_id}.json"
|
|
55
|
+
if history_file.exists():
|
|
56
|
+
with open(history_file, "r", encoding="utf-8") as f:
|
|
57
|
+
data = json.load(f)
|
|
58
|
+
# Detect format
|
|
59
|
+
if isinstance(data, dict) and "dialogues" in data:
|
|
60
|
+
# New format
|
|
61
|
+
data["dialogues"].append(agent_record)
|
|
62
|
+
else:
|
|
63
|
+
# Old format (list)
|
|
64
|
+
data = {"dialogues": data + [agent_record], "control": {}}
|
|
65
|
+
else:
|
|
66
|
+
# Create new format
|
|
67
|
+
data = {"dialogues": [agent_record], "control": {}}
|
|
68
|
+
|
|
69
|
+
# Write back
|
|
70
|
+
with open(history_file, "w", encoding="utf-8") as f:
|
|
71
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
# Silent fail to avoid breaking the hook chain
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
main()
|
record.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import os
|
|
3
|
+
import requests
|
|
4
|
+
import datetime
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
# 导入统一日志系统
|
|
10
|
+
from debug_logger import get_debug_logger
|
|
11
|
+
|
|
12
|
+
# 导入路径配置模块
|
|
13
|
+
try:
|
|
14
|
+
from path_config import get_path_config
|
|
15
|
+
PATH_CONFIG_AVAILABLE = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
PATH_CONFIG_AVAILABLE = False
|
|
18
|
+
|
|
19
|
+
# 全局变量
|
|
20
|
+
_project_path = None
|
|
21
|
+
_env_loaded = False
|
|
22
|
+
|
|
23
|
+
def setup_stats_logger():
|
|
24
|
+
"""设置统计日志,统一保存到脚本所在目录"""
|
|
25
|
+
logger = logging.getLogger('statistics')
|
|
26
|
+
if not logger.handlers:
|
|
27
|
+
logger.setLevel(logging.DEBUG)
|
|
28
|
+
|
|
29
|
+
# 统一使用脚本所在目录,确保日志位置固定
|
|
30
|
+
script_dir = Path(__file__).parent
|
|
31
|
+
log_file = script_dir / "log.txt"
|
|
32
|
+
|
|
33
|
+
file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
|
|
34
|
+
file_handler.setLevel(logging.DEBUG)
|
|
35
|
+
|
|
36
|
+
formatter = logging.Formatter(
|
|
37
|
+
'%(asctime)s [%(levelname)s] %(name)s: %(message)s',
|
|
38
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
39
|
+
)
|
|
40
|
+
file_handler.setFormatter(formatter)
|
|
41
|
+
logger.addHandler(file_handler)
|
|
42
|
+
|
|
43
|
+
return logger
|
|
44
|
+
|
|
45
|
+
# 创建全局统计日志实例
|
|
46
|
+
stats_logger = setup_stats_logger()
|
|
47
|
+
|
|
48
|
+
def get_absolute_path(path: str, base_dir: str) -> str:
|
|
49
|
+
"""将路径转换为绝对路径
|
|
50
|
+
如果是绝对路径直接返回,否则基于base_dir转换为绝对路径
|
|
51
|
+
"""
|
|
52
|
+
if not path:
|
|
53
|
+
return path
|
|
54
|
+
return path if os.path.isabs(path) else os.path.join(base_dir, path)
|
|
55
|
+
|
|
56
|
+
def set_project_path(project_path: str):
|
|
57
|
+
"""设置项目路径并加载对应的.env文件"""
|
|
58
|
+
global _project_path, _env_loaded
|
|
59
|
+
_project_path = project_path
|
|
60
|
+
|
|
61
|
+
if not project_path:
|
|
62
|
+
logger = get_debug_logger()
|
|
63
|
+
logger.log_warning("统计模块:未提供项目路径", "STATS")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
if PATH_CONFIG_AVAILABLE:
|
|
67
|
+
# 使用path_config获取.env文件路径
|
|
68
|
+
path_config = get_path_config(project_path)
|
|
69
|
+
env_path = path_config.get_env_file_path()
|
|
70
|
+
|
|
71
|
+
if env_path and os.path.exists(env_path):
|
|
72
|
+
load_dotenv(env_path)
|
|
73
|
+
_env_loaded = True
|
|
74
|
+
else:
|
|
75
|
+
logger = get_debug_logger()
|
|
76
|
+
logger.log_warning(f"统计模块未找到.env文件: {env_path}", "STATS")
|
|
77
|
+
else:
|
|
78
|
+
# 兜底方案:直接拼接路径
|
|
79
|
+
env_path = os.path.join(project_path, '.agent', '.env')
|
|
80
|
+
if os.path.exists(env_path):
|
|
81
|
+
load_dotenv(env_path)
|
|
82
|
+
_env_loaded = True
|
|
83
|
+
else:
|
|
84
|
+
logger = get_debug_logger()
|
|
85
|
+
logger.log_warning(f"统计模块兜底也未找到.env文件: {env_path}", "STATS")
|
|
86
|
+
|
|
87
|
+
def get_user_info():
|
|
88
|
+
"""从GitLab认证文件获取用户信息 - 功能已移除
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Tuple[str, str]: (user_id, user_name)
|
|
92
|
+
"""
|
|
93
|
+
# GitLab认证功能已移除
|
|
94
|
+
logger = get_debug_logger()
|
|
95
|
+
logger.log_info("GitLab认证功能已移除", "STATS")
|
|
96
|
+
return None, None
|
|
97
|
+
|
|
98
|
+
def report_action(data):
|
|
99
|
+
"""向API上报操作数据
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
data (dict): 上报数据对象,包含以下字段:
|
|
103
|
+
- user_name (str): 用户名(必填)
|
|
104
|
+
- action (str): 操作类型,如'执行工作流'、'工作流:下一步'等(必填)
|
|
105
|
+
- content (str, optional): 操作内容详情,默认为空字符串
|
|
106
|
+
- workflow_name (str, optional): 工作流名称,默认为空字符串
|
|
107
|
+
- task_name (str, optional): 任务名称,默认为空字符串
|
|
108
|
+
- step_name (str, optional): 步骤名称,默认为空字符串
|
|
109
|
+
- task_id (str, optional): 任务ID,默认为空字符串
|
|
110
|
+
其他字段会作为扩展字段一起上报
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
bool: 上报是否成功
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
ValueError: 当必填字段缺失时抛出异常
|
|
117
|
+
"""
|
|
118
|
+
if not data.get('action'):
|
|
119
|
+
raise ValueError("action 是必填字段")
|
|
120
|
+
|
|
121
|
+
# 获取当前时间戳(毫秒)
|
|
122
|
+
current_time = int(datetime.datetime.now().timestamp() * 1000)
|
|
123
|
+
|
|
124
|
+
# 定义所有必要字段及其默认值
|
|
125
|
+
user_id, user_name = get_user_info()
|
|
126
|
+
required_fields = {
|
|
127
|
+
'user_name': user_name, # 必填,但已经在上面检查过了
|
|
128
|
+
'action': '', # 必填,但已经在上面检查过了
|
|
129
|
+
'content': '',
|
|
130
|
+
'workflow_name': '',
|
|
131
|
+
'task_name': '',
|
|
132
|
+
'step_name': '',
|
|
133
|
+
'task_id': '',
|
|
134
|
+
'time': current_time
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# 使用默认值填充缺失字段
|
|
138
|
+
for field, default_value in required_fields.items():
|
|
139
|
+
data.setdefault(field, default_value)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
stats_logger.info(f"开始上报统计: action={data['action']}, user={user_name}")
|
|
143
|
+
response = requests.post('https://gitstat.aqwhr.cn/tapd/user-action-data/save', json=data)
|
|
144
|
+
stats_logger.info(f"统计上报成功: status={response.status_code}, action={data['action']}")
|
|
145
|
+
return response.status_code == 200
|
|
146
|
+
except Exception as e:
|
|
147
|
+
stats_logger.error(f"统计上报失败: {e}")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
if __name__ == '__main__':
|
|
151
|
+
# 测试用户信息获取
|
|
152
|
+
user_id, user_name = get_user_info()
|
|
153
|
+
print(f"User ID: {user_id}, User Name: {user_name}")
|
|
154
|
+
|
|
155
|
+
# 测试数据上报
|
|
156
|
+
if user_name:
|
|
157
|
+
# 测试基本上报
|
|
158
|
+
success = report_action({
|
|
159
|
+
'user_name': user_name,
|
|
160
|
+
'action': '测试上报',
|
|
161
|
+
'content': '测试内容'
|
|
162
|
+
})
|
|
163
|
+
print(f"Basic report result: {'Success' if success else 'Failed'}")
|
|
164
|
+
|
|
165
|
+
# 测试完整参数上报
|
|
166
|
+
success = report_action({
|
|
167
|
+
'user_name': user_name,
|
|
168
|
+
'action': '工作流执行',
|
|
169
|
+
'content': '执行开发工作流',
|
|
170
|
+
'workflow_name': '开发工作流',
|
|
171
|
+
'task_name': '代码开发',
|
|
172
|
+
'step_name': '编写功能',
|
|
173
|
+
'task_id': '1'
|
|
174
|
+
})
|
|
175
|
+
print(f"Full report result: {'Success' if success else 'Failed'}")
|
|
176
|
+
|
|
177
|
+
# 测试带扩展字段的上报
|
|
178
|
+
success = report_action({
|
|
179
|
+
'user_name': user_name,
|
|
180
|
+
'action': '自定义事件',
|
|
181
|
+
'content': '测试扩展字段',
|
|
182
|
+
'custom_field': '自定义值',
|
|
183
|
+
'extra_info': {
|
|
184
|
+
'key1': 'value1',
|
|
185
|
+
'key2': 'value2'
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
print(f"Extended report result: {'Success' if success else 'Failed'}")
|