stigmergy 1.0.68 → 1.0.70

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.
Files changed (102) hide show
  1. package/README.en.md +306 -300
  2. package/README.md +469 -301
  3. package/package.json +97 -81
  4. package/scripts/publish.js +268 -0
  5. package/scripts/simple-publish.js +59 -0
  6. package/src/index.js +12 -0
  7. package/test/enhanced-main-alignment.test.js +298 -0
  8. package/test/hook-system-integration-test.js +307 -0
  9. package/test/natural-language-skills-test.js +320 -0
  10. package/test/nl-integration-test.js +179 -0
  11. package/test/parameter-parsing-test.js +143 -0
  12. package/test/real-test.js +435 -0
  13. package/test/system-compatibility-test.js +447 -0
  14. package/test/tdd-fixes-test.js +211 -0
  15. package/test/third-party-skills-test.js +321 -0
  16. package/test/tool-selection-integration-test.js +157 -0
  17. package/test/unit/cli-scanner.test.js +291 -0
  18. package/test/unit/cross-cli-executor.test.js +399 -0
  19. package/src/adapters/claude/__init__.py +0 -13
  20. package/src/adapters/claude/claude_skills_integration.py +0 -609
  21. package/src/adapters/claude/hook_adapter.py +0 -663
  22. package/src/adapters/claude/install_claude_integration.py +0 -265
  23. package/src/adapters/claude/skills_hook_adapter.py +0 -841
  24. package/src/adapters/claude/standalone_claude_adapter.py +0 -384
  25. package/src/adapters/cline/__init__.py +0 -20
  26. package/src/adapters/cline/config.py +0 -108
  27. package/src/adapters/cline/install_cline_integration.py +0 -617
  28. package/src/adapters/cline/mcp_server.py +0 -713
  29. package/src/adapters/cline/standalone_cline_adapter.py +0 -459
  30. package/src/adapters/codebuddy/__init__.py +0 -13
  31. package/src/adapters/codebuddy/buddy_adapter.py +0 -1125
  32. package/src/adapters/codebuddy/install_codebuddy_integration.py +0 -279
  33. package/src/adapters/codebuddy/skills_hook_adapter.py +0 -672
  34. package/src/adapters/codebuddy/skills_integration.py +0 -395
  35. package/src/adapters/codebuddy/standalone_codebuddy_adapter.py +0 -403
  36. package/src/adapters/codex/__init__.py +0 -11
  37. package/src/adapters/codex/base.py +0 -46
  38. package/src/adapters/codex/install_codex_integration.py +0 -311
  39. package/src/adapters/codex/mcp_server.py +0 -493
  40. package/src/adapters/codex/natural_language_parser.py +0 -82
  41. package/src/adapters/codex/slash_command_adapter.py +0 -326
  42. package/src/adapters/codex/standalone_codex_adapter.py +0 -362
  43. package/src/adapters/copilot/__init__.py +0 -13
  44. package/src/adapters/copilot/install_copilot_integration.py +0 -564
  45. package/src/adapters/copilot/mcp_adapter.py +0 -772
  46. package/src/adapters/copilot/mcp_server.py +0 -168
  47. package/src/adapters/copilot/standalone_copilot_adapter.py +0 -114
  48. package/src/adapters/gemini/__init__.py +0 -13
  49. package/src/adapters/gemini/extension_adapter.py +0 -690
  50. package/src/adapters/gemini/install_gemini_integration.py +0 -257
  51. package/src/adapters/gemini/standalone_gemini_adapter.py +0 -366
  52. package/src/adapters/iflow/__init__.py +0 -7
  53. package/src/adapters/iflow/hook_adapter.py +0 -1038
  54. package/src/adapters/iflow/hook_installer.py +0 -536
  55. package/src/adapters/iflow/install_iflow_integration.py +0 -271
  56. package/src/adapters/iflow/official_hook_adapter.py +0 -1272
  57. package/src/adapters/iflow/standalone_iflow_adapter.py +0 -48
  58. package/src/adapters/iflow/workflow_adapter.py +0 -793
  59. package/src/adapters/qoder/hook_installer.py +0 -732
  60. package/src/adapters/qoder/install_qoder_integration.py +0 -265
  61. package/src/adapters/qoder/notification_hook_adapter.py +0 -863
  62. package/src/adapters/qoder/standalone_qoder_adapter.py +0 -48
  63. package/src/adapters/qwen/__init__.py +0 -17
  64. package/src/adapters/qwencode/__init__.py +0 -13
  65. package/src/adapters/qwencode/inheritance_adapter.py +0 -818
  66. package/src/adapters/qwencode/install_qwencode_integration.py +0 -276
  67. package/src/adapters/qwencode/standalone_qwencode_adapter.py +0 -399
  68. package/src/atomic_collaboration_handler.py +0 -461
  69. package/src/cli_collaboration_agent.py +0 -697
  70. package/src/collaboration/hooks.py +0 -315
  71. package/src/core/__init__.py +0 -21
  72. package/src/core/ai_environment_scanner.py +0 -331
  73. package/src/core/base_adapter.py +0 -220
  74. package/src/core/cli_hook_integration.py +0 -406
  75. package/src/core/cross_cli_executor.py +0 -713
  76. package/src/core/cross_cli_mapping.py +0 -1165
  77. package/src/core/cross_platform_encoding.py +0 -365
  78. package/src/core/cross_platform_safe_cli.py +0 -894
  79. package/src/core/direct_cli_executor.py +0 -805
  80. package/src/core/direct_cli_hook_system.py +0 -958
  81. package/src/core/enhanced_init_processor.py +0 -467
  82. package/src/core/graceful_cli_executor.py +0 -912
  83. package/src/core/md_enhancer.py +0 -342
  84. package/src/core/md_generator.py +0 -619
  85. package/src/core/models.py +0 -218
  86. package/src/core/parser.py +0 -108
  87. package/src/core/real_cli_hook_system.py +0 -852
  88. package/src/core/real_cross_cli_system.py +0 -925
  89. package/src/core/verified_cross_cli_system.py +0 -961
  90. package/src/deploy.js +0 -737
  91. package/src/enhanced-main.js +0 -626
  92. package/src/enhanced_deploy.js +0 -303
  93. package/src/enhanced_universal_cli_setup.py +0 -930
  94. package/src/kimi_wrapper.py +0 -104
  95. package/src/main.js +0 -1309
  96. package/src/shell_integration.py +0 -398
  97. package/src/simple-main.js +0 -315
  98. package/src/smart_router_creator.py +0 -323
  99. package/src/universal_cli_setup.py +0 -1289
  100. package/src/utils/__init__.py +0 -12
  101. package/src/utils/cli_detector.py +0 -445
  102. package/src/utils/file_utils.py +0 -246
