gemcode 0.4.5__tar.gz → 0.4.7__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.
Files changed (183) hide show
  1. {gemcode-0.4.5/src/gemcode.egg-info → gemcode-0.4.7}/PKG-INFO +1 -1
  2. {gemcode-0.4.5 → gemcode-0.4.7}/pyproject.toml +1 -1
  3. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/agent_mesh.py +60 -16
  4. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/invoke.py +1 -4
  5. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/org.py +19 -0
  6. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/repl_slash.py +21 -1
  7. {gemcode-0.4.5 → gemcode-0.4.7/src/gemcode.egg-info}/PKG-INFO +1 -1
  8. {gemcode-0.4.5 → gemcode-0.4.7}/LICENSE +0 -0
  9. {gemcode-0.4.5 → gemcode-0.4.7}/MANIFEST.in +0 -0
  10. {gemcode-0.4.5 → gemcode-0.4.7}/README.md +0 -0
  11. {gemcode-0.4.5 → gemcode-0.4.7}/setup.cfg +0 -0
  12. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/__init__.py +0 -0
  13. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/__main__.py +0 -0
  14. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/a2a_bridge.py +0 -0
  15. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/agent.py +0 -0
  16. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/agent_habits.py +0 -0
  17. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/agent_intelligence.py +0 -0
  18. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/agent_triggers.py +0 -0
  19. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/audit.py +0 -0
  20. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/autocompact.py +0 -0
  21. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/automations.py +0 -0
  22. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/autotune.py +0 -0
  23. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/callbacks.py +0 -0
  24. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/capability_routing.py +0 -0
  25. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/checkpoints.py +0 -0
  26. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/cli.py +0 -0
  27. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/codebase_awareness.py +0 -0
  28. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/compaction.py +0 -0
  29. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/computer_use/__init__.py +0 -0
  30. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/computer_use/browser_computer.py +0 -0
  31. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/config.py +0 -0
  32. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/context_budget.py +0 -0
  33. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/context_warning.py +0 -0
  34. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/credentials.py +0 -0
  35. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/curated_memory.py +0 -0
  36. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/delegation_learning.py +0 -0
  37. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/dynamic_policy.py +0 -0
  38. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/evals/harness.py +0 -0
  39. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/event_bus.py +0 -0
  40. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/fleet_reports.py +0 -0
  41. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/hitl_session.py +0 -0
  42. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/hooks.py +0 -0
  43. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/ide_protocol.py +0 -0
  44. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/ide_stdio.py +0 -0
  45. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/intent_classifier.py +0 -0
  46. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/interactions.py +0 -0
  47. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/kaira_client.py +0 -0
  48. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/kaira_daemon.py +0 -0
  49. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/kaira_ipc.py +0 -0
  50. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/kaira_job_store.py +0 -0
  51. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/learning.py +0 -0
  52. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/limits.py +0 -0
  53. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/live_audio_engine.py +0 -0
  54. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/logging_config.py +0 -0
  55. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/mcp_loader.py +0 -0
  56. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/memory/__init__.py +0 -0
  57. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/memory/embedding_memory_service.py +0 -0
  58. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/memory/file_memory_service.py +0 -0
  59. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/modality_tools.py +0 -0
  60. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/model_errors.py +0 -0
  61. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/model_routing.py +0 -0
  62. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/multimodal_input.py +0 -0
  63. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/openapi_loader.py +0 -0
  64. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/output_styles.py +0 -0
  65. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/paths.py +0 -0
  66. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/permissions.py +0 -0
  67. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/plugins/__init__.py +0 -0
  68. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  69. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  70. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/policy_profile.py +0 -0
  71. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/pricing.py +0 -0
  72. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/prompt_suggestions.py +0 -0
  73. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/query/__init__.py +0 -0
  74. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/query/config.py +0 -0
  75. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/query/deps.py +0 -0
  76. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/query/engine.py +0 -0
  77. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/query/stop_hooks.py +0 -0
  78. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/query/token_budget.py +0 -0
  79. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/query/transitions.py +0 -0
  80. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/query_sanitizer.py +0 -0
  81. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/refine.py +0 -0
  82. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/repl_commands.py +0 -0
  83. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/review_agent.py +0 -0
  84. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/rules.py +0 -0
  85. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/self_healing.py +0 -0
  86. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/session_runtime.py +0 -0
  87. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/session_store.py +0 -0
  88. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/session_summariser.py +0 -0
  89. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/skills.py +0 -0
  90. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/slash_commands.py +0 -0
  91. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/thinking.py +0 -0
  92. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tool_prompt_manifest.py +0 -0
  93. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tool_registry.py +0 -0
  94. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tool_result_store.py +0 -0
  95. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tool_synthesis.py +0 -0
  96. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/__init__.py +0 -0
  97. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/automations_tools.py +0 -0
  98. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/bash.py +0 -0
  99. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/browser.py +0 -0
  100. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/compress_memory.py +0 -0
  101. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/curated_memory.py +0 -0
  102. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/edit.py +0 -0
  103. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/filesystem.py +0 -0
  104. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/notebook.py +0 -0
  105. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/notes.py +0 -0
  106. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/org_tools.py +0 -0
  107. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/repo_map.py +0 -0
  108. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/search.py +0 -0
  109. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/shell.py +0 -0
  110. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/shell_gate.py +0 -0
  111. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/skills.py +0 -0
  112. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/subtask.py +0 -0
  113. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/tasks.py +0 -0
  114. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/think.py +0 -0
  115. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/todo.py +0 -0
  116. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/user_choice.py +0 -0
  117. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/veomem_tools.py +0 -0
  118. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/web.py +0 -0
  119. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools/web_search.py +0 -0
  120. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tools_inspector.py +0 -0
  121. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/trust.py +0 -0
  122. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tui/input_handler.py +0 -0
  123. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tui/scrollback.py +0 -0
  124. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tui/spinner.py +0 -0
  125. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tui/welcome_banner.py +0 -0
  126. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/tui/welcome_rich.py +0 -0
  127. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/veomem_bridge.py +0 -0
  128. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/version.py +0 -0
  129. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/vertex.py +0 -0
  130. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/wal.py +0 -0
  131. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/web/__init__.py +0 -0
  132. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/web/sse_adapter.py +0 -0
  133. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/web/terminal_repl.py +0 -0
  134. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/web/web_sse_compat.py +0 -0
  135. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode/workspace_hints.py +0 -0
  136. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode.egg-info/SOURCES.txt +0 -0
  137. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode.egg-info/dependency_links.txt +0 -0
  138. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode.egg-info/entry_points.txt +0 -0
  139. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode.egg-info/requires.txt +0 -0
  140. {gemcode-0.4.5 → gemcode-0.4.7}/src/gemcode.egg-info/top_level.txt +0 -0
  141. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_add_dir.py +0 -0
  142. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_agent_habits.py +0 -0
  143. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_agent_instruction.py +0 -0
  144. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_agent_mesh.py +0 -0
  145. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_autocompact.py +0 -0
  146. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_automations.py +0 -0
  147. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_capability_routing.py +0 -0
  148. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_checkpoint_diff_command.py +0 -0
  149. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_cli_init.py +0 -0
  150. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_compress_memory_tool.py +0 -0
  151. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_computer_use_permissions.py +0 -0
  152. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_context_budget.py +0 -0
  153. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_context_warning.py +0 -0
  154. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_credentials.py +0 -0
  155. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_eval_harness_layout.py +0 -0
  156. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_event_bus.py +0 -0
  157. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_fleet_reports.py +0 -0
  158. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_ide_stdio_attachments.py +0 -0
  159. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_interactive_permission_ask.py +0 -0
  160. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_kaira_ipc_paths.py +0 -0
  161. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_kaira_scheduler.py +0 -0
  162. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_modality_tools.py +0 -0
  163. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_model_error_retry.py +0 -0
  164. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_model_errors.py +0 -0
  165. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_model_routing.py +0 -0
  166. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_multimodal_input.py +0 -0
  167. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_output_styles_and_rules.py +0 -0
  168. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_paths.py +0 -0
  169. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_permissions.py +0 -0
  170. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_prompt_suggestions.py +0 -0
  171. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_repl_commands.py +0 -0
  172. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_repl_slash.py +0 -0
  173. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_session_runtime_cache.py +0 -0
  174. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_skills.py +0 -0
  175. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_slash_commands.py +0 -0
  176. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_slash_completion_registry.py +0 -0
  177. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_thinking_config.py +0 -0
  178. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_token_budget.py +0 -0
  179. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_tool_context_circulation.py +0 -0
  180. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_tools.py +0 -0
  181. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_tools_inspector.py +0 -0
  182. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_web_sse_adapter.py +0 -0
  183. {gemcode-0.4.5 → gemcode-0.4.7}/tests/test_workspace_hints.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.4.5
