monoco-toolkit 0.3.12__py3-none-any.whl → 0.4.0__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.
- monoco/core/automation/__init__.py +0 -11
- monoco/core/automation/handlers.py +108 -26
- monoco/core/config.py +28 -10
- monoco/core/daemon/__init__.py +5 -0
- monoco/core/daemon/pid.py +290 -0
- monoco/core/injection.py +86 -8
- monoco/core/integrations.py +0 -24
- monoco/core/router/__init__.py +1 -39
- monoco/core/router/action.py +3 -142
- monoco/core/scheduler/events.py +28 -2
- monoco/core/setup.py +9 -0
- monoco/core/sync.py +199 -4
- monoco/core/watcher/__init__.py +6 -0
- monoco/core/watcher/base.py +18 -1
- monoco/core/watcher/im.py +460 -0
- monoco/core/watcher/memo.py +40 -48
- monoco/daemon/app.py +3 -60
- monoco/daemon/commands.py +459 -25
- monoco/daemon/scheduler.py +1 -16
- monoco/daemon/services.py +15 -0
- monoco/features/agent/resources/en/AGENTS.md +14 -14
- monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/hooks/__init__.py +61 -6
- monoco/features/hooks/commands.py +281 -271
- monoco/features/hooks/dispatchers/__init__.py +23 -0
- monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
- monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
- monoco/features/hooks/manager.py +357 -0
- monoco/features/hooks/models.py +262 -0
- monoco/features/hooks/parser.py +322 -0
- monoco/features/hooks/universal_interceptor.py +503 -0
- monoco/features/im/__init__.py +67 -0
- monoco/features/im/core.py +782 -0
- monoco/features/im/models.py +311 -0
- monoco/features/issue/commands.py +65 -50
- monoco/features/issue/core.py +199 -99
- monoco/features/issue/domain_commands.py +0 -19
- monoco/features/issue/resources/en/AGENTS.md +17 -122
- monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
- monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
- monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
- monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
- monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
- monoco/features/issue/resources/zh/AGENTS.md +18 -123
- monoco/features/memo/cli.py +15 -64
- monoco/features/memo/core.py +6 -34
- monoco/features/memo/models.py +24 -15
- monoco/features/memo/resources/en/AGENTS.md +31 -0
- monoco/features/memo/resources/zh/AGENTS.md +28 -5
- monoco/main.py +5 -3
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
- monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
- monoco/core/automation/config.py +0 -338
- monoco/core/execution.py +0 -67
- monoco/core/executor/__init__.py +0 -38
- monoco/core/executor/agent_action.py +0 -254
- monoco/core/executor/git_action.py +0 -303
- monoco/core/executor/im_action.py +0 -309
- monoco/core/executor/pytest_action.py +0 -218
- monoco/core/router/router.py +0 -392
- monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
- monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
- monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
- monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
- monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
- monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
- monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
- monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
- monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
- monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
- monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
- monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
- monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/hooks/adapter.py +0 -67
- monoco/features/hooks/core.py +0 -441
- monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
- monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco_toolkit-0.3.12.dist-info/RECORD +0 -202
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/licenses/LICENSE +0 -0
monoco/core/sync.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import typer
|
|
2
|
+
import subprocess
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import Optional, List
|
|
4
5
|
from monoco.core.registry import FeatureRegistry
|
|
@@ -21,8 +22,10 @@ def _get_targets(root: Path, config, cli_target: Optional[Path]) -> List[Path]:
|
|
|
21
22
|
return targets
|
|
22
23
|
|
|
23
24
|
# 2. Registry Defaults (Dynamic Detection)
|
|
25
|
+
# We now default to ALL integrations instead of auto-detecting
|
|
26
|
+
# because we want to enable all agents by default.
|
|
24
27
|
integrations = get_active_integrations(
|
|
25
|
-
root, config_overrides=None, auto_detect=
|
|
28
|
+
root, config_overrides=None, auto_detect=False
|
|
26
29
|
)
|
|
27
30
|
|
|
28
31
|
if integrations:
|
|
@@ -133,8 +136,9 @@ def sync_command(
|
|
|
133
136
|
skill_manager = SkillManager(root, active_features)
|
|
134
137
|
|
|
135
138
|
# Get active integrations
|
|
139
|
+
# Disable auto-detect to distribute to all supported frameworks
|
|
136
140
|
integrations = get_active_integrations(
|
|
137
|
-
root, config_overrides=None, auto_detect=
|
|
141
|
+
root, config_overrides=None, auto_detect=False
|
|
138
142
|
)
|
|
139
143
|
|
|
140
144
|
if integrations:
|
|
@@ -165,7 +169,7 @@ def sync_command(
|
|
|
165
169
|
# 5. Distribute Workflows (if --workflows flag is set)
|
|
166
170
|
if workflows:
|
|
167
171
|
console.print("[bold blue]Distributing Flow Skills as Workflows...[/bold blue]")
|
|
168
|
-
|
|
172
|
+
|
|
169
173
|
try:
|
|
170
174
|
workflow_results = skill_manager.distribute_workflows(force=False, lang=skill_lang)
|
|
171
175
|
success_count = sum(1 for v in workflow_results.values() if v)
|
|
@@ -182,6 +186,103 @@ def sync_command(
|
|
|
182
186
|
f"[red] Failed to distribute workflows: {e}[/red]"
|
|
183
187
|
)
|
|
184
188
|
|
|
189
|
+
# 6. Sync Universal Hooks (Git & Agent)
|
|
190
|
+
console.print("[bold blue]Synchronizing Universal Hooks...[/bold blue]")
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
from monoco.features.hooks import UniversalHookManager, HookType
|
|
194
|
+
from monoco.features.hooks.dispatchers import (
|
|
195
|
+
GitHookDispatcher,
|
|
196
|
+
ClaudeCodeDispatcher,
|
|
197
|
+
GeminiDispatcher,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
hooks_manager = UniversalHookManager()
|
|
201
|
+
|
|
202
|
+
# Register Dispatchers
|
|
203
|
+
git_dispatcher = GitHookDispatcher()
|
|
204
|
+
hooks_manager.register_dispatcher(HookType.GIT, git_dispatcher)
|
|
205
|
+
|
|
206
|
+
# Register Agent Dispatchers for active platforms
|
|
207
|
+
# NOTE: Only Claude Code and Gemini CLI are officially supported
|
|
208
|
+
agent_dispatchers = {
|
|
209
|
+
"claude-code": ClaudeCodeDispatcher(),
|
|
210
|
+
"gemini-cli": GeminiDispatcher(),
|
|
211
|
+
}
|
|
212
|
+
for dispatcher in agent_dispatchers.values():
|
|
213
|
+
hooks_manager.register_dispatcher(HookType.AGENT, dispatcher)
|
|
214
|
+
|
|
215
|
+
# 6.1 Scan for hooks
|
|
216
|
+
all_hooks = []
|
|
217
|
+
|
|
218
|
+
# 6.1.1 Scan builtin hooks from hooks feature
|
|
219
|
+
try:
|
|
220
|
+
from monoco.features import hooks as hooks_module
|
|
221
|
+
hooks_feature_dir = Path(hooks_module.__file__).parent
|
|
222
|
+
builtin_hooks_dir = hooks_feature_dir / "resources" / "hooks"
|
|
223
|
+
if builtin_hooks_dir.exists():
|
|
224
|
+
groups = hooks_manager.scan(builtin_hooks_dir)
|
|
225
|
+
for group in groups.values():
|
|
226
|
+
all_hooks.extend(group.hooks)
|
|
227
|
+
except Exception as e:
|
|
228
|
+
console.print(f"[dim] No builtin hooks found: {e}[/dim]")
|
|
229
|
+
|
|
230
|
+
# 6.1.2 Scan for hooks in all active features
|
|
231
|
+
for feature in active_features:
|
|
232
|
+
if feature.name == "hooks":
|
|
233
|
+
continue # Already scanned
|
|
234
|
+
|
|
235
|
+
import importlib
|
|
236
|
+
try:
|
|
237
|
+
# Use the module where the feature class is defined (usually adapter.py)
|
|
238
|
+
module_name = feature.__class__.__module__
|
|
239
|
+
module = importlib.import_module(module_name)
|
|
240
|
+
|
|
241
|
+
if hasattr(module, "__file__") and module.__file__:
|
|
242
|
+
# feature_dir is the directory containing adapter.py
|
|
243
|
+
feature_dir = Path(module.__file__).parent
|
|
244
|
+
hooks_resource_dir = feature_dir / "resources" / "hooks"
|
|
245
|
+
|
|
246
|
+
if hooks_resource_dir.exists():
|
|
247
|
+
groups = hooks_manager.scan(hooks_resource_dir)
|
|
248
|
+
for group in groups.values():
|
|
249
|
+
all_hooks.extend(group.hooks)
|
|
250
|
+
except Exception:
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
# 6.2 Sync Git Hooks
|
|
254
|
+
git_hooks = [h for h in all_hooks if h.metadata.type == HookType.GIT]
|
|
255
|
+
|
|
256
|
+
if not (root / ".git").exists():
|
|
257
|
+
console.print("[dim] Git repository not found. Initializing...[/dim]")
|
|
258
|
+
# Set global default branch to main
|
|
259
|
+
subprocess.run(["git", "config", "--global", "init.defaultBranch", "main"], check=False)
|
|
260
|
+
subprocess.run(["git", "init"], cwd=root, check=False)
|
|
261
|
+
|
|
262
|
+
git_results = git_dispatcher.sync(git_hooks, root)
|
|
263
|
+
|
|
264
|
+
git_installed = sum(1 for v in git_results.values() if v)
|
|
265
|
+
if git_installed > 0:
|
|
266
|
+
console.print(f"[green] ✓ Synchronized {git_installed} Git hooks[/green]")
|
|
267
|
+
elif git_hooks:
|
|
268
|
+
console.print("[yellow] No Git hooks were successfully synchronized[/yellow]")
|
|
269
|
+
|
|
270
|
+
# 6.3 Sync Agent Hooks using the new ACL-based dispatchers
|
|
271
|
+
agent_hooks = [h for h in all_hooks if h.metadata.type == HookType.AGENT]
|
|
272
|
+
|
|
273
|
+
for provider, dispatcher in agent_dispatchers.items():
|
|
274
|
+
provider_hooks = [h for h in agent_hooks if h.metadata.provider == provider]
|
|
275
|
+
if provider_hooks:
|
|
276
|
+
results = dispatcher.sync(provider_hooks, root)
|
|
277
|
+
success_count = sum(1 for v in results.values() if v)
|
|
278
|
+
if success_count > 0:
|
|
279
|
+
console.print(f"[green] ✓ Synchronized {success_count} agent hooks to {provider}[/green]")
|
|
280
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
console.print(f"[red] Failed to synchronize Universal Hooks: {e}[/red]")
|
|
283
|
+
import traceback
|
|
284
|
+
console.print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
285
|
+
|
|
185
286
|
# 4. Determine Targets
|
|
186
287
|
targets = _get_targets(root, config, target)
|
|
187
288
|
|
|
@@ -299,7 +400,7 @@ def uninstall_command(
|
|
|
299
400
|
|
|
300
401
|
# 3. Clean up Workflows
|
|
301
402
|
console.print("[bold blue]Cleaning up distributed workflows...[/bold blue]")
|
|
302
|
-
|
|
403
|
+
|
|
303
404
|
try:
|
|
304
405
|
removed_count = skill_manager.cleanup_workflows()
|
|
305
406
|
if removed_count > 0:
|
|
@@ -310,3 +411,97 @@ def uninstall_command(
|
|
|
310
411
|
console.print(
|
|
311
412
|
f"[red] Failed to clean workflows: {e}[/red]"
|
|
312
413
|
)
|
|
414
|
+
|
|
415
|
+
# 4. Clean up Git Hooks
|
|
416
|
+
console.print("[bold blue]Cleaning up Git Hooks...[/bold blue]")
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
from monoco.features.hooks.dispatchers import GitHookDispatcher
|
|
420
|
+
|
|
421
|
+
git_dispatcher = GitHookDispatcher()
|
|
422
|
+
installed = git_dispatcher.list_installed(root)
|
|
423
|
+
|
|
424
|
+
uninstalled = 0
|
|
425
|
+
for hook_info in installed:
|
|
426
|
+
if git_dispatcher.uninstall(hook_info["event"], root):
|
|
427
|
+
uninstalled += 1
|
|
428
|
+
|
|
429
|
+
if uninstalled > 0:
|
|
430
|
+
console.print(
|
|
431
|
+
f"[green] ✓ Removed {uninstalled} Git hooks[/green]"
|
|
432
|
+
)
|
|
433
|
+
else:
|
|
434
|
+
console.print("[dim] No Monoco Git hooks to clean up[/dim]")
|
|
435
|
+
except Exception as e:
|
|
436
|
+
console.print(f"[red] Failed to clean Git hooks: {e}[/red]")
|
|
437
|
+
|
|
438
|
+
# 5. Clean up Agent Hooks
|
|
439
|
+
console.print("[bold blue]Cleaning up Agent Hooks...[/bold blue]")
|
|
440
|
+
|
|
441
|
+
try:
|
|
442
|
+
from monoco.features.hooks.dispatchers import (
|
|
443
|
+
ClaudeCodeDispatcher,
|
|
444
|
+
GeminiDispatcher,
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
# Clean up Claude Code hooks
|
|
448
|
+
claude_dispatcher = ClaudeCodeDispatcher()
|
|
449
|
+
claude_settings = claude_dispatcher.get_settings_path(root)
|
|
450
|
+
if claude_settings and claude_settings.exists():
|
|
451
|
+
try:
|
|
452
|
+
import json
|
|
453
|
+
with open(claude_settings, "r", encoding="utf-8") as f:
|
|
454
|
+
settings = json.load(f)
|
|
455
|
+
|
|
456
|
+
if "hooks" in settings:
|
|
457
|
+
original_count = len(settings["hooks"])
|
|
458
|
+
# Remove Monoco-managed hooks
|
|
459
|
+
for event in list(settings["hooks"].keys()):
|
|
460
|
+
configs = settings["hooks"][event]
|
|
461
|
+
if isinstance(configs, list):
|
|
462
|
+
settings["hooks"][event] = [
|
|
463
|
+
c for c in configs if not c.get("_monoco_managed")
|
|
464
|
+
]
|
|
465
|
+
# Clean up empty events
|
|
466
|
+
settings["hooks"] = {
|
|
467
|
+
k: v for k, v in settings["hooks"].items() if v
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
with open(claude_settings, "w", encoding="utf-8") as f:
|
|
471
|
+
json.dump(settings, f, indent=2, ensure_ascii=False)
|
|
472
|
+
|
|
473
|
+
console.print("[green] ✓ Cleaned up Claude Code hooks[/green]")
|
|
474
|
+
except Exception as e:
|
|
475
|
+
console.print(f"[red] Failed to clean Claude Code hooks: {e}[/red]")
|
|
476
|
+
|
|
477
|
+
# Clean up Gemini CLI hooks
|
|
478
|
+
gemini_dispatcher = GeminiDispatcher()
|
|
479
|
+
gemini_settings = gemini_dispatcher.get_settings_path(root)
|
|
480
|
+
if gemini_settings and gemini_settings.exists():
|
|
481
|
+
try:
|
|
482
|
+
import json
|
|
483
|
+
with open(gemini_settings, "r", encoding="utf-8") as f:
|
|
484
|
+
settings = json.load(f)
|
|
485
|
+
|
|
486
|
+
if "hooks" in settings:
|
|
487
|
+
# Remove Monoco-managed hooks
|
|
488
|
+
for event in list(settings["hooks"].keys()):
|
|
489
|
+
configs = settings["hooks"][event]
|
|
490
|
+
if isinstance(configs, list):
|
|
491
|
+
settings["hooks"][event] = [
|
|
492
|
+
c for c in configs if not c.get("_monoco_managed")
|
|
493
|
+
]
|
|
494
|
+
# Clean up empty events
|
|
495
|
+
settings["hooks"] = {
|
|
496
|
+
k: v for k, v in settings["hooks"].items() if v
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
with open(gemini_settings, "w", encoding="utf-8") as f:
|
|
500
|
+
json.dump(settings, f, indent=2, ensure_ascii=False)
|
|
501
|
+
|
|
502
|
+
console.print("[green] ✓ Cleaned up Gemini CLI hooks[/green]")
|
|
503
|
+
except Exception as e:
|
|
504
|
+
console.print(f"[red] Failed to clean Gemini CLI hooks: {e}[/red]")
|
|
505
|
+
|
|
506
|
+
except Exception as e:
|
|
507
|
+
console.print(f"[red] Failed to clean Agent hooks: {e}[/red]")
|
monoco/core/watcher/__init__.py
CHANGED
|
@@ -35,6 +35,7 @@ from .issue import IssueWatcher, IssueFileEvent
|
|
|
35
35
|
from .memo import MemoWatcher, MemoFileEvent
|
|
36
36
|
from .task import TaskWatcher, TaskFileEvent
|
|
37
37
|
from .dropzone import DropzoneWatcher, DropzoneFileEvent
|
|
38
|
+
from .im import IMWatcher, IMFileEvent, IMInboundWatcher, IMWebhookWatcher
|
|
38
39
|
|
|
39
40
|
__all__ = [
|
|
40
41
|
# Base classes
|
|
@@ -54,4 +55,9 @@ __all__ = [
|
|
|
54
55
|
"TaskFileEvent",
|
|
55
56
|
"DropzoneWatcher",
|
|
56
57
|
"DropzoneFileEvent",
|
|
58
|
+
# IM watchers (FEAT-0167)
|
|
59
|
+
"IMWatcher",
|
|
60
|
+
"IMFileEvent",
|
|
61
|
+
"IMInboundWatcher",
|
|
62
|
+
"IMWebhookWatcher",
|
|
57
63
|
]
|
monoco/core/watcher/base.py
CHANGED
|
@@ -180,6 +180,23 @@ class FilesystemWatcher(ABC):
|
|
|
180
180
|
if callback in self._callbacks:
|
|
181
181
|
self._callbacks.remove(callback)
|
|
182
182
|
|
|
183
|
+
def _is_async_callable(self, callback: Callable) -> bool:
|
|
184
|
+
"""
|
|
185
|
+
Check if a callable is async (coroutine function or has async __call__).
|
|
186
|
+
|
|
187
|
+
This handles both:
|
|
188
|
+
- Regular async functions: async def func(): ...
|
|
189
|
+
- Callable objects with async __call__: class Handler: async def __call__(self, ...): ...
|
|
190
|
+
"""
|
|
191
|
+
# Direct check for coroutine function
|
|
192
|
+
if inspect.iscoroutinefunction(callback):
|
|
193
|
+
return True
|
|
194
|
+
# Check for callable object with async __call__ method
|
|
195
|
+
if hasattr(callback, "__call__") and not inspect.ismethod(callback):
|
|
196
|
+
if inspect.iscoroutinefunction(callback.__call__):
|
|
197
|
+
return True
|
|
198
|
+
return False
|
|
199
|
+
|
|
183
200
|
async def emit(self, event: FileEvent) -> None:
|
|
184
201
|
"""
|
|
185
202
|
Emit a file event to all registered callbacks and EventBus.
|
|
@@ -190,7 +207,7 @@ class FilesystemWatcher(ABC):
|
|
|
190
207
|
# Call local callbacks
|
|
191
208
|
for callback in self._callbacks:
|
|
192
209
|
try:
|
|
193
|
-
if
|
|
210
|
+
if self._is_async_callable(callback):
|
|
194
211
|
await callback(event)
|
|
195
212
|
else:
|
|
196
213
|
callback(event)
|