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,1272 +0,0 @@
1
- """
2
- iFlow CLI官方Hook适配器 - 基于iFlow CLI官方Hook系统的原生集成
3
-
4
- 严格基于iFlow CLI官方文档实现9种Hook类型:
5
- 1. PreToolUse - 工具执行前触发
6
- 2. PostToolUse - 工具执行后触发
7
- 3. SetUpEnvironment - 环境设置时触发
8
- 4. Stop - 主会话结束时触发
9
- 5. SubagentStop - 子代理会话结束时触发
10
- 6. SessionStart - 会话开始时触发
11
- 7. SessionEnd - 会话结束时触发
12
- 8. UserPromptSubmit - 用户提示词提交时触发
13
- 9. Notification - 通知发送时触发
14
-
15
- 完全符合项目约束条件:
16
- - 使用iFlow CLI官方Hook机制
17
- - 不改变CLI启动和使用方式
18
- - 不依赖包装器
19
- - 完全无损扩展
20
- """
21
-
22
- import os
23
- import json
24
- import logging
25
- import asyncio
26
- import subprocess
27
- from typing import Dict, Any, Optional, List, Callable
28
- from datetime import datetime
29
- from pathlib import Path
30
- from dataclasses import dataclass
31
-
32
- from ...core.base_adapter import BaseCrossCLIAdapter, IntentResult
33
- from ...core.parser import NaturalLanguageParser
34
-
35
- logger = logging.getLogger(__name__)
36
-
37
-
38
- @dataclass
39
- class IFlowHookEvent:
40
- """iFlow Hook事件对象"""
41
- hook_type: str
42
- matcher: Optional[str]
43
- data: Dict[str, Any]
44
- timestamp: datetime
45
- session_id: str
46
- tool_name: Optional[str] = None
47
- command: Optional[str] = None
48
-
49
-
50
- class IFlowOfficialHookAdapter(BaseCrossCLIAdapter):
51
- """
52
- iFlow CLI官方Hook适配器
53
-
54
- 基于iFlow CLI官方Hook系统实现跨CLI调用功能
55
- 支持所有9种官方Hook类型和完整的matcher功能
56
- """
57
-
58
- def __init__(self, cli_name: str = "iflow"):
59
- """
60
- 初始化iFlow官方Hook适配器
61
-
62
- Args:
63
- cli_name: CLI工具名称,默认为"iflow"
64
- """
65
- super().__init__(cli_name)
66
-
67
- # iFlow官方Hook配置
68
- self.iflow_settings_file = os.path.expanduser("~/.iflow/settings.json")
69
- self.iflow_config_dir = os.path.expanduser("~/.iflow")
70
- self.hooks_enabled = False
71
- self.hook_scripts_dir = os.path.join(self.iflow_config_dir, "hooks")
72
-
73
- # 9种官方Hook处理器
74
- self.official_hooks = {
75
- 'PreToolUse': self.handle_pre_tool_use,
76
- 'PostToolUse': self.handle_post_tool_use,
77
- 'SetUpEnvironment': self.handle_set_up_environment,
78
- 'Stop': self.handle_stop,
79
- 'SubagentStop': self.handle_subagent_stop,
80
- 'SessionStart': self.handle_session_start,
81
- 'SessionEnd': self.handle_session_end,
82
- 'UserPromptSubmit': self.handle_user_prompt_submit,
83
- 'Notification': self.handle_notification
84
- }
85
-
86
- # 统计信息
87
- self.hook_executions = {hook: 0 for hook in self.official_hooks.keys()}
88
- self.cross_cli_interceptions = 0
89
- self.processed_events: List[IFlowHookEvent] = []
90
- self.active_sessions: Dict[str, Dict] = {}
91
-
92
- # 组件
93
- self.parser = NaturalLanguageParser()
94
- self.hook_config = self._get_default_hook_config()
95
-
96
- logger.info("iFlow官方Hook适配器初始化完成")
97
-
98
- def _get_default_hook_config(self) -> Dict[str, Any]:
99
- """获取默认Hook配置"""
100
- return {
101
- "hooks": {
102
- "PreToolUse": [
103
- {
104
- "matcher": "*",
105
- "hooks": [
106
- {
107
- "type": "command",
108
- "command": "python -m src.adapters.iflow.official_hook_adapter PreToolUse",
109
- "timeout": 30
110
- }
111
- ]
112
- }
113
- ],
114
- "PostToolUse": [
115
- {
116
- "matcher": "*",
117
- "hooks": [
118
- {
119
- "type": "command",
120
- "command": "python -m src.adapters.iflow.official_hook_adapter PostToolUse",
121
- "timeout": 15
122
- }
123
- ]
124
- }
125
- ],
126
- "SetUpEnvironment": [
127
- {
128
- "hooks": [
129
- {
130
- "type": "command",
131
- "command": "python -m src.adapters.iflow.official_hook_adapter SetUpEnvironment",
132
- "timeout": 30
133
- }
134
- ]
135
- }
136
- ],
137
- "Stop": [
138
- {
139
- "hooks": [
140
- {
141
- "type": "command",
142
- "command": "python -m src.adapters.iflow.official_hook_adapter Stop",
143
- "timeout": 10
144
- }
145
- ]
146
- }
147
- ],
148
- "SubagentStop": [
149
- {
150
- "hooks": [
151
- {
152
- "type": "command",
153
- "command": "python -m src.adapters.iflow.official_hook_adapter SubagentStop",
154
- "timeout": 10
155
- }
156
- ]
157
- }
158
- ],
159
- "SessionStart": [
160
- {
161
- "matcher": "startup|resume",
162
- "hooks": [
163
- {
164
- "type": "command",
165
- "command": "python -m src.adapters.iflow.official_hook_adapter SessionStart",
166
- "timeout": 15
167
- }
168
- ]
169
- }
170
- ],
171
- "SessionEnd": [
172
- {
173
- "hooks": [
174
- {
175
- "type": "command",
176
- "command": "python -m src.adapters.iflow.official_hook_adapter SessionEnd",
177
- "timeout": 15
178
- }
179
- ]
180
- }
181
- ],
182
- "UserPromptSubmit": [
183
- {
184
- "matcher": ".*",
185
- "hooks": [
186
- {
187
- "type": "command",
188
- "command": "python -m src.adapters.iflow.official_hook_adapter UserPromptSubmit",
189
- "timeout": 30
190
- }
191
- ]
192
- }
193
- ],
194
- "Notification": [
195
- {
196
- "matcher": ".*",
197
- "hooks": [
198
- {
199
- "type": "command",
200
- "command": "python -m src.adapters.iflow.official_hook_adapter Notification",
201
- "timeout": 10
202
- }
203
- ]
204
- }
205
- ]
206
- }
207
- }
208
-
209
- async def initialize(self) -> bool:
210
- """
211
- 初始化适配器
212
-
213
- Returns:
214
- bool: 初始化是否成功
215
- """
216
- try:
217
- logger.info("开始初始化iFlow官方Hook适配器...")
218
-
219
- # 1. 检查iFlow CLI环境
220
- if not self._check_iflow_environment():
221
- logger.error("iFlow CLI环境检查失败")
222
- return False
223
-
224
- # 2. 创建Hook目录
225
- await self._ensure_hook_directories()
226
-
227
- # 3. 注册Hook配置到iFlow设置
228
- if not await self._register_iflow_hooks():
229
- logger.error("Hook注册失败")
230
- return False
231
-
232
- # 4. 创建Hook脚本
233
- if not await self._create_hook_scripts():
234
- logger.error("Hook脚本创建失败")
235
- return False
236
-
237
- # 5. 初始化协作系统
238
- await self._initialize_collaboration_system()
239
-
240
- self.hooks_enabled = True
241
- logger.info("iFlow官方Hook适配器初始化成功")
242
- return True
243
-
244
- except Exception as e:
245
- logger.error(f"初始化iFlow官方Hook适配器失败: {e}")
246
- self.record_error()
247
- return False
248
-
249
- def _check_iflow_environment(self) -> bool:
250
- """
251
- 检查iFlow CLI环境
252
-
253
- Returns:
254
- bool: 环境是否可用
255
- """
256
- try:
257
- # 检查iFlow CLI命令
258
- result = subprocess.run(
259
- ['iflow', '--version'],
260
- capture_output=True,
261
- text=True,
262
- timeout=5
263
- )
264
-
265
- if result.returncode == 0:
266
- logger.info(f"检测到iFlow CLI: {result.stdout.strip()}")
267
- return True
268
- else:
269
- logger.warning("iFlow CLI不可用")
270
- return False
271
-
272
- except (subprocess.TimeoutExpired, FileNotFoundError):
273
- logger.warning("iFlow CLI环境检查失败")
274
- return True # 开发环境中继续
275
-
276
- async def _ensure_hook_directories(self) -> None:
277
- """确保Hook目录存在"""
278
- directories = [
279
- self.iflow_config_dir,
280
- self.hooks_scripts_dir,
281
- os.path.join(self.iflow_config_dir, "logs")
282
- ]
283
-
284
- for directory in directories:
285
- os.makedirs(directory, exist_ok=True)
286
-
287
- async def _register_iflow_hooks(self) -> bool:
288
- """
289
- 注册Hook配置到iFlow设置
290
-
291
- Returns:
292
- bool: 注册是否成功
293
- """
294
- try:
295
- # 读取现有设置
296
- existing_settings = {}
297
- if os.path.exists(self.iflow_settings_file):
298
- with open(self.iflow_settings_file, 'r', encoding='utf-8') as f:
299
- existing_settings = json.load(f)
300
-
301
- # 合并Hook配置
302
- if 'hooks' not in existing_settings:
303
- existing_settings['hooks'] = {}
304
-
305
- # 合并我们的Hook配置
306
- for hook_type, hook_config in self.hook_config['hooks'].items():
307
- if hook_type not in existing_settings['hooks']:
308
- existing_settings['hooks'][hook_type] = []
309
- existing_settings['hooks'][hook_type].extend(hook_config)
310
-
311
- # 保存设置
312
- with open(self.iflow_settings_file, 'w', encoding='utf-8') as f:
313
- json.dump(existing_settings, f, indent=2, ensure_ascii=False)
314
-
315
- logger.info(f"Hook配置已注册到: {self.iflow_settings_file}")
316
- return True
317
-
318
- except Exception as e:
319
- logger.error(f"注册Hook配置失败: {e}")
320
- return False
321
-
322
- async def _create_hook_scripts(self) -> bool:
323
- """
324
- 创建Hook脚本
325
-
326
- Returns:
327
- bool: 创建是否成功
328
- """
329
- try:
330
- # 创建主Hook脚本
331
- hook_script_content = '''#!/usr/bin/env python3
332
- """
333
- iFlow CLI Hook执行脚本
334
- """
335
- import sys
336
- import json
337
- import asyncio
338
- from pathlib import Path
339
-
340
- # 添加项目路径
341
- project_root = Path(__file__).parent.parent.parent.parent
342
- sys.path.insert(0, str(project_root))
343
-
344
- from src.adapters.iflow.official_hook_adapter import IFlowOfficialHookAdapter
345
-
346
- async def main():
347
- """主函数"""
348
- if len(sys.argv) < 2:
349
- print("Usage: hook_script.py <hook_type>", file=sys.stderr)
350
- sys.exit(1)
351
-
352
- hook_type = sys.argv[1]
353
- adapter = IFlowOfficialHookAdapter()
354
-
355
- # 读取stdin数据(iFlow通过stdin传递Hook数据)
356
- try:
357
- input_data = json.loads(sys.stdin.read())
358
- except:
359
- input_data = {}
360
-
361
- # 执行对应的Hook处理器
362
- try:
363
- result = await adapter.execute_hook_from_command(hook_type, input_data)
364
- if result:
365
- print(result)
366
- except Exception as e:
367
- print(f"Hook执行错误: {e}", file=sys.stderr)
368
- sys.exit(1)
369
-
370
- if __name__ == "__main__":
371
- asyncio.run(main())
372
- '''
373
-
374
- hook_script_path = os.path.join(self.hooks_scripts_dir, "hook_handler.py")
375
- with open(hook_script_path, 'w', encoding='utf-8') as f:
376
- f.write(hook_script_content)
377
-
378
- # 设置执行权限
379
- os.chmod(hook_script_path, 0o755)
380
-
381
- logger.info(f"Hook脚本已创建: {hook_script_path}")
382
- return True
383
-
384
- except Exception as e:
385
- logger.error(f"创建Hook脚本失败: {e}")
386
- return False
387
-
388
- async def _initialize_collaboration_system(self) -> None:
389
- """初始化协作系统"""
390
- self.active_sessions = {}
391
- logger.info("协作系统初始化完成")
392
-
393
- # ==================== 官方Hook处理器 ====================
394
-
395
- async def handle_pre_tool_use(self, event_data: Dict[str, Any]) -> Optional[str]:
396
- """
397
- PreToolUse Hook处理器 - 工具执行前触发
398
-
399
- 用途:
400
- - 验证工具参数
401
- - 设置执行环境
402
- - 记录工具调用日志
403
- - 阻止不安全的操作
404
- - 检测跨CLI调用意图
405
-
406
- Args:
407
- event_data: Hook事件数据
408
-
409
- Returns:
410
- Optional[str]: 处理结果,返回None继续执行,返回字符串可阻止执行
411
- """
412
- try:
413
- self.hook_executions['PreToolUse'] += 1
414
-
415
- tool_name = event_data.get('tool_name', '')
416
- tool_args = event_data.get('args', [])
417
-
418
- # 记录事件
419
- event = IFlowHookEvent(
420
- hook_type='PreToolUse',
421
- matcher=tool_name,
422
- data=event_data,
423
- timestamp=datetime.now(),
424
- session_id=event_data.get('session_id', ''),
425
- tool_name=tool_name
426
- )
427
- self.processed_events.append(event)
428
-
429
- logger.debug(f"PreToolUse: {tool_name}")
430
-
431
- # 检测跨CLI调用意图
432
- cross_cli_result = await self._detect_cross_cli_in_tool_use(tool_name, tool_args)
433
- if cross_cli_result:
434
- self.cross_cli_interceptions += 1
435
- return cross_cli_result
436
-
437
- return None # 继续正常工具执行
438
-
439
- except Exception as e:
440
- logger.error(f"PreToolUse Hook处理失败: {e}")
441
- self.record_error()
442
- return None
443
-
444
- async def handle_post_tool_use(self, event_data: Dict[str, Any]) -> Optional[str]:
445
- """
446
- PostToolUse Hook处理器 - 工具执行后触发
447
-
448
- 用途:
449
- - 处理工具执行结果
450
- - 记录执行日志
451
- - 执行清理操作
452
- - 结果后处理
453
-
454
- Args:
455
- event_data: Hook事件数据
456
-
457
- Returns:
458
- Optional[str]: 处理结果
459
- """
460
- try:
461
- self.hook_executions['PostToolUse'] += 1
462
-
463
- tool_name = event_data.get('tool_name', '')
464
- tool_result = event_data.get('result', '')
465
-
466
- # 记录事件
467
- event = IFlowHookEvent(
468
- hook_type='PostToolUse',
469
- matcher=tool_name,
470
- data=event_data,
471
- timestamp=datetime.now(),
472
- session_id=event_data.get('session_id', ''),
473
- tool_name=tool_name
474
- )
475
- self.processed_events.append(event)
476
-
477
- logger.debug(f"PostToolUse: {tool_name}")
478
-
479
- # 可以在这里处理工具执行结果
480
- # 例如:格式化结果、执行后处理等
481
-
482
- return None
483
-
484
- except Exception as e:
485
- logger.error(f"PostToolUse Hook处理失败: {e}")
486
- return None
487
-
488
- async def handle_set_up_environment(self, event_data: Dict[str, Any]) -> Optional[str]:
489
- """
490
- SetUpEnvironment Hook处理器 - 环境设置时触发
491
-
492
- 用途:
493
- - 初始化会话环境
494
- - 设置环境变量
495
- - 准备跨CLI环境
496
- - 执行启动前设置
497
-
498
- Args:
499
- event_data: Hook事件数据
500
-
501
- Returns:
502
- Optional[str]: 处理结果
503
- """
504
- try:
505
- self.hook_executions['SetUpEnvironment'] += 1
506
-
507
- session_id = event_data.get('session_id', 'default')
508
-
509
- # 记录事件
510
- event = IFlowHookEvent(
511
- hook_type='SetUpEnvironment',
512
- matcher=None,
513
- data=event_data,
514
- timestamp=datetime.now(),
515
- session_id=session_id
516
- )
517
- self.processed_events.append(event)
518
-
519
- logger.info("SetUpEnvironment: 初始化跨CLI环境")
520
-
521
- # 初始化会话的跨CLI环境
522
- self.active_sessions[session_id] = {
523
- 'start_time': datetime.now(),
524
- 'cross_cli_calls': 0,
525
- 'environment': 'ready'
526
- }
527
-
528
- return None
529
-
530
- except Exception as e:
531
- logger.error(f"SetUpEnvironment Hook处理失败: {e}")
532
- return None
533
-
534
- async def handle_stop(self, event_data: Dict[str, Any]) -> Optional[str]:
535
- """
536
- Stop Hook处理器 - 主会话结束时触发
537
-
538
- 用途:
539
- - 清理会话资源
540
- - 记录会话总结
541
- - 执行清理操作
542
- - 保存会话状态
543
-
544
- Args:
545
- event_data: Hook事件数据
546
-
547
- Returns:
548
- Optional[str]: 处理结果
549
- """
550
- try:
551
- self.hook_executions['Stop'] += 1
552
-
553
- session_id = event_data.get('session_id', '')
554
-
555
- # 记录事件
556
- event = IFlowHookEvent(
557
- hook_type='Stop',
558
- matcher=None,
559
- data=event_data,
560
- timestamp=datetime.now(),
561
- session_id=session_id
562
- )
563
- self.processed_events.append(event)
564
-
565
- logger.info(f"Stop: 清理会话 {session_id}")
566
-
567
- # 清理会话资源
568
- if session_id in self.active_sessions:
569
- session_data = self.active_sessions[session_id]
570
- logger.info(f"会话统计: 跨CLI调用次数 {session_data.get('cross_cli_calls', 0)}")
571
- del self.active_sessions[session_id]
572
-
573
- return None
574
-
575
- except Exception as e:
576
- logger.error(f"Stop Hook处理失败: {e}")
577
- return None
578
-
579
- async def handle_subagent_stop(self, event_data: Dict[str, Any]) -> Optional[str]:
580
- """
581
- SubagentStop Hook处理器 - 子代理会话结束时触发
582
-
583
- 用途:
584
- - 清理子代理资源
585
- - 记录子任务执行情况
586
- - 合并子任务结果
587
- - 执行子任务后处理
588
-
589
- Args:
590
- event_data: Hook事件数据
591
-
592
- Returns:
593
- Optional[str]: 处理结果
594
- """
595
- try:
596
- self.hook_executions['SubagentStop'] += 1
597
-
598
- subagent_id = event_data.get('subagent_id', '')
599
- parent_session_id = event_data.get('parent_session_id', '')
600
-
601
- # 记录事件
602
- event = IFlowHookEvent(
603
- hook_type='SubagentStop',
604
- matcher=None,
605
- data=event_data,
606
- timestamp=datetime.now(),
607
- session_id=parent_session_id
608
- )
609
- self.processed_events.append(event)
610
-
611
- logger.debug(f"SubagentStop: 子代理 {subagent_id} 结束")
612
-
613
- return None
614
-
615
- except Exception as e:
616
- logger.error(f"SubagentStop Hook处理失败: {e}")
617
- return None
618
-
619
- async def handle_session_start(self, event_data: Dict[str, Any]) -> Optional[str]:
620
- """
621
- SessionStart Hook处理器 - 会话开始时触发
622
-
623
- 用途:
624
- - 初始化会话环境
625
- - 设置日志记录
626
- - 发送会话开始通知
627
- - 执行启动时的预处理
628
- - 支持matcher: startup|resume|clear|compress
629
-
630
- Args:
631
- event_data: Hook事件数据
632
-
633
- Returns:
634
- Optional[str]: 处理结果
635
- """
636
- try:
637
- self.hook_executions['SessionStart'] += 1
638
-
639
- session_type = event_data.get('session_type', 'startup')
640
- session_id = event_data.get('session_id', '')
641
-
642
- # 记录事件
643
- event = IFlowHookEvent(
644
- hook_type='SessionStart',
645
- matcher=session_type,
646
- data=event_data,
647
- timestamp=datetime.now(),
648
- session_id=session_id
649
- )
650
- self.processed_events.append(event)
651
-
652
- logger.info(f"SessionStart: 会话开始 ({session_type})")
653
-
654
- # 根据会话类型执行不同处理
655
- if session_type == 'startup':
656
- await self._handle_startup_session(event_data)
657
- elif session_type == 'resume':
658
- await self._handle_resume_session(event_data)
659
- elif session_type == 'clear':
660
- await self._handle_clear_session(event_data)
661
- elif session_type == 'compress':
662
- await self._handle_compress_session(event_data)
663
-
664
- return None
665
-
666
- except Exception as e:
667
- logger.error(f"SessionStart Hook处理失败: {e}")
668
- return None
669
-
670
- async def handle_session_end(self, event_data: Dict[str, Any]) -> Optional[str]:
671
- """
672
- SessionEnd Hook处理器 - 会话结束时触发
673
-
674
- 用途:
675
- - 生成会话总结
676
- - 保存会话数据
677
- - 执行清理操作
678
- - 记录会话统计
679
-
680
- Args:
681
- event_data: Hook事件数据
682
-
683
- Returns:
684
- Optional[str]: 处理结果
685
- """
686
- try:
687
- self.hook_executions['SessionEnd'] += 1
688
-
689
- session_id = event_data.get('session_id', '')
690
-
691
- # 记录事件
692
- event = IFlowHookEvent(
693
- hook_type='SessionEnd',
694
- matcher=None,
695
- data=event_data,
696
- timestamp=datetime.now(),
697
- session_id=session_id
698
- )
699
- self.processed_events.append(event)
700
-
701
- logger.info(f"SessionEnd: 会话结束 {session_id}")
702
-
703
- # 生成会话总结
704
- await self._generate_session_summary(session_id)
705
-
706
- return None
707
-
708
- except Exception as e:
709
- logger.error(f"SessionEnd Hook处理失败: {e}")
710
- return None
711
-
712
- async def handle_user_prompt_submit(self, event_data: Dict[str, Any]) -> Optional[str]:
713
- """
714
- UserPromptSubmit Hook处理器 - 用户提示词提交时触发
715
-
716
- 用途:
717
- - 检测跨CLI调用意图(核心功能)
718
- - 内容过滤和验证
719
- - 提示词预处理
720
- - 可通过返回非零退出码阻止提示词提交
721
-
722
- Args:
723
- event_data: Hook事件数据,包含prompt字段
724
-
725
- Returns:
726
- Optional[str]: 处理结果,返回非零退出码可阻止提交
727
- """
728
- try:
729
- self.hook_executions['UserPromptSubmit'] += 1
730
-
731
- user_prompt = event_data.get('prompt', '')
732
- session_id = event_data.get('session_id', '')
733
-
734
- # 记录事件
735
- event = IFlowHookEvent(
736
- hook_type='UserPromptSubmit',
737
- matcher=None, # 可以根据提示词内容进行匹配
738
- data=event_data,
739
- timestamp=datetime.now(),
740
- session_id=session_id,
741
- command=user_prompt[:100] # 截取前100字符作为命令
742
- )
743
- self.processed_events.append(event)
744
-
745
- logger.debug(f"UserPromptSubmit: {user_prompt[:50]}...")
746
-
747
- # 核心功能:检测跨CLI调用意图
748
- intent = self.parser.parse_intent(user_prompt, "iflow")
749
-
750
- if intent.is_cross_cli:
751
- # 避免自我调用
752
- if intent.target_cli != self.cli_name:
753
- # 执行跨CLI调用
754
- cross_cli_result = await self._execute_cross_cli_call(
755
- intent.target_cli,
756
- intent.task,
757
- event_data
758
- )
759
-
760
- if cross_cli_result:
761
- # 更新会话统计
762
- if session_id in self.active_sessions:
763
- self.active_sessions[session_id]['cross_cli_calls'] += 1
764
-
765
- self.cross_cli_interceptions += 1
766
-
767
- # 返回跨CLI结果,这会替换原始的用户提示词
768
- return f"[跨CLI调用结果]\n\n{cross_cli_result}\n\n[原始用户请求]\n{user_prompt}"
769
-
770
- return None # 继续正常处理用户提示词
771
-
772
- except Exception as e:
773
- logger.error(f"UserPromptSubmit Hook处理失败: {e}")
774
- self.record_error()
775
- return None
776
-
777
- async def handle_notification(self, event_data: Dict[str, Any]) -> Optional[str]:
778
- """
779
- Notification Hook处理器 - 通知发送时触发
780
-
781
- 用途:
782
- - 通知内容记录
783
- - 第三方系统集成
784
- - 通知格式转换
785
- - 自定义通知处理
786
- - 特殊行为:退出码2不阻止通知,仅将stderr显示给用户
787
-
788
- Args:
789
- event_data: Hook事件数据,包含message字段
790
-
791
- Returns:
792
- Optional[str]: 处理结果
793
- """
794
- try:
795
- self.hook_executions['Notification'] += 1
796
-
797
- notification_message = event_data.get('message', '')
798
- notification_type = event_data.get('type', 'info')
799
- session_id = event_data.get('session_id', '')
800
-
801
- # 记录事件
802
- event = IFlowHookEvent(
803
- hook_type='Notification',
804
- matcher=None, # 可以根据通知消息内容进行匹配
805
- data=event_data,
806
- timestamp=datetime.now(),
807
- session_id=session_id
808
- )
809
- self.processed_events.append(event)
810
-
811
- logger.debug(f"Notification: {notification_message[:50]}...")
812
-
813
- # 可以在这里处理通知,例如:
814
- # - 记录到日志
815
- # - 发送到外部系统
816
- # - 格式转换等
817
-
818
- return None
819
-
820
- except Exception as e:
821
- logger.error(f"Notification Hook处理失败: {e}")
822
- return None
823
-
824
- # ==================== 跨CLI功能 ====================
825
-
826
- async def _detect_cross_cli_in_tool_use(self, tool_name: str, tool_args: List[Any]) -> Optional[str]:
827
- """
828
- 在工具使用中检测跨CLI调用
829
-
830
- Args:
831
- tool_name: 工具名称
832
- tool_args: 工具参数
833
-
834
- Returns:
835
- Optional[str]: 跨CLI调用结果
836
- """
837
- try:
838
- # 将工具参数转换为文本进行分析
839
- args_text = ' '.join(str(arg) for arg in tool_args)
840
- full_command = f"{tool_name} {args_text}"
841
-
842
- # 解析跨CLI意图
843
- intent = self.parser.parse_intent(full_command, "iflow")
844
-
845
- if intent.is_cross_cli and intent.target_cli != self.cli_name:
846
- # 执行跨CLI调用
847
- return await self._execute_cross_cli_call(
848
- intent.target_cli,
849
- intent.task,
850
- {"tool_name": tool_name, "args": tool_args}
851
- )
852
-
853
- return None
854
-
855
- except Exception as e:
856
- logger.error(f"检测工具使用跨CLI调用失败: {e}")
857
- return None
858
-
859
- async def _execute_cross_cli_call(
860
- self,
861
- target_cli: str,
862
- task: str,
863
- context: Dict[str, Any]
864
- ) -> Optional[str]:
865
- """
866
- 执行跨CLI调用
867
-
868
- Args:
869
- target_cli: 目标CLI工具
870
- task: 要执行的任务
871
- context: 执行上下文
872
-
873
- Returns:
874
- Optional[str]: 执行结果
875
- """
876
- try:
877
- logger.info(f"执行跨CLI调用: {target_cli} -> {task}")
878
-
879
- # 获取目标CLI适配器
880
- from ...core.base_adapter import get_cross_cli_adapter
881
- target_adapter = get_cross_cli_adapter(target_cli)
882
-
883
- if not target_adapter:
884
- logger.warning(f"目标CLI适配器不可用: {target_cli}")
885
- return self._format_error_result(target_cli, task, f"目标CLI工具 '{target_cli}' 不可用")
886
-
887
- if not target_adapter.is_available():
888
- logger.warning(f"目标CLI工具不可用: {target_cli}")
889
- return self._format_error_result(target_cli, task, f"目标CLI工具 '{target_cli}' 当前不可用")
890
-
891
- # 构建执行上下文
892
- execution_context = {
893
- 'source_cli': self.cli_name,
894
- 'target_cli': target_cli,
895
- 'original_task': task,
896
- 'hook_context': context,
897
- 'session_id': context.get('session_id', ''),
898
- 'timestamp': datetime.now().isoformat()
899
- }
900
-
901
- # 执行任务
902
- result = await target_adapter.execute_task(task, execution_context)
903
-
904
- # 记录成功的跨CLI调用
905
- self.processed_requests.append({
906
- 'type': 'cross_cli_execution',
907
- 'target_cli': target_cli,
908
- 'task': task,
909
- 'success': True,
910
- 'result_length': len(result),
911
- 'timestamp': datetime.now().isoformat()
912
- })
913
-
914
- # 格式化结果
915
- formatted_result = self._format_success_result(target_cli, task, result)
916
-
917
- logger.info(f"跨CLI调用成功: {target_cli}")
918
- return formatted_result
919
-
920
- except Exception as e:
921
- logger.error(f"跨CLI调用失败: {target_cli}, {e}")
922
- self.record_error()
923
-
924
- self.processed_requests.append({
925
- 'type': 'cross_cli_execution',
926
- 'target_cli': target_cli,
927
- 'task': task,
928
- 'success': False,
929
- 'error': str(e),
930
- 'timestamp': datetime.now().isoformat()
931
- })
932
-
933
- return self._format_error_result(target_cli, task, str(e))
934
-
935
- # ==================== 会话处理方法 ====================
936
-
937
- async def _handle_startup_session(self, event_data: Dict[str, Any]) -> None:
938
- """处理新启动会话"""
939
- session_id = event_data.get('session_id', '')
940
- self.active_sessions[session_id] = {
941
- 'start_time': datetime.now(),
942
- 'session_type': 'startup',
943
- 'cross_cli_calls': 0,
944
- 'environment': 'initialized'
945
- }
946
-
947
- async def _handle_resume_session(self, event_data: Dict[str, Any]) -> None:
948
- """处理恢复会话"""
949
- session_id = event_data.get('session_id', '')
950
- if session_id not in self.active_sessions:
951
- self.active_sessions[session_id] = {
952
- 'start_time': datetime.now(),
953
- 'session_type': 'resume',
954
- 'cross_cli_calls': 0,
955
- 'environment': 'resumed'
956
- }
957
-
958
- async def _handle_clear_session(self, event_data: Dict[str, Any]) -> None:
959
- """处理清理会话"""
960
- session_id = event_data.get('session_id', '')
961
- # 清理会话状态但保留基础信息
962
- if session_id in self.active_sessions:
963
- self.active_sessions[session_id].update({
964
- 'session_type': 'cleared',
965
- 'cross_cli_calls': 0,
966
- 'environment': 'cleared'
967
- })
968
-
969
- async def _handle_compress_session(self, event_data: Dict[str, Any]) -> None:
970
- """处理压缩会话"""
971
- session_id = event_data.get('session_id', '')
972
- if session_id in self.active_sessions:
973
- self.active_sessions[session_id]['session_type'] = 'compressed'
974
-
975
- async def _generate_session_summary(self, session_id: str) -> None:
976
- """生成会话总结"""
977
- if session_id in self.active_sessions:
978
- session_data = self.active_sessions[session_id]
979
- summary = {
980
- 'session_id': session_id,
981
- 'session_type': session_data.get('session_type', 'unknown'),
982
- 'start_time': session_data.get('start_time'),
983
- 'end_time': datetime.now(),
984
- 'cross_cli_calls': session_data.get('cross_cli_calls', 0),
985
- 'total_hook_executions': sum(self.hook_executions.values())
986
- }
987
-
988
- logger.info(f"会话总结: {summary}")
989
-
990
- # 可以将会话总结保存到文件或发送到外部系统
991
-
992
- # ==================== 结果格式化 ====================
993
-
994
- def _format_success_result(self, target_cli: str, task: str, result: str) -> str:
995
- """
996
- 格式化成功的跨CLI调用结果
997
-
998
- Args:
999
- target_cli: 目标CLI工具
1000
- task: 原始任务
1001
- result: 执行结果
1002
-
1003
- Returns:
1004
- str: 格式化的结果
1005
- """
1006
- return f"""## 🔗 跨CLI调用结果 (iFlow Hook)
1007
-
1008
- **源工具**: iFlow CLI
1009
- **目标工具**: {target_cli.upper()}
1010
- **原始任务**: {task}
1011
- **执行时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
1012
-
1013
- ---
1014
-
1015
- {result}
1016
-
1017
- ---
1018
-
1019
- *此结果由跨CLI集成系统通过iFlow CLI官方Hook提供*"""
1020
-
1021
- def _format_error_result(self, target_cli: str, task: str, error_message: str) -> str:
1022
- """
1023
- 格式化错误的跨CLI调用结果
1024
-
1025
- Args:
1026
- target_cli: 目标CLI工具
1027
- task: 原始任务
1028
- error_message: 错误信息
1029
-
1030
- Returns:
1031
- str: 格式化的错误结果
1032
- """
1033
- return f"""## ❌ 跨CLI调用失败
1034
-
1035
- **源工具**: iFlow CLI
1036
- **目标工具**: {target_cli.upper()}
1037
- **原始任务**: {task}
1038
- **错误信息**: {error_message}
1039
- **失败时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
1040
-
1041
- 请检查目标CLI工具是否正确安装和配置。
1042
-
1043
- ---
1044
-
1045
- *此错误由跨CLI集成系统报告*"""
1046
-
1047
- # ==================== 命令行执行接口 ====================
1048
-
1049
- async def execute_hook_from_command(self, hook_type: str, input_data: Dict[str, Any]) -> Optional[str]:
1050
- """
1051
- 从命令行执行Hook(用于iFlow Hook脚本调用)
1052
-
1053
- Args:
1054
- hook_type: Hook类型
1055
- input_data: 输入数据
1056
-
1057
- Returns:
1058
- Optional[str]: 执行结果
1059
- """
1060
- try:
1061
- if hook_type not in self.official_hooks:
1062
- logger.error(f"未知的Hook类型: {hook_type}")
1063
- return None
1064
-
1065
- hook_handler = self.official_hooks[hook_type]
1066
- return await hook_handler(input_data)
1067
-
1068
- except Exception as e:
1069
- logger.error(f"从命令行执行Hook失败: {hook_type}, {e}")
1070
- return None
1071
-
1072
- # ==================== 基础接口实现 ====================
1073
-
1074
- def is_available(self) -> bool:
1075
- """
1076
- 检查适配器是否可用
1077
-
1078
- Returns:
1079
- bool: 是否可用
1080
- """
1081
- return (
1082
- self.hooks_enabled and
1083
- self._check_iflow_environment() and
1084
- len(self.official_hooks) > 0
1085
- )
1086
-
1087
- async def execute_task(self, task: str, context: Dict[str, Any]) -> str:
1088
- """
1089
- 执行跨CLI任务 - iFlow适配器的具体实现
1090
-
1091
- Args:
1092
- task: 要执行的任务描述
1093
- context: 执行上下文信息
1094
-
1095
- Returns:
1096
- str: 任务执行结果
1097
- """
1098
- try:
1099
- # 直接使用UserPromptSubmit Hook的逻辑
1100
- event_data = {
1101
- 'prompt': task,
1102
- 'session_id': context.get('session_id', ''),
1103
- **context
1104
- }
1105
-
1106
- result = await self.handle_user_prompt_submit(event_data)
1107
-
1108
- if result:
1109
- # 提取跨CLI调用结果部分
1110
- if "[跨CLI调用结果]" in result:
1111
- # 分离跨CLI结果和原始请求
1112
- parts = result.split("[原始用户请求]")
1113
- if len(parts) > 1:
1114
- return parts[0].replace("[跨CLI调用结果]\n\n", "").strip()
1115
- return result
1116
-
1117
- return f"iFlow官方Hook适配器处理: {task}"
1118
-
1119
- except Exception as e:
1120
- logger.error(f"执行任务失败: {task}, 错误: {e}")
1121
- self.record_error()
1122
- return f"任务执行失败: {str(e)}"
1123
-
1124
- async def health_check(self) -> Dict[str, Any]:
1125
- """
1126
- 健康检查
1127
-
1128
- Returns:
1129
- Dict[str, Any]: 健康状态
1130
- """
1131
- base_health = await super().health_check()
1132
-
1133
- iflow_health = {
1134
- 'hooks_enabled': self.hooks_enabled,
1135
- 'hook_executions': self.hook_executions.copy(),
1136
- 'cross_cli_interceptions': self.cross_cli_interceptions,
1137
- 'processed_events_count': len(self.processed_events),
1138
- 'active_sessions_count': len(self.active_sessions),
1139
- 'iflow_settings_file': self.iflow_settings_file,
1140
- 'iflow_settings_exists': os.path.exists(self.iflow_settings_file),
1141
- 'hooks_scripts_dir': self.hooks_scripts_dir,
1142
- 'supported_hooks': list(self.official_hooks.keys())
1143
- }
1144
-
1145
- # 检查环境
1146
- try:
1147
- iflow_health['iflow_environment'] = self._check_iflow_environment()
1148
- except Exception as e:
1149
- iflow_health['iflow_environment_error'] = str(e)
1150
-
1151
- # 合并基础健康信息
1152
- base_health.update(iflow_health)
1153
- return base_health
1154
-
1155
- def get_statistics(self) -> Dict[str, Any]:
1156
- """
1157
- 获取适配器统计信息
1158
-
1159
- Returns:
1160
- Dict[str, Any]: 统计信息
1161
- """
1162
- base_stats = super().get_statistics()
1163
-
1164
- iflow_stats = {
1165
- 'hooks_enabled': self.hooks_enabled,
1166
- 'hook_executions': self.hook_executions.copy(),
1167
- 'cross_cli_interceptions': self.cross_cli_interceptions,
1168
- 'processed_events_count': len(self.processed_events),
1169
- 'active_sessions_count': len(self.active_sessions),
1170
- 'total_hook_calls': sum(self.hook_executions.values()),
1171
- 'supported_hooks': list(self.official_hooks.keys()),
1172
- 'iflow_settings_file': self.iflow_settings_file
1173
- }
1174
-
1175
- base_stats.update(iflow_stats)
1176
- return base_stats
1177
-
1178
- async def cleanup(self) -> bool:
1179
- """
1180
- 清理适配器资源
1181
-
1182
- Returns:
1183
- bool: 清理是否成功
1184
- """
1185
- try:
1186
- # 清理统计信息
1187
- self.processed_events.clear()
1188
- self.active_sessions.clear()
1189
- self.hook_executions = {hook: 0 for hook in self.official_hooks.keys()}
1190
-
1191
- logger.info("iFlow官方Hook适配器清理完成")
1192
- return True
1193
-
1194
- except Exception as e:
1195
- logger.error(f"清理iFlow官方Hook适配器失败: {e}")
1196
- return False
1197
-
1198
-
1199
- # 创建全局适配器实例
1200
- _global_adapter: Optional[IFlowOfficialHookAdapter] = None
1201
-
1202
-
1203
- def get_iflow_official_hook_adapter() -> IFlowOfficialHookAdapter:
1204
- """
1205
- 获取iFlow官方Hook适配器实例
1206
-
1207
- Returns:
1208
- IFlowOfficialHookAdapter: 适配器实例
1209
- """
1210
- global _global_adapter
1211
- if _global_adapter is None:
1212
- _global_adapter = IFlowOfficialHookAdapter()
1213
- # 异步初始化需要在调用时进行
1214
- return _global_adapter
1215
-
1216
-
1217
- # 便捷函数
1218
- async def initialize_iflow_official_adapter() -> bool:
1219
- """
1220
- 初始化iFlow官方Hook适配器
1221
-
1222
- Returns:
1223
- bool: 初始化是否成功
1224
- """
1225
- adapter = get_iflow_official_hook_adapter()
1226
- return await adapter.initialize()
1227
-
1228
-
1229
- def is_iflow_official_adapter_available() -> bool:
1230
- """
1231
- 检查iFlow官方Hook适配器是否可用
1232
-
1233
- Returns:
1234
- bool: 是否可用
1235
- """
1236
- adapter = get_iflow_official_hook_adapter()
1237
- return adapter.is_available()
1238
-
1239
-
1240
- if __name__ == "__main__":
1241
- import asyncio
1242
- import sys
1243
- import json
1244
-
1245
- async def main():
1246
- """命令行入口 - 用于iFlow Hook脚本调用"""
1247
- if len(sys.argv) < 2:
1248
- print("Usage: python official_hook_adapter.py <hook_type>", file=sys.stderr)
1249
- sys.exit(1)
1250
-
1251
- hook_type = sys.argv[1]
1252
-
1253
- # 读取stdin数据
1254
- try:
1255
- input_data = json.loads(sys.stdin.read())
1256
- except:
1257
- input_data = {}
1258
-
1259
- # 执行Hook
1260
- adapter = IFlowOfficialHookAdapter()
1261
- result = await adapter.execute_hook_from_command(hook_type, input_data)
1262
-
1263
- if result:
1264
- print(result)
1265
-
1266
- # 返回适当的退出码
1267
- if result and "失败" in result:
1268
- sys.exit(1)
1269
- else:
1270
- sys.exit(0)
1271
-
1272
- asyncio.run(main())