@@ -1,863 +0,0 @@
1
- """
2
- QoderCLI通知Hook适配器 - 基于QoderCLI通知Hook系统的原生集成
3
-
4
- 基于QoderCLI官方通知Hook机制实现跨CLI调用功能。
5
- Qoder CLI 目前主要支持通知类 Hooks,使用 AppleScript 进行 macOS 通知。
6
-
7
- QoderCLI Hook机制:
8
- - 通知类Hooks:通过osascript发送macOS系统通知
9
- - 环境变量Hook:通过QODER_CROSS_CLI_* 环境变量进行通信
10
- - 钩子点:任务执行前、执行后、错误处理
11
- - 平台支持:主要针对macOS,兼容其他平台的fallback机制
12
-
13
- 完全符合项目约束条件:
14
- - 使用Qoder CLI官方Hook机制
15
- - 不改变CLI启动和使用方式
16
- - 不依赖包装器
17
- - 完全无损扩展
18
- """
19
-
20
- import os
21
- import sys
22
- import json
23
- import logging
24
- import asyncio
25
- import subprocess
26
- import platform
27
- import tempfile
28
- from typing import Dict, Any, Optional, List
29
- from datetime import datetime
30
- from pathlib import Path
31
- from dataclasses import dataclass
32
-
33
- from ...core.base_adapter import BaseCrossCLIAdapter, IntentResult
34
- from ...core.parser import NaturalLanguageParser
35
-
36
- logger = logging.getLogger(__name__)
37
-
38
-
39
- @dataclass
40
- class QoderHookEvent:
41
- """Qoder Hook事件对象"""
42
- hook_type: str
43
- stage: str
44
- data: Dict[str, Any]
45
- timestamp: datetime
46
- session_id: str
47
- command: Optional[str] = None
48
- exit_code: Optional[int] = None
49
-
50
-
51
- class QoderNotificationHookAdapter(BaseCrossCLIAdapter):
52
- """
53
- QoderCLI通知Hook适配器
54
-
55
- 基于Qoder CLI的通知Hook系统和环境变量机制实现跨CLI调用功能。
56
- 主要通过通知系统进行状态提示,环境变量进行实际数据交换。
57
- """
58
-
59
- def __init__(self, cli_name: str = "qoder"):
60
- """
61
- 初始化Qoder通知Hook适配器
62
-
63
- Args:
64
- cli_name: CLI工具名称,默认为"qoder"
65
- """
66
- super().__init__(cli_name)
67
-
68
- # Qoder Hook相关配置
69
- self.is_macos = platform.system() == "Darwin"
70
- self.hook_enabled = False
71
- self.cross_cli_enabled = True
72
-
73
- # 环境变量配置
74
- self.env_vars = {
75
- 'QODER_CROSS_CLI_ENABLED': '1',
76
- 'QODER_CROSS_CLI_RESPONSE_FILE': '',
77
- 'QODER_CROSS_CLI_REQUEST_FILE': '',
78
- 'QODER_CROSS_CLI_STATUS_FILE': '',
79
- 'QODER_HOOK_STAGE': '',
80
- 'QODER_HOOK_COMMAND': '',
81
- 'QODER_HOOK_SESSION_ID': ''
82
- }
83
-
84
- # 统计信息
85
- self.hook_executions = {
86
- 'pre_command': 0,
87
- 'post_command': 0,
88
- 'error_handling': 0,
89
- 'notification_sent': 0
90
- }
91
- self.cross_cli_calls = 0
92
- self.processed_events: List[QoderHookEvent] = []
93
- self.active_sessions: Dict[str, Dict] = {}
94
-
95
- # Hook脚本路径
96
- self.hook_script_dir = os.path.expanduser("~/.qoder/hooks")
97
- self.temp_dir = None
98
-
99
- # 组件
100
- self.parser = NaturalLanguageParser()
101
-
102
- logger.info("Qoder通知Hook适配器初始化完成")
103
-
104
- async def initialize(self) -> bool:
105
- """
106
- 初始化适配器
107
-
108
- Returns:
109
- bool: 初始化是否成功
110
- """
111
- try:
112
- logger.info("开始初始化Qoder通知Hook适配器...")
113
-
114
- # 1. 检查Qoder CLI环境
115
- if not self._check_qoder_environment():
116
- logger.error("Qoder CLI环境检查失败")
117
- return False
118
-
119
- # 2. 创建临时目录和Hook目录
120
- await self._create_directories()
121
-
122
- # 3. 设置环境变量
123
- await self._setup_environment_variables()
124
-
125
- # 4. 创建Hook脚本
126
- if not await self._create_hook_scripts():
127
- logger.error("Hook脚本创建失败")
128
- return False
129
-
130
- # 5. 初始化通知系统
131
- await self._initialize_notification_system()
132
-
133
- self.hook_enabled = True
134
- logger.info("Qoder通知Hook适配器初始化成功")
135
- return True
136
-
137
- except Exception as e:
138
- logger.error(f"初始化Qoder通知Hook适配器失败: {e}")
139
- self.record_error()
140
- return False
141
-
142
- def _check_qoder_environment(self) -> bool:
143
- """
144
- 检查Qoder CLI环境
145
-
146
- Returns:
147
- bool: 环境是否可用
148
- """
149
- try:
150
- # 检查Qoder CLI命令
151
- result = subprocess.run(
152
- ['qoder', '--version'],
153
- capture_output=True,
154
- text=True,
155
- timeout=5
156
- )
157
-
158
- if result.returncode == 0:
159
- logger.info(f"检测到Qoder CLI: {result.stdout.strip()}")
160
- return True
161
- else:
162
- logger.warning("Qoder CLI不可用,使用开发模式")
163
- return True # 开发环境中继续
164
-
165
- except (subprocess.TimeoutExpired, FileNotFoundError):
166
- logger.warning("Qoder CLI环境检查失败,使用开发模式")
167
- return True # 开发环境中继续
168
-
169
- async def _create_directories(self) -> None:
170
- """创建必要的目录"""
171
- directories = [
172
- self.hook_script_dir,
173
- os.path.expanduser("~/.qoder/logs"),
174
- os.path.expanduser("~/.qoder/cache")
175
- ]
176
-
177
- for directory in directories:
178
- os.makedirs(directory, exist_ok=True)
179
-
180
- # 创建临时目录
181
- self.temp_dir = tempfile.mkdtemp(prefix="qoder_cross_cli_")
182
- logger.info(f"临时目录: {self.temp_dir}")
183
-
184
- async def _setup_environment_variables(self) -> None:
185
- """设置环境变量"""
186
- # 设置响应文件路径
187
- response_file = os.path.join(self.temp_dir, "cross_cli_response.json")
188
- request_file = os.path.join(self.temp_dir, "cross_cli_request.json")
189
- status_file = os.path.join(self.temp_dir, "cross_cli_status.json")
190
-
191
- self.env_vars.update({
192
- 'QODER_CROSS_CLI_RESPONSE_FILE': response_file,
193
- 'QODER_CROSS_CLI_REQUEST_FILE': request_file,
194
- 'QODER_CROSS_CLI_STATUS_FILE': status_file
195
- })
196
-
197
- # 设置环境变量
198
- for key, value in self.env_vars.items():
199
- os.environ[key] = value
200
-
201
- logger.info("环境变量设置完成")
202
-
203
- async def _create_hook_scripts(self) -> bool:
204
- """
205
- 创建Hook脚本
206
-
207
- Returns:
208
- bool: 创建是否成功
209
- """
210
- try:
211
- # 创建前置Hook脚本
212
- pre_hook_script = '''#!/bin/bash
213
- # Qoder CLI前置Hook脚本
214
- # 用于检测跨CLI调用意图
215
-
216
- COMMAND="$1"
217
- STAGE="pre_command"
218
- SESSION_ID="${QODER_HOOK_SESSION_ID:-$(date +%s)}"
219
-
220
- # 设置环境变量
221
- export QODER_HOOK_STAGE="$STAGE"
222
- export QODER_HOOK_COMMAND="$COMMAND"
223
- export QODER_HOOK_SESSION_ID="$SESSION_ID"
224
-
225
- # 记录请求到文件
226
- REQUEST_FILE="$QODER_CROSS_CLI_REQUEST_FILE"
227
- if [ -n "$REQUEST_FILE" ]; then
228
- cat > "$REQUEST_FILE" << EOF
229
- {
230
- "stage": "$STAGE",
231
- "command": "$COMMAND",
232
- "session_id": "$SESSION_ID",
233
- "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
234
- "env_vars": $(env | grep QODER_ | jq -R 'split("="; {key: .[0], value: .[1]}' | jq -s 'from_entries')
235
- }
236
- EOF
237
- fi
238
-
239
- # 检测跨CLI调用意图
240
- if echo "$COMMAND" | grep -E "(请用|调用|用|让).*claude|gemini|qwencode|iflow|qoder|codebuddy|codex" > /dev/null 2>&1; then
241
- # 发送跨CLI检测通知
242
- if command -v osascript > /dev/null 2>&1; then
243
- osascript -e "display notification \"检测到跨CLI调用意图\" with title \"QoderCLI\" subtitle \"准备调用其他AI工具\""
244
- fi
245
- fi
246
-
247
- exit 0
248
- '''
249
-
250
- # 创建后置Hook脚本
251
- post_hook_script = '''#!/bin/bash
252
- # Qoder CLI后置Hook脚本
253
- # 用于处理跨CLI调用结果
254
-
255
- EXIT_CODE=$?
256
- STAGE="post_command"
257
- COMMAND="$1"
258
- SESSION_ID="${QODER_HOOK_SESSION_ID:-$(date +%s)}"
259
-
260
- # 设置环境变量
261
- export QODER_HOOK_STAGE="$STAGE"
262
- export QODER_HOOK_COMMAND="$COMMAND"
263
- export QODER_HOOK_SESSION_ID="$SESSION_ID"
264
-
265
- # 记录完成状态
266
- if [ -n "$QODER_CROSS_CLI_STATUS_FILE" ]; then
267
- cat > "$QODER_CROSS_CLI_STATUS_FILE" << EOF
268
- {
269
- "stage": "$STAGE",
270
- "command": "$COMMAND",
271
- "session_id": "$SESSION_ID",
272
- "exit_code": $EXIT_CODE,
273
- "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
274
- "completed": true
275
- }
276
- EOF
277
- fi
278
-
279
- # 检查是否有跨CLI响应
280
- RESPONSE_FILE="$QODER_CROSS_CLI_RESPONSE_FILE"
281
- if [ -f "$RESPONSE_FILE" ] && [ -s "$RESPONSE_FILE" ]; then
282
- # 发送完成通知
283
- if command -v osascript > /dev/null 2>&1; then
284
- osascript -e 'display notification "[OK] 跨CLI调用完成" with title "QoderCLI"'
285
- fi
286
- fi
287
-
288
- exit $EXIT_CODE
289
- '''
290
-
291
- # 创建错误处理Hook脚本
292
- error_hook_script = '''#!/bin/bash
293
- # Qoder CLI错误处理Hook脚本
294
- # 用于处理跨CLI调用错误
295
-
296
- EXIT_CODE=$?
297
- STAGE="error_handling"
298
- COMMAND="$1"
299
- SESSION_ID="${QODER_HOOK_SESSION_ID:-$(date +%s)}"
300
-
301
- # 设置环境变量
302
- export QODER_HOOK_STAGE="$STAGE"
303
- export QODER_HOOK_COMMAND="$COMMAND"
304
- export QODER_HOOK_SESSION_ID="$SESSION_ID"
305
-
306
- # 如果有错误,发送通知
307
- if [ $EXIT_CODE -ne 0 ]; then
308
- if command -v osascript > /dev/null 2>&1; then
309
- osascript -e 'display notification "⌛️ 你提交的任务需要授权呀…" with title "QoderCLI"'
310
- fi
311
- fi
312
-
313
- exit 0
314
- '''
315
-
316
- # 写入脚本文件
317
- scripts = {
318
- 'pre_hook.sh': pre_hook_script,
319
- 'post_hook.sh': post_hook_script,
320
- 'error_hook.sh': error_hook_script
321
- }
322
-
323
- for filename, content in scripts.items():
324
- script_path = os.path.join(self.hook_script_dir, filename)
325
- with open(script_path, 'w', encoding='utf-8') as f:
326
- f.write(content)
327
- os.chmod(script_path, 0o755)
328
-
329
- logger.info("Qoder Hook脚本创建完成")
330
- return True
331
-
332
- except Exception as e:
333
- logger.error(f"创建Hook脚本失败: {e}")
334
- return False
335
-
336
- async def _initialize_notification_system(self) -> None:
337
- """初始化通知系统"""
338
- if self.is_macos:
339
- # 测试AppleScript是否可用
340
- try:
341
- subprocess.run([
342
- 'osascript', '-e', 'display notification "Qoder CLI Hook系统初始化" with title "测试通知"'
343
- ], check=True, capture_output=True, timeout=5)
344
- logger.info("macOS通知系统初始化成功")
345
- except subprocess.CalledProcessError:
346
- logger.warning("macOS通知系统不可用,将使用fallback通知")
347
- else:
348
- logger.info("非macOS系统,将使用fallback通知机制")
349
-
350
- # ==================== 跨CLI功能实现 ====================
351
-
352
- async def handle_cross_cli_detection(self, command: str, session_id: str) -> Optional[str]:
353
- """
354
- 处理跨CLI调用检测
355
-
356
- Args:
357
- command: 命令内容
358
- session_id: 会话ID
359
-
360
- Returns:
361
- Optional[str]: 跨CLI调用结果
362
- """
363
- try:
364
- logger.info(f"检测跨CLI调用: {command}")
365
-
366
- # 解析跨CLI意图
367
- intent = self.parser.parse_intent(command, "qoder")
368
-
369
- if not intent.is_cross_cli:
370
- return None
371
-
372
- # 避免自我调用
373
- if intent.target_cli == self.cli_name:
374
- return None
375
-
376
- # 发送检测通知
377
- await self._send_notification(
378
- f"检测到跨CLI调用: {intent.target_cli}",
379
- "QoderCLI",
380
- subtitle=f"任务: {intent.task[:50]}..."
381
- )
382
-
383
- # 执行跨CLI调用
384
- result = await self._execute_cross_cli_call(
385
- intent.target_cli,
386
- intent.task,
387
- {"command": command, "session_id": session_id}
388
- )
389
-
390
- if result:
391
- self.cross_cli_calls += 1
392
-
393
- # 将结果写入响应文件
394
- await self._write_response_file(result)
395
-
396
- # 发送完成通知
397
- await self._send_notification(
398
- "[OK] 跨CLI调用完成",
399
- "QoderCLI",
400
- subtitle=f"{intent.target_cli.upper()} 任务已完成"
401
- )
402
-
403
- return result
404
-
405
- return None
406
-
407
- except Exception as e:
408
- logger.error(f"处理跨CLI检测失败: {e}")
409
- self.record_error()
410
- return None
411
-
412
- async def _execute_cross_cli_call(
413
- self,
414
- target_cli: str,
415
- task: str,
416
- context: Dict[str, Any]
417
- ) -> Optional[str]:
418
- """
419
- 执行跨CLI调用
420
-
421
- Args:
422
- target_cli: 目标CLI工具
423
- task: 要执行的任务
424
- context: 执行上下文
425
-
426
- Returns:
427
- Optional[str]: 执行结果
428
- """
429
- try:
430
- logger.info(f"执行跨CLI调用: {target_cli} -> {task}")
431
-
432
- # 获取目标CLI适配器
433
- from ...core.base_adapter import get_cross_cli_adapter
434
- target_adapter = get_cross_cli_adapter(target_cli)
435
-
436
- if not target_adapter:
437
- logger.warning(f"目标CLI适配器不可用: {target_cli}")
438
- return self._format_error_result(target_cli, task, f"目标CLI工具 '{target_cli}' 不可用")
439
-
440
- if not target_adapter.is_available():
441
- logger.warning(f"目标CLI工具不可用: {target_cli}")
442
- return self._format_error_result(target_cli, task, f"目标CLI工具 '{target_cli}' 当前不可用")
443
-
444
- # 构建执行上下文
445
- execution_context = {
446
- 'source_cli': self.cli_name,
447
- 'target_cli': target_cli,
448
- 'original_task': task,
449
- 'qoder_context': context,
450
- 'timestamp': datetime.now().isoformat()
451
- }
452
-
453
- # 执行任务
454
- result = await target_adapter.execute_task(task, execution_context)
455
-
456
- # 记录成功的跨CLI调用
457
- self.processed_requests.append({
458
- 'type': 'cross_cli_execution',
459
- 'target_cli': target_cli,
460
- 'task': task,
461
- 'success': True,
462
- 'result_length': len(result),
463
- 'timestamp': datetime.now().isoformat()
464
- })
465
-
466
- # 格式化结果
467
- formatted_result = self._format_success_result(target_cli, task, result)
468
-
469
- logger.info(f"跨CLI调用成功: {target_cli}")
470
- return formatted_result
471
-
472
- except Exception as e:
473
- logger.error(f"跨CLI调用失败: {target_cli}, {e}")
474
- self.record_error()
475
-
476
- self.processed_requests.append({
477
- 'type': 'cross_cli_execution',
478
- 'target_cli': target_cli,
479
- 'task': task,
480
- 'success': False,
481
- 'error': str(e),
482
- 'timestamp': datetime.now().isoformat()
483
- })
484
-
485
- return self._format_error_result(target_cli, task, str(e))
486
-
487
- async def _send_notification(self, message: str, title: str = "QoderCLI", subtitle: str = "") -> None:
488
- """
489
- 发送通知
490
-
491
- Args:
492
- message: 通知消息
493
- title: 通知标题
494
- subtitle: 副标题
495
- """
496
- try:
497
- if self.is_macos:
498
- # 使用AppleScript发送macOS通知
499
- script = f'display notification "{message}" with title "{title}"'
500
- if subtitle:
501
- script += f' subtitle "{subtitle}"'
502
-
503
- subprocess.run(['osascript', '-e', script], check=True, capture_output=True, timeout=5)
504
- self.hook_executions['notification_sent'] += 1
505
- else:
506
- # 非macOS系统的fallback通知
507
- logger.info(f"[NOTIFICATION] {title}: {message} ({subtitle})")
508
-
509
- except Exception as e:
510
- logger.error(f"发送通知失败: {e}")
511
- # Fallback到日志
512
- logger.info(f"[NOTIFICATION] {title}: {message}")
513
-
514
- async def _write_response_file(self, result: str) -> None:
515
- """
516
- 写入响应文件
517
-
518
- Args:
519
- result: 响应结果
520
- """
521
- try:
522
- response_file = self.env_vars.get('QODER_CROSS_CLI_RESPONSE_FILE')
523
- if response_file:
524
- response_data = {
525
- 'result': result,
526
- 'timestamp': datetime.now().isoformat(),
527
- 'cross_cli': True
528
- }
529
-
530
- with open(response_file, 'w', encoding='utf-8') as f:
531
- json.dump(response_data, f, ensure_ascii=False, indent=2)
532
-
533
- logger.debug(f"响应已写入: {response_file}")
534
-
535
- except Exception as e:
536
- logger.error(f"写入响应文件失败: {e}")
537
-
538
- def _format_success_result(self, target_cli: str, task: str, result: str) -> str:
539
- """
540
- 格式化成功的跨CLI调用结果
541
-
542
- Args:
543
- target_cli: 目标CLI工具
544
- task: 原始任务
545
- result: 执行结果
546
-
547
- Returns:
548
- str: 格式化的结果
549
- """
550
- return f"""## 🔗 跨CLI调用结果 (Qoder Hook)
551
-
552
- **源工具**: Qoder CLI
553
- **目标工具**: {target_cli.upper()}
554
- **原始任务**: {task}
555
- **执行时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
556
-
557
- ---
558
-
559
- {result}
560
-
561
- ---
562
-
563
- *此结果由跨CLI集成系统通过Qoder CLI通知Hook提供*"""
564
-
565
- def _format_error_result(self, target_cli: str, task: str, error_message: str) -> str:
566
- """
567
- 格式化错误的跨CLI调用结果
568
-
569
- Args:
570
- target_cli: 目标CLI工具
571
- task: 原始任务
572
- error_message: 错误信息
573
-
574
- Returns:
575
- str: 格式化的错误结果
576
- """
577
- return f"""## ❌ 跨CLI调用失败
578
-
579
- **源工具**: Qoder CLI
580
- **目标工具**: {target_cli.upper()}
581
- **原始任务**: {task}
582
- **错误信息**: {error_message}
583
- **失败时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
584
-
585
- 请检查目标CLI工具是否正确安装和配置。
586
-
587
- ---
588
-
589
- *此错误由跨CLI集成系统报告*"""
590
-
591
- # ==================== Hook监控 ====================
592
-
593
- async def monitor_hook_events(self) -> None:
594
- """监控Hook事件"""
595
- try:
596
- request_file = self.env_vars.get('QODER_CROSS_CLI_REQUEST_FILE')
597
- status_file = self.env_vars.get('QODER_CROSS_CLI_STATUS_FILE')
598
-
599
- if request_file and os.path.exists(request_file):
600
- await self._process_request_file(request_file)
601
-
602
- if status_file and os.path.exists(status_file):
603
- await self._process_status_file(status_file)
604
-
605
- except Exception as e:
606
- logger.error(f"监控Hook事件失败: {e}")
607
-
608
- async def _process_request_file(self, file_path: str) -> None:
609
- """处理请求文件"""
610
- try:
611
- with open(file_path, 'r', encoding='utf-8') as f:
612
- request_data = json.load(f)
613
-
614
- event = QoderHookEvent(
615
- hook_type='notification',
616
- stage=request_data.get('stage', ''),
617
- data=request_data,
618
- timestamp=datetime.now(),
619
- session_id=request_data.get('session_id', ''),
620
- command=request_data.get('command', '')
621
- )
622
-
623
- self.processed_events.append(event)
624
-
625
- # 如果是前置命令,检测跨CLI调用
626
- if event.stage == 'pre_command' and event.command:
627
- cross_cli_result = await self.handle_cross_cli_detection(
628
- event.command,
629
- event.session_id
630
- )
631
-
632
- if cross_cli_result:
633
- logger.info("通过Hook检测到并处理了跨CLI调用")
634
-
635
- except Exception as e:
636
- logger.error(f"处理请求文件失败: {e}")
637
-
638
- async def _process_status_file(self, file_path: str) -> None:
639
- """处理状态文件"""
640
- try:
641
- with open(file_path, 'r', encoding='utf-8') as f:
642
- status_data = json.load(f)
643
-
644
- logger.debug(f"Hook状态: {status_data}")
645
-
646
- # 更新会话统计
647
- session_id = status_data.get('session_id', '')
648
- if session_id:
649
- if session_id not in self.active_sessions:
650
- self.active_sessions[session_id] = {
651
- 'start_time': datetime.now(),
652
- 'commands': [],
653
- 'cross_cli_calls': 0
654
- }
655
-
656
- session = self.active_sessions[session_id]
657
- session['commands'].append(status_data)
658
-
659
- except Exception as e:
660
- logger.error(f"处理状态文件失败: {e}")
661
-
662
- # ==================== 基础接口实现 ====================
663
-
664
- def is_available(self) -> bool:
665
- """
666
- 检查适配器是否可用
667
-
668
- Returns:
669
- bool: 是否可用
670
- """
671
- return (
672
- self.hook_enabled and
673
- os.path.exists(self.hook_script_dir) and
674
- self.temp_dir is not None
675
- )
676
-
677
- async def execute_task(self, task: str, context: Dict[str, Any]) -> str:
678
- """
679
- 执行跨CLI任务 - Qoder适配器的具体实现
680
-
681
- Args:
682
- task: 要执行的任务描述
683
- context: 执行上下文信息
684
-
685
- Returns:
686
- str: 任务执行结果
687
- """
688
- try:
689
- session_id = context.get('session_id', f"task-{datetime.now().timestamp()}")
690
-
691
- # 直接处理跨CLI检测
692
- cross_cli_result = await self.handle_cross_cli_detection(task, session_id)
693
-
694
- if cross_cli_result:
695
- return cross_cli_result
696
-
697
- return f"Qoder通知Hook适配器处理: {task}"
698
-
699
- except Exception as e:
700
- logger.error(f"执行任务失败: {task}, 错误: {e}")
701
- self.record_error()
702
- return f"任务执行失败: {str(e)}"
703
-
704
- async def health_check(self) -> Dict[str, Any]:
705
- """
706
- 健康检查
707
-
708
- Returns:
709
- Dict[str, Any]: 健康状态
710
- """
711
- base_health = await super().health_check()
712
-
713
- qoder_health = {
714
- 'hook_enabled': self.hook_enabled,
715
- 'is_macos': self.is_macos,
716
- 'hook_executions': self.hook_executions.copy(),
717
- 'cross_cli_calls': self.cross_cli_calls,
718
- 'processed_events_count': len(self.processed_events),
719
- 'active_sessions_count': len(self.active_sessions),
720
- 'hook_script_dir': self.hook_script_dir,
721
- 'hook_scripts_exist': os.path.exists(os.path.join(self.hook_script_dir, 'pre_hook.sh')),
722
- 'temp_dir': self.temp_dir,
723
- 'env_vars_configured': all(key in os.environ for key in self.env_vars.keys())
724
- }
725
-
726
- # 检查环境
727
- try:
728
- qoder_health['qoder_environment'] = self._check_qoder_environment()
729
- except Exception as e:
730
- qoder_health['qoder_environment_error'] = str(e)
731
-
732
- # 合并基础健康信息
733
- base_health.update(qoder_health)
734
- return base_health
735
-
736
- def get_statistics(self) -> Dict[str, Any]:
737
- """
738
- 获取适配器统计信息
739
-
740
- Returns:
741
- Dict[str, Any]: 统计信息
742
- """
743
- base_stats = super().get_statistics()
744
-
745
- qoder_stats = {
746
- 'hook_enabled': self.hook_enabled,
747
- 'is_macos': self.is_macos,
748
- 'hook_executions': self.hook_executions.copy(),
749
- 'cross_cli_calls': self.cross_cli_calls,
750
- 'processed_events_count': len(self.processed_events),
751
- 'active_sessions_count': len(self.active_sessions),
752
- 'total_hook_calls': sum(self.hook_executions.values()),
753
- 'notification_sent': self.hook_executions['notification_sent'],
754
- 'hook_script_dir': self.hook_script_dir
755
- }
756
-
757
- base_stats.update(qoder_stats)
758
- return base_stats
759
-
760
- async def cleanup(self) -> bool:
761
- """
762
- 清理适配器资源
763
-
764
- Returns:
765
- bool: 清理是否成功
766
- """
767
- try:
768
- # 清理统计信息
769
- self.processed_events.clear()
770
- self.active_sessions.clear()
771
- self.hook_executions = {key: 0 for key in self.hook_executions.keys()}
772
-
773
- # 清理临时目录
774
- if self.temp_dir and os.path.exists(self.temp_dir):
775
- import shutil
776
- shutil.rmtree(self.temp_dir, ignore_errors=True)
777
- self.temp_dir = None
778
-
779
- logger.info("Qoder通知Hook适配器清理完成")
780
- return True
781
-
782
- except Exception as e:
783
- logger.error(f"清理Qoder通知Hook适配器失败: {e}")
784
- return False
785
-
786
- async def start_monitoring(self) -> None:
787
- """开始监控Hook事件"""
788
- if not self.is_available():
789
- logger.warning("适配器不可用,无法开始监控")
790
- return
791
-
792
- logger.info("开始监控Qoder Hook事件")
793
- try:
794
- while self.hook_enabled:
795
- await self.monitor_hook_events()
796
- await asyncio.sleep(1) # 每秒检查一次
797
- except asyncio.CancelledError:
798
- logger.info("Hook监控已停止")
799
- except Exception as e:
800
- logger.error(f"Hook监控异常: {e}")
801
-
802
-
803
- # 创建全局适配器实例
804
- _global_adapter: Optional[QoderNotificationHookAdapter] = None
805
-
806
-
807
- def get_qoder_notification_hook_adapter() -> QoderNotificationHookAdapter:
808
- """
809
- 获取Qoder通知Hook适配器实例
810
-
811
- Returns:
812
- QoderNotificationHookAdapter: 适配器实例
813
- """
814
- global _global_adapter
815
- if _global_adapter is None:
816
- _global_adapter = QoderNotificationHookAdapter()
817
- # 异步初始化需要在调用时进行
818
- return _global_adapter
819
-
820
-
821
- # 便捷函数
822
- async def initialize_qoder_notification_adapter() -> bool:
823
- """
824
- 初始化Qoder通知Hook适配器
825
-
826
- Returns:
827
- bool: 初始化是否成功
828
- """
829
- adapter = get_qoder_notification_hook_adapter()
830
- return await adapter.initialize()
831
-
832
-
833
- def is_qoder_notification_adapter_available() -> bool:
834
- """
835
- 检查Qoder通知Hook适配器是否可用
836
-
837
- Returns:
838
- bool: 是否可用
839
- """
840
- adapter = get_qoder_notification_hook_adapter()
841
- return adapter.is_available()
842
-
843
-
844
- if __name__ == "__main__":
845
- import asyncio
846
-
847
- async def main():
848
- """主函数 - 用于测试和独立运行"""
849
- adapter = QoderNotificationHookAdapter()
850
-
851
- # 初始化
852
- if await adapter.initialize():
853
- print("Qoder通知Hook适配器初始化成功")
854
-
855
- # 开始监控
856
- try:
857
- await adapter.start_monitoring()
858
- except KeyboardInterrupt:
859
- print("\n停止监控")
860
- else:
861
- print("Qoder通知Hook适配器初始化失败")
862
-
863
- asyncio.run(main())