gemcode 0.4.6__tar.gz → 0.4.9__tar.gz
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.
- {gemcode-0.4.6/src/gemcode.egg-info → gemcode-0.4.9}/PKG-INFO +1 -1
- {gemcode-0.4.6 → gemcode-0.4.9}/pyproject.toml +1 -1
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/agent_mesh.py +97 -17
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/fleet_reports.py +11 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/invoke.py +1 -4
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/org_tools.py +17 -113
- {gemcode-0.4.6 → gemcode-0.4.9/src/gemcode.egg-info}/PKG-INFO +1 -1
- {gemcode-0.4.6 → gemcode-0.4.9}/LICENSE +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/MANIFEST.in +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/README.md +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/setup.cfg +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/__init__.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/__main__.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/a2a_bridge.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/agent.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/agent_habits.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/agent_intelligence.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/agent_triggers.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/audit.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/automations.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/autotune.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/checkpoints.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/cli.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/codebase_awareness.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/compaction.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/config.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/credentials.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/curated_memory.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/delegation_learning.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/dynamic_policy.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/evals/harness.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/event_bus.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/hooks.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/ide_protocol.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/ide_stdio.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/intent_classifier.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/interactions.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/kaira_client.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/kaira_daemon.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/kaira_ipc.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/kaira_job_store.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/learning.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/limits.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/multimodal_input.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/openapi_loader.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/org.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/output_styles.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/paths.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/permissions.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/policy_profile.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/pricing.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/query/config.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/query_sanitizer.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/refine.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/repl_commands.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/repl_slash.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/review_agent.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/rules.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/self_healing.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/session_store.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/session_summariser.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/skills.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/thinking.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tool_result_store.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tool_synthesis.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/__init__.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/automations_tools.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/compress_memory.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/curated_memory.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/notebook.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/notes.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/repo_map.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/skills.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/tasks.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/user_choice.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/veomem_tools.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools/web_search.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/trust.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/veomem_bridge.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/version.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/vertex.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/wal.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/web/sse_adapter.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/web/web_sse_compat.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode.egg-info/SOURCES.txt +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_add_dir.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_agent_habits.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_agent_mesh.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_autocompact.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_automations.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_capability_routing.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_checkpoint_diff_command.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_cli_init.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_compress_memory_tool.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_context_budget.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_context_warning.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_credentials.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_eval_harness_layout.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_event_bus.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_fleet_reports.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_ide_stdio_attachments.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_kaira_ipc_paths.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_kaira_scheduler.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_modality_tools.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_model_errors.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_model_routing.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_multimodal_input.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_output_styles_and_rules.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_paths.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_permissions.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_repl_commands.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_repl_slash.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_session_runtime_cache.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_skills.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_slash_commands.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_slash_completion_registry.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_thinking_config.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_token_budget.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_tools.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_web_sse_adapter.py +0 -0
- {gemcode-0.4.6 → gemcode-0.4.9}/tests/test_workspace_hints.py +0 -0
|
@@ -66,7 +66,11 @@ class AgentMesh:
|
|
|
66
66
|
self._running: dict[str, asyncio.Task] = {}
|
|
67
67
|
self._completed: list[AgentJob] = []
|
|
68
68
|
self._scheduler_task: asyncio.Task | None = None
|
|
69
|
-
self._stop
|
|
69
|
+
self._stop: asyncio.Event | None = None # Created in background loop
|
|
70
|
+
self._bg_thread: "threading.Thread | None" = None
|
|
71
|
+
self._bg_loop: asyncio.AbstractEventLoop | None = None
|
|
72
|
+
# _stop is created lazily in the background loop
|
|
73
|
+
self._stop_flag = False
|
|
70
74
|
|
|
71
75
|
# Subscribe to org.assign messages on the bus
|
|
72
76
|
self._bus.subscribe(
|
|
@@ -112,25 +116,69 @@ class AgentMesh:
|
|
|
112
116
|
return self._bus
|
|
113
117
|
|
|
114
118
|
def start(self) -> None:
|
|
115
|
-
"""Start the background
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
"""Start the mesh in a dedicated background thread with its own event loop.
|
|
120
|
+
|
|
121
|
+
This is critical: the TUI's event loop is blocked on user input most of the
|
|
122
|
+
time. If we run mesh jobs on the same loop, they can only execute during the
|
|
123
|
+
brief moments prompt_toolkit yields control — not enough for a full agent turn.
|
|
124
|
+
|
|
125
|
+
By running in a separate thread, mesh jobs execute independently. Results are
|
|
126
|
+
written to fleet_reports.jsonl and picked up on the next user turn, or printed
|
|
127
|
+
to the terminal via the notification callback.
|
|
128
|
+
"""
|
|
129
|
+
if self._bg_thread is not None and self._bg_thread.is_alive():
|
|
130
|
+
return # Already running
|
|
131
|
+
|
|
132
|
+
import threading
|
|
133
|
+
|
|
134
|
+
def _run_bg_loop():
|
|
135
|
+
"""Background thread entry: create a new event loop and run the scheduler."""
|
|
136
|
+
self._bg_loop = asyncio.new_event_loop()
|
|
137
|
+
asyncio.set_event_loop(self._bg_loop)
|
|
138
|
+
try:
|
|
139
|
+
self._bg_loop.run_until_complete(self._bg_main())
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
142
|
+
finally:
|
|
143
|
+
try:
|
|
144
|
+
self._bg_loop.close()
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
self._bg_thread = threading.Thread(target=_run_bg_loop, daemon=True, name="gemcode-mesh")
|
|
149
|
+
self._bg_thread.start()
|
|
150
|
+
|
|
151
|
+
async def _bg_main(self) -> None:
|
|
152
|
+
"""Main coroutine for the background event loop."""
|
|
153
|
+
self._stop = asyncio.Event() # Create in the correct loop
|
|
154
|
+
|
|
155
|
+
# Start all sub-systems in this loop
|
|
156
|
+
self._scheduler_task = asyncio.create_task(self._scheduler_loop())
|
|
157
|
+
|
|
119
158
|
if self._trigger_engine is not None:
|
|
120
159
|
self._trigger_engine.start()
|
|
121
|
-
# Start habit scheduler (cron/interval recurring tasks)
|
|
122
160
|
if self._habit_scheduler is not None:
|
|
123
161
|
self._habit_scheduler.start()
|
|
124
162
|
|
|
163
|
+
# Wait until stopped
|
|
164
|
+
await self._stop.wait()
|
|
165
|
+
|
|
125
166
|
def stop(self) -> None:
|
|
126
167
|
"""Stop the scheduler and trigger engine."""
|
|
127
|
-
self._stop
|
|
168
|
+
if self._stop is not None:
|
|
169
|
+
self._stop.set()
|
|
128
170
|
if self._scheduler_task and not self._scheduler_task.done():
|
|
129
171
|
self._scheduler_task.cancel()
|
|
130
172
|
if self._trigger_engine is not None:
|
|
131
173
|
self._trigger_engine.stop()
|
|
132
174
|
if self._habit_scheduler is not None:
|
|
133
175
|
self._habit_scheduler.stop()
|
|
176
|
+
# Signal the background loop to exit
|
|
177
|
+
if self._bg_loop is not None:
|
|
178
|
+
try:
|
|
179
|
+
self._bg_loop.call_soon_threadsafe(self._bg_loop.stop)
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
134
182
|
|
|
135
183
|
def enqueue(
|
|
136
184
|
self,
|
|
@@ -141,7 +189,7 @@ class AgentMesh:
|
|
|
141
189
|
member_name: str = "",
|
|
142
190
|
meta: dict[str, Any] | None = None,
|
|
143
191
|
) -> str:
|
|
144
|
-
"""Enqueue a job and return its job_id."""
|
|
192
|
+
"""Enqueue a job and return its job_id. Thread-safe."""
|
|
145
193
|
job_id = f"mesh_{uuid.uuid4().hex[:10]}"
|
|
146
194
|
self._seq += 1
|
|
147
195
|
job = AgentJob(
|
|
@@ -163,13 +211,9 @@ class AgentMesh:
|
|
|
163
211
|
payload={"job_id": job_id, "member": member_name, "priority": priority},
|
|
164
212
|
))
|
|
165
213
|
|
|
166
|
-
# Auto-start
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if self._scheduler_task is None or self._scheduler_task.done():
|
|
170
|
-
self._scheduler_task = loop.create_task(self._scheduler_loop())
|
|
171
|
-
except RuntimeError:
|
|
172
|
-
pass
|
|
214
|
+
# Auto-start the background thread if not running
|
|
215
|
+
if self._bg_thread is None or not self._bg_thread.is_alive():
|
|
216
|
+
self.start()
|
|
173
217
|
|
|
174
218
|
return job_id
|
|
175
219
|
|
|
@@ -267,7 +311,7 @@ class AgentMesh:
|
|
|
267
311
|
|
|
268
312
|
async def _scheduler_loop(self) -> None:
|
|
269
313
|
"""Continuously dequeue and run jobs."""
|
|
270
|
-
while not self._stop.is_set():
|
|
314
|
+
while self._stop is None or not self._stop.is_set():
|
|
271
315
|
try:
|
|
272
316
|
await self._sem.acquire()
|
|
273
317
|
try:
|
|
@@ -590,11 +634,47 @@ class AgentMesh:
|
|
|
590
634
|
)
|
|
591
635
|
return {"ok": True, "job_id": job_id}
|
|
592
636
|
|
|
637
|
+
async def agent_dm(to: str, message: str) -> dict:
|
|
638
|
+
"""Send a direct message to another agent via the event bus."""
|
|
639
|
+
from_name = job.member_name or 'anonymous'
|
|
640
|
+
await mesh._bus.publish(BusMessage(
|
|
641
|
+
topic='agent.dm', from_addr=from_name, to_addr=to,
|
|
642
|
+
payload={'from': from_name, 'to': to, 'message': message},
|
|
643
|
+
))
|
|
644
|
+
try:
|
|
645
|
+
from gemcode.fleet_reports import append_fleet_report
|
|
646
|
+
fleet_root = resolve_fleet_root(mesh.cfg.project_root)
|
|
647
|
+
append_fleet_report(fleet_root, topic='agent.dm', payload={
|
|
648
|
+
'from': from_name, 'to': to, 'message': message[:4000],
|
|
649
|
+
})
|
|
650
|
+
except Exception:
|
|
651
|
+
pass
|
|
652
|
+
return {'ok': True, 'sent_to': to}
|
|
653
|
+
|
|
654
|
+
async def agent_broadcast(message: str) -> dict:
|
|
655
|
+
"""Broadcast a message to all agents and the manager."""
|
|
656
|
+
from_name = job.member_name or 'anonymous'
|
|
657
|
+
await mesh._bus.publish(BusMessage(
|
|
658
|
+
topic='agent.broadcast', from_addr=from_name, to_addr='',
|
|
659
|
+
payload={'from': from_name, 'message': message},
|
|
660
|
+
))
|
|
661
|
+
try:
|
|
662
|
+
from gemcode.fleet_reports import append_fleet_report
|
|
663
|
+
fleet_root = resolve_fleet_root(mesh.cfg.project_root)
|
|
664
|
+
append_fleet_report(fleet_root, topic='agent.broadcast', payload={
|
|
665
|
+
'from': from_name, 'message': message[:4000],
|
|
666
|
+
})
|
|
667
|
+
except Exception:
|
|
668
|
+
pass
|
|
669
|
+
return {'ok': True, 'broadcast_from': from_name}
|
|
670
|
+
|
|
593
671
|
mesh_delegate.__name__ = "mesh_delegate"
|
|
594
672
|
mesh_report.__name__ = "mesh_report"
|
|
595
673
|
mesh_enqueue.__name__ = "mesh_enqueue"
|
|
674
|
+
agent_dm.__name__ = "agent_dm"
|
|
675
|
+
agent_broadcast.__name__ = "agent_broadcast"
|
|
596
676
|
|
|
597
|
-
return [mesh_delegate, mesh_report, mesh_enqueue]
|
|
677
|
+
return [mesh_delegate, mesh_report, mesh_enqueue, agent_dm, agent_broadcast]
|
|
598
678
|
|
|
599
679
|
# ── Status / Introspection ──────────────────────────────────────────────
|
|
600
680
|
|
|
@@ -273,6 +273,17 @@ def _format_record(rec: dict[str, Any]) -> str:
|
|
|
273
273
|
elif isinstance(res, str) and res.strip():
|
|
274
274
|
lines.append(f" result: {res[:8000]}")
|
|
275
275
|
|
|
276
|
+
elif topic == "agent.dm":
|
|
277
|
+
from_name = str(payload.get("from") or "")
|
|
278
|
+
to_name = str(payload.get("to") or "")
|
|
279
|
+
msg = str(payload.get("message") or "").strip()
|
|
280
|
+
lines.append(f"[agent.dm] {from_name} → {to_name}: {msg[:4000]}")
|
|
281
|
+
|
|
282
|
+
elif topic == "agent.broadcast":
|
|
283
|
+
from_name = str(payload.get("from") or "")
|
|
284
|
+
msg = str(payload.get("message") or "").strip()
|
|
285
|
+
lines.append(f"[agent.broadcast] {from_name}: {msg[:4000]}")
|
|
286
|
+
|
|
276
287
|
return "\n".join(lines)
|
|
277
288
|
|
|
278
289
|
|
|
@@ -111,10 +111,7 @@ async def run_turn(
|
|
|
111
111
|
try:
|
|
112
112
|
from gemcode.agent_mesh import ensure_mesh
|
|
113
113
|
mesh = ensure_mesh(cfg)
|
|
114
|
-
|
|
115
|
-
mesh.start()
|
|
116
|
-
except Exception:
|
|
117
|
-
pass
|
|
114
|
+
mesh.start() # Starts background thread if not already running
|
|
118
115
|
except Exception:
|
|
119
116
|
pass
|
|
120
117
|
|
|
@@ -152,34 +152,7 @@ def make_org_tools(cfg: GemCodeConfig) -> list:
|
|
|
152
152
|
pass
|
|
153
153
|
|
|
154
154
|
# ── Also try IPC if daemon is running (bonus, not required) ───────────
|
|
155
|
-
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
from gemcode.kaira_ipc import fleet_manager_ipc_path
|
|
159
|
-
|
|
160
|
-
sock = str(fleet_manager_ipc_path(fleet_root))
|
|
161
|
-
try:
|
|
162
|
-
if not Path(sock).exists():
|
|
163
|
-
return # No daemon, but that's fine — bus + fleet reports already handled it
|
|
164
|
-
except Exception:
|
|
165
|
-
return
|
|
166
|
-
|
|
167
|
-
try:
|
|
168
|
-
from gemcode.kaira_client import KairaIpcClient
|
|
169
|
-
|
|
170
|
-
c = await KairaIpcClient.connect(socket_path=str(sock))
|
|
171
|
-
try:
|
|
172
|
-
for to_addr in chain:
|
|
173
|
-
await c.publish(
|
|
174
|
-
topic="org.report",
|
|
175
|
-
to=str(to_addr or "manager"),
|
|
176
|
-
from_addr=from_addr,
|
|
177
|
-
payload=payload,
|
|
178
|
-
)
|
|
179
|
-
finally:
|
|
180
|
-
await c.close()
|
|
181
|
-
except Exception:
|
|
182
|
-
pass
|
|
155
|
+
# Removed: Kaira IPC is no longer needed. The bus + fleet reports handle everything.
|
|
183
156
|
|
|
184
157
|
def org_list() -> dict:
|
|
185
158
|
"""List available org members (workers)."""
|
|
@@ -219,7 +192,7 @@ def make_org_tools(cfg: GemCodeConfig) -> list:
|
|
|
219
192
|
return {"ok": True, "tree": org_tree(root)}
|
|
220
193
|
|
|
221
194
|
async def org_delegate(member: str, task: str, context: str = "") -> dict:
|
|
222
|
-
"""Delegate a task to an org member
|
|
195
|
+
"""Delegate a task to an org member. Uses the mesh — no daemon required."""
|
|
223
196
|
m = find_member(root, member)
|
|
224
197
|
if m is None:
|
|
225
198
|
return {"ok": False, "error": f"unknown member: {member}"}
|
|
@@ -229,109 +202,40 @@ def make_org_tools(cfg: GemCodeConfig) -> list:
|
|
|
229
202
|
if not task:
|
|
230
203
|
return {"ok": False, "error": "missing task"}
|
|
231
204
|
|
|
232
|
-
#
|
|
233
|
-
# The mesh runs real ADK agents in-process with their own sessions.
|
|
234
|
-
# This is the PRIMARY path — no daemon required.
|
|
205
|
+
# Primary path: Agent Mesh (always available, runs in background thread)
|
|
235
206
|
mesh = _get_mesh(cfg)
|
|
236
207
|
if mesh is not None:
|
|
237
208
|
try:
|
|
238
|
-
# For kaira_workers, run async (non-blocking) by default
|
|
239
|
-
# For subagents, run sync (blocking) to return result immediately
|
|
240
209
|
wait = (m.kind != "kaira_worker")
|
|
241
210
|
result = await mesh.delegate_to_member(
|
|
242
|
-
member=m,
|
|
243
|
-
task=task,
|
|
244
|
-
context=ctx,
|
|
245
|
-
priority=0,
|
|
246
|
-
wait=wait,
|
|
211
|
+
member=m, task=task, context=ctx, priority=0, wait=wait,
|
|
247
212
|
)
|
|
248
213
|
if result.get("ok"):
|
|
249
214
|
return {"ok": True, "delegated_to": m.to_dict(), **result}
|
|
250
|
-
# If mesh delegation failed, fall through to other strategies
|
|
251
215
|
except Exception:
|
|
252
216
|
pass
|
|
253
217
|
|
|
254
|
-
#
|
|
255
|
-
if m.kind == "kaira_worker":
|
|
256
|
-
try:
|
|
257
|
-
from gemcode.kaira_client import KairaIpcClient
|
|
258
|
-
fleet_root = resolve_fleet_root(getattr(cfg, "project_root", Path.cwd()))
|
|
259
|
-
from gemcode.kaira_ipc import fleet_manager_ipc_path
|
|
260
|
-
|
|
261
|
-
sock_s = str(fleet_manager_ipc_path(fleet_root))
|
|
262
|
-
if Path(sock_s).exists():
|
|
263
|
-
client = await KairaIpcClient.connect(socket_path=sock_s)
|
|
264
|
-
try:
|
|
265
|
-
session_id = str(getattr(cfg, "_active_session_id", "") or "")
|
|
266
|
-
notify_chain = _ancestor_addresses_for(m)
|
|
267
|
-
header = (
|
|
268
|
-
f"You are {m.name} ({m.title}).\n"
|
|
269
|
-
f"Role description: {m.description or '(none)'}\n\n"
|
|
270
|
-
"Do the assigned task. Keep outputs concise and actionable.\n"
|
|
271
|
-
)
|
|
272
|
-
prompt = header + "\nTask:\n" + task
|
|
273
|
-
if ctx:
|
|
274
|
-
prompt += "\n\nContext:\n" + ctx
|
|
275
|
-
meta = {
|
|
276
|
-
"org": {
|
|
277
|
-
"member": (m.to_dict() if hasattr(m, "to_dict") else {}),
|
|
278
|
-
"capabilities": {
|
|
279
|
-
"kind": getattr(m, "kind", ""),
|
|
280
|
-
"address": getattr(m, "address", "") or getattr(m, "name", ""),
|
|
281
|
-
"workspace_rel": getattr(m, "workspace_rel", "") or "",
|
|
282
|
-
"reports_to": getattr(m, "reports_to", "") or "",
|
|
283
|
-
},
|
|
284
|
-
"task": task,
|
|
285
|
-
"context": ctx,
|
|
286
|
-
"notify_chain": notify_chain,
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
res = await client.request(
|
|
290
|
-
action="enqueue",
|
|
291
|
-
prompt=prompt,
|
|
292
|
-
priority=0,
|
|
293
|
-
session_id=session_id,
|
|
294
|
-
meta=meta,
|
|
295
|
-
)
|
|
296
|
-
if res.get("ok"):
|
|
297
|
-
job_id = str(res.get("job_id") or "")
|
|
298
|
-
await _publish_org_report(
|
|
299
|
-
m=m, status="delegated", task=task, context=ctx, job_id=job_id,
|
|
300
|
-
result={"kind": "kaira_worker", "job_id": job_id},
|
|
301
|
-
)
|
|
302
|
-
return {"ok": True, "delegated_to": m.to_dict(), "job_id": job_id}
|
|
303
|
-
finally:
|
|
304
|
-
await client.close()
|
|
305
|
-
except Exception:
|
|
306
|
-
pass
|
|
307
|
-
|
|
308
|
-
# ── Strategy 3: In-process subtask fallback ───────────────────────────
|
|
309
|
-
header = (
|
|
310
|
-
f"You are {m.name} ({m.title}).\n"
|
|
311
|
-
f"Role description: {m.description or '(none)'}\n\n"
|
|
312
|
-
"Before acting, load and follow your role skill if available.\n"
|
|
313
|
-
f"- If a GemSkill exists: call load_skill(\"{m.skill_name or 'member-' + m.name.lower()}\")\n\n"
|
|
314
|
-
"Do the assigned task. Keep outputs concise and actionable.\n"
|
|
315
|
-
)
|
|
316
|
-
prompt = header + "\nTask:\n" + task
|
|
317
|
-
if ctx:
|
|
318
|
-
prompt += "\n\nContext:\n" + ctx
|
|
319
|
-
|
|
218
|
+
# Fallback: in-process subtask (blocking but guaranteed to work)
|
|
320
219
|
try:
|
|
321
220
|
from gemcode.tools.subtask import make_run_subtask_tool
|
|
322
221
|
|
|
222
|
+
header = (
|
|
223
|
+
f"You are {m.name} ({m.title}).\n"
|
|
224
|
+
f"Role: {m.description or '(none)'}\n\n"
|
|
225
|
+
"Do the assigned task. Keep outputs concise and actionable.\n"
|
|
226
|
+
)
|
|
227
|
+
prompt = header + "\nTask:\n" + task
|
|
228
|
+
if ctx:
|
|
229
|
+
prompt += "\n\nContext:\n" + ctx
|
|
230
|
+
|
|
323
231
|
run_subtask = make_run_subtask_tool(cfg)
|
|
324
232
|
out = await run_subtask(prompt, "")
|
|
325
233
|
result = out.get("result") if isinstance(out, dict) else out
|
|
326
|
-
await _publish_org_report(
|
|
327
|
-
m=m, status="finished", task=task, context=ctx, result=result,
|
|
328
|
-
)
|
|
234
|
+
await _publish_org_report(m=m, status="finished", task=task, context=ctx, result=result)
|
|
329
235
|
return {"ok": True, "delegated_to": m.to_dict(), "result": result}
|
|
330
236
|
except Exception as e:
|
|
331
|
-
await _publish_org_report(
|
|
332
|
-
|
|
333
|
-
error=f"all_strategies_failed: {type(e).__name__}: {e}",
|
|
334
|
-
)
|
|
237
|
+
await _publish_org_report(m=m, status="failed", task=task, context=ctx,
|
|
238
|
+
error=f"delegation_failed: {type(e).__name__}: {e}")
|
|
335
239
|
return {"ok": False, "error": f"delegation_failed: {type(e).__name__}: {e}"}
|
|
336
240
|
|
|
337
241
|
async def org_spawn(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|