3
+ Version: 0.4.7
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.4.5"
7
+ version = "0.4.7"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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 = asyncio.Event()
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 scheduler loop and trigger engine."""
116
- if self._scheduler_task is None or self._scheduler_task.done():
117
- self._scheduler_task = asyncio.create_task(self._scheduler_loop())
118
- # Start self-triggering agents
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.set()
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 scheduler if not running
167
- try:
168
- loop = asyncio.get_running_loop()
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:
@@ -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
- try:
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
 
@@ -336,6 +336,25 @@ def hire_member(
336
336
  root = resolve_fleet_root(project_root)
337
337
  org = load_org(root)
338
338
  members = list(org.get("members") or [])
339
+
340
+ # Prevent duplicate names — ADK sub_agents require unique names
341
+ clean_name = name.strip().lower()
342
+ for existing in members:
343
+ if str(existing.get("name", "")).strip().lower() == clean_name:
344
+ # Return the existing member instead of creating a duplicate
345
+ return OrgMember(
346
+ id=str(existing.get("id") or ""),
347
+ name=str(existing.get("name") or ""),
348
+ title=str(existing.get("title") or title),
349
+ kind=str(existing.get("kind") or kind),
350
+ address=str(existing.get("address") or ""),
351
+ workspace_rel=str(existing.get("workspace_rel") or ""),
352
+ reports_to=str(existing.get("reports_to") or ""),
353
+ skill_name=str(existing.get("skill_name") or ""),
354
+ description=str(existing.get("description") or description),
355
+ created_ms=int(existing.get("created_ms") or 0),
356
+ )
357
+
339
358
  now = _now_ms()
340
359
  mid = f"m_{uuid.uuid4().hex[:10]}"
341
360
  m = OrgMember(
@@ -2964,7 +2964,27 @@ async def process_repl_slash(
2964
2964
  out()
2965
2965
  return ReplSlashResult(skip_model_turn=True)
2966
2966
  except Exception as e:
2967
- out(f"[agent] agent runtime not reachable at {sock}: {type(e).__name__}: {e}")
2967
+ # Fallback: use the mesh to run the prompt for this agent
2968
+ out(f"[agent] runtime not reachable, using mesh fallback")
2969
+ try:
2970
+ from gemcode.tools.org_tools import make_org_tools
2971
+ org_delegate = None
2972
+ for t in make_org_tools(cfg):
2973
+ if getattr(t, "__name__", "") == "org_delegate":
2974
+ org_delegate = t
2975
+ break
2976
+ if org_delegate is not None:
2977
+ d = await org_delegate(member=m.name, task=prompt, context="")
2978
+ if isinstance(d, dict) and d.get("ok"):
2979
+ out(f"[agent] delegated via mesh: member={m.name}")
2980
+ if d.get("result"):
2981
+ out(str(d.get("result"))[:2000])
2982
+ else:
2983
+ out(f"[agent] mesh delegation failed: {d.get('error') if isinstance(d, dict) else d}")
2984
+ else:
2985
+ out("[agent] org_delegate tool not available")
2986
+ except Exception as e2:
2987
+ out(f"[agent] fallback failed: {type(e2).__name__}: {e2}")
2968
2988
  out()
2969
2989
  return ReplSlashResult(skip_model_turn=True)
2970
2990
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.4.5
3
+ Version: 0.4.7
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
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