gobby 0.2.5__py3-none-any.whl → 0.2.6__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.
Files changed (148) hide show
  1. gobby/adapters/claude_code.py +13 -4
  2. gobby/adapters/codex.py +43 -3
  3. gobby/agents/runner.py +8 -0
  4. gobby/cli/__init__.py +6 -0
  5. gobby/cli/clones.py +419 -0
  6. gobby/cli/conductor.py +266 -0
  7. gobby/cli/installers/antigravity.py +3 -9
  8. gobby/cli/installers/claude.py +9 -9
  9. gobby/cli/installers/codex.py +2 -8
  10. gobby/cli/installers/gemini.py +2 -8
  11. gobby/cli/installers/shared.py +71 -8
  12. gobby/cli/skills.py +858 -0
  13. gobby/cli/tasks/ai.py +0 -440
  14. gobby/cli/tasks/crud.py +44 -6
  15. gobby/cli/tasks/main.py +0 -4
  16. gobby/cli/tui.py +2 -2
  17. gobby/cli/utils.py +3 -3
  18. gobby/clones/__init__.py +13 -0
  19. gobby/clones/git.py +547 -0
  20. gobby/conductor/__init__.py +16 -0
  21. gobby/conductor/alerts.py +135 -0
  22. gobby/conductor/loop.py +164 -0
  23. gobby/conductor/monitors/__init__.py +11 -0
  24. gobby/conductor/monitors/agents.py +116 -0
  25. gobby/conductor/monitors/tasks.py +155 -0
  26. gobby/conductor/pricing.py +234 -0
  27. gobby/conductor/token_tracker.py +160 -0
  28. gobby/config/app.py +63 -1
  29. gobby/config/search.py +110 -0
  30. gobby/config/servers.py +1 -1
  31. gobby/config/skills.py +43 -0
  32. gobby/config/tasks.py +6 -14
  33. gobby/hooks/event_handlers.py +145 -2
  34. gobby/hooks/hook_manager.py +48 -2
  35. gobby/hooks/skill_manager.py +130 -0
  36. gobby/install/claude/hooks/hook_dispatcher.py +4 -4
  37. gobby/install/codex/hooks/hook_dispatcher.py +1 -1
  38. gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
  39. gobby/llm/claude.py +22 -34
  40. gobby/llm/claude_executor.py +46 -256
  41. gobby/llm/codex_executor.py +59 -291
  42. gobby/llm/executor.py +21 -0
  43. gobby/llm/gemini.py +134 -110
  44. gobby/llm/litellm_executor.py +143 -6
  45. gobby/llm/resolver.py +95 -33
  46. gobby/mcp_proxy/instructions.py +54 -0
  47. gobby/mcp_proxy/models.py +15 -0
  48. gobby/mcp_proxy/registries.py +68 -5
  49. gobby/mcp_proxy/server.py +33 -3
  50. gobby/mcp_proxy/services/tool_proxy.py +81 -1
  51. gobby/mcp_proxy/stdio.py +2 -1
  52. gobby/mcp_proxy/tools/__init__.py +0 -2
  53. gobby/mcp_proxy/tools/agent_messaging.py +317 -0
  54. gobby/mcp_proxy/tools/clones.py +903 -0
  55. gobby/mcp_proxy/tools/memory.py +1 -24
  56. gobby/mcp_proxy/tools/metrics.py +65 -1
  57. gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
  58. gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
  59. gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
  60. gobby/mcp_proxy/tools/session_messages.py +1 -2
  61. gobby/mcp_proxy/tools/skills/__init__.py +631 -0
  62. gobby/mcp_proxy/tools/task_orchestration.py +7 -0
  63. gobby/mcp_proxy/tools/task_readiness.py +14 -0
  64. gobby/mcp_proxy/tools/task_sync.py +1 -1
  65. gobby/mcp_proxy/tools/tasks/_context.py +0 -20
  66. gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
  67. gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
  68. gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
  69. gobby/mcp_proxy/tools/tasks/_lifecycle.py +60 -29
  70. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
  71. gobby/mcp_proxy/tools/workflows.py +1 -1
  72. gobby/mcp_proxy/tools/worktrees.py +5 -0
  73. gobby/memory/backends/__init__.py +6 -1
  74. gobby/memory/backends/mem0.py +6 -1
  75. gobby/memory/extractor.py +477 -0
  76. gobby/memory/manager.py +11 -2
  77. gobby/prompts/defaults/handoff/compact.md +63 -0
  78. gobby/prompts/defaults/handoff/session_end.md +57 -0
  79. gobby/prompts/defaults/memory/extract.md +61 -0
  80. gobby/runner.py +37 -16
  81. gobby/search/__init__.py +48 -6
  82. gobby/search/backends/__init__.py +159 -0
  83. gobby/search/backends/embedding.py +225 -0
  84. gobby/search/embeddings.py +238 -0
  85. gobby/search/models.py +148 -0
  86. gobby/search/unified.py +496 -0
  87. gobby/servers/http.py +23 -8
  88. gobby/servers/routes/admin.py +280 -0
  89. gobby/servers/routes/mcp/tools.py +241 -52
  90. gobby/servers/websocket.py +2 -2
  91. gobby/sessions/analyzer.py +2 -0
  92. gobby/sessions/transcripts/base.py +1 -0
  93. gobby/sessions/transcripts/claude.py +64 -5
  94. gobby/skills/__init__.py +91 -0
  95. gobby/skills/loader.py +685 -0
  96. gobby/skills/manager.py +384 -0
  97. gobby/skills/parser.py +258 -0
  98. gobby/skills/search.py +463 -0
  99. gobby/skills/sync.py +119 -0
  100. gobby/skills/updater.py +385 -0
  101. gobby/skills/validator.py +368 -0
  102. gobby/storage/clones.py +378 -0
  103. gobby/storage/database.py +1 -1
  104. gobby/storage/memories.py +43 -13
  105. gobby/storage/migrations.py +180 -6
  106. gobby/storage/sessions.py +73 -0
  107. gobby/storage/skills.py +749 -0
  108. gobby/storage/tasks/_crud.py +4 -4
  109. gobby/storage/tasks/_lifecycle.py +41 -6
  110. gobby/storage/tasks/_manager.py +14 -5
  111. gobby/storage/tasks/_models.py +8 -3
  112. gobby/sync/memories.py +39 -4
  113. gobby/sync/tasks.py +83 -6
  114. gobby/tasks/__init__.py +1 -2
  115. gobby/tasks/validation.py +24 -15
  116. gobby/tui/api_client.py +4 -7
  117. gobby/tui/app.py +5 -3
  118. gobby/tui/screens/orchestrator.py +1 -2
  119. gobby/tui/screens/tasks.py +2 -4
  120. gobby/tui/ws_client.py +1 -1
  121. gobby/utils/daemon_client.py +2 -2
  122. gobby/workflows/actions.py +84 -2
  123. gobby/workflows/context_actions.py +43 -0
  124. gobby/workflows/detection_helpers.py +115 -31
  125. gobby/workflows/engine.py +13 -2
  126. gobby/workflows/lifecycle_evaluator.py +29 -1
  127. gobby/workflows/loader.py +19 -6
  128. gobby/workflows/memory_actions.py +74 -0
  129. gobby/workflows/summary_actions.py +17 -0
  130. gobby/workflows/task_enforcement_actions.py +448 -6
  131. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/METADATA +82 -21
  132. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/RECORD +136 -107
  133. gobby/install/codex/prompts/forget.md +0 -7
  134. gobby/install/codex/prompts/memories.md +0 -7
  135. gobby/install/codex/prompts/recall.md +0 -7
  136. gobby/install/codex/prompts/remember.md +0 -13
  137. gobby/llm/gemini_executor.py +0 -339
  138. gobby/mcp_proxy/tools/task_expansion.py +0 -591
  139. gobby/tasks/context.py +0 -747
  140. gobby/tasks/criteria.py +0 -342
  141. gobby/tasks/expansion.py +0 -626
  142. gobby/tasks/prompts/expand.py +0 -327
  143. gobby/tasks/research.py +0 -421
  144. gobby/tasks/tdd.py +0 -352
  145. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/WHEEL +0 -0
  146. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/entry_points.txt +0 -0
  147. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/licenses/LICENSE.md +0 -0
  148. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,317 @@
1
+ """
2
+ Inter-agent messaging tools for the gobby-agents MCP server.
3
+
4
+ Provides messaging capabilities between parent and child sessions:
5
+ - send_to_parent: Child sends message to its parent session
6
+ - send_to_child: Parent sends message to a specific child
7
+ - poll_messages: Check for incoming messages
8
+ - mark_message_read: Mark a message as read
9
+ - broadcast_to_children: Send message to all running children
10
+
11
+ These tools resolve session relationships from RunningAgentRegistry.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ if TYPE_CHECKING:
20
+ from gobby.agents.registry import RunningAgentRegistry
21
+ from gobby.mcp_proxy.tools.internal import InternalToolRegistry
22
+ from gobby.storage.inter_session_messages import InterSessionMessageManager
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ def add_messaging_tools(
28
+ registry: InternalToolRegistry,
29
+ message_manager: InterSessionMessageManager,
30
+ agent_registry: RunningAgentRegistry,
31
+ ) -> None:
32
+ """
33
+ Add inter-agent messaging tools to an existing registry.
34
+
35
+ Args:
36
+ registry: The InternalToolRegistry to add tools to (typically gobby-agents)
37
+ message_manager: InterSessionMessageManager for persisting messages
38
+ agent_registry: RunningAgentRegistry for resolving parent/child relationships
39
+ """
40
+
41
+ @registry.tool(
42
+ name="send_to_parent",
43
+ description="Send a message from a child session to its parent session.",
44
+ )
45
+ async def send_to_parent(
46
+ session_id: str,
47
+ content: str,
48
+ priority: str = "normal",
49
+ ) -> dict[str, Any]:
50
+ """
51
+ Send a message to the parent session.
52
+
53
+ Use this when a child agent needs to communicate status, results,
54
+ or requests back to its parent session.
55
+
56
+ Args:
57
+ session_id: The current (child) session ID
58
+ content: Message content to send
59
+ priority: Message priority ("normal" or "urgent")
60
+
61
+ Returns:
62
+ Dict with success status and message details
63
+ """
64
+ try:
65
+ # Find the running agent to get parent relationship
66
+ agent = agent_registry.get_by_session(session_id)
67
+ if not agent:
68
+ return {
69
+ "success": False,
70
+ "error": f"Session {session_id} not found in running agent registry",
71
+ }
72
+
73
+ parent_session_id = agent.parent_session_id
74
+ if not parent_session_id:
75
+ return {
76
+ "success": False,
77
+ "error": "No parent session found for this agent",
78
+ }
79
+
80
+ # Create the message
81
+ msg = message_manager.create_message(
82
+ from_session=session_id,
83
+ to_session=parent_session_id,
84
+ content=content,
85
+ priority=priority,
86
+ )
87
+
88
+ logger.info(f"Message sent from {session_id} to parent {parent_session_id}: {msg.id}")
89
+
90
+ return {
91
+ "success": True,
92
+ "message": msg.to_dict(),
93
+ "parent_session_id": parent_session_id,
94
+ }
95
+
96
+ except Exception as e:
97
+ logger.error(f"Failed to send message to parent: {e}")
98
+ return {
99
+ "success": False,
100
+ "error": str(e),
101
+ }
102
+
103
+ @registry.tool(
104
+ name="send_to_child",
105
+ description="Send a message from a parent session to a specific child session.",
106
+ )
107
+ async def send_to_child(
108
+ parent_session_id: str,
109
+ child_session_id: str,
110
+ content: str,
111
+ priority: str = "normal",
112
+ ) -> dict[str, Any]:
113
+ """
114
+ Send a message to a child session.
115
+
116
+ Use this when a parent agent needs to communicate instructions,
117
+ updates, or coordination messages to a spawned child.
118
+
119
+ Args:
120
+ parent_session_id: The parent session ID (sender)
121
+ child_session_id: The child session ID (recipient)
122
+ content: Message content to send
123
+ priority: Message priority ("normal" or "urgent")
124
+
125
+ Returns:
126
+ Dict with success status and message details
127
+ """
128
+ try:
129
+ # Verify the child exists and belongs to this parent
130
+ child_agent = agent_registry.get_by_session(child_session_id)
131
+ if not child_agent:
132
+ return {
133
+ "success": False,
134
+ "error": f"Child session {child_session_id} not found in running agent registry",
135
+ }
136
+
137
+ if child_agent.parent_session_id != parent_session_id:
138
+ return {
139
+ "success": False,
140
+ "error": (
141
+ f"Session {child_session_id} is not a child of {parent_session_id}. "
142
+ f"Actual parent: {child_agent.parent_session_id}"
143
+ ),
144
+ }
145
+
146
+ # Create the message
147
+ msg = message_manager.create_message(
148
+ from_session=parent_session_id,
149
+ to_session=child_session_id,
150
+ content=content,
151
+ priority=priority,
152
+ )
153
+
154
+ logger.info(
155
+ f"Message sent from {parent_session_id} to child {child_session_id}: {msg.id}"
156
+ )
157
+
158
+ return {
159
+ "success": True,
160
+ "message": msg.to_dict(),
161
+ }
162
+
163
+ except Exception as e:
164
+ logger.error(f"Failed to send message to child: {e}")
165
+ return {
166
+ "success": False,
167
+ "error": str(e),
168
+ }
169
+
170
+ @registry.tool(
171
+ name="poll_messages",
172
+ description="Poll for messages sent to this session.",
173
+ )
174
+ async def poll_messages(
175
+ session_id: str,
176
+ unread_only: bool = True,
177
+ ) -> dict[str, Any]:
178
+ """
179
+ Poll for incoming messages.
180
+
181
+ Check for messages sent to this session from parent or child sessions.
182
+ By default, returns only unread messages.
183
+
184
+ Args:
185
+ session_id: The session ID to check messages for
186
+ unread_only: If True, only return unread messages (default: True)
187
+
188
+ Returns:
189
+ Dict with success status and list of messages
190
+ """
191
+ try:
192
+ messages = message_manager.get_messages(
193
+ to_session=session_id,
194
+ unread_only=unread_only,
195
+ )
196
+
197
+ return {
198
+ "success": True,
199
+ "messages": [msg.to_dict() for msg in messages],
200
+ "count": len(messages),
201
+ }
202
+
203
+ except Exception as e:
204
+ logger.error(f"Failed to poll messages: {e}")
205
+ return {
206
+ "success": False,
207
+ "error": str(e),
208
+ }
209
+
210
+ @registry.tool(
211
+ name="mark_message_read",
212
+ description="Mark a message as read.",
213
+ )
214
+ async def mark_message_read(
215
+ message_id: str,
216
+ ) -> dict[str, Any]:
217
+ """
218
+ Mark a message as read.
219
+
220
+ After processing a message, mark it as read so it won't appear
221
+ in subsequent poll_messages calls with unread_only=True.
222
+
223
+ Args:
224
+ message_id: The message ID to mark as read
225
+
226
+ Returns:
227
+ Dict with success status and updated message
228
+ """
229
+ try:
230
+ msg = message_manager.mark_read(message_id)
231
+
232
+ return {
233
+ "success": True,
234
+ "message": msg.to_dict(),
235
+ }
236
+
237
+ except ValueError:
238
+ return {
239
+ "success": False,
240
+ "error": f"Message not found: {message_id}",
241
+ }
242
+ except Exception as e:
243
+ logger.error(f"Failed to mark message as read: {e}")
244
+ return {
245
+ "success": False,
246
+ "error": str(e),
247
+ }
248
+
249
+ @registry.tool(
250
+ name="broadcast_to_children",
251
+ description="Broadcast a message to all running child sessions.",
252
+ )
253
+ async def broadcast_to_children(
254
+ parent_session_id: str,
255
+ content: str,
256
+ priority: str = "normal",
257
+ ) -> dict[str, Any]:
258
+ """
259
+ Broadcast a message to all running children.
260
+
261
+ Send the same message to all child sessions spawned by this parent.
262
+ Useful for coordination or shutdown signals.
263
+
264
+ Args:
265
+ parent_session_id: The parent session ID
266
+ content: Message content to broadcast
267
+ priority: Message priority ("normal" or "urgent")
268
+
269
+ Returns:
270
+ Dict with success status and count of messages sent
271
+ """
272
+ try:
273
+ children = agent_registry.list_by_parent(parent_session_id)
274
+
275
+ if not children:
276
+ return {
277
+ "success": True,
278
+ "sent_count": 0,
279
+ "message": "No running children found",
280
+ }
281
+
282
+ sent_count = 0
283
+ errors = []
284
+
285
+ for child in children:
286
+ try:
287
+ message_manager.create_message(
288
+ from_session=parent_session_id,
289
+ to_session=child.session_id,
290
+ content=content,
291
+ priority=priority,
292
+ )
293
+ sent_count += 1
294
+ except Exception as e:
295
+ errors.append(f"{child.session_id}: {e}")
296
+
297
+ result: dict[str, Any] = {
298
+ "success": True,
299
+ "sent_count": sent_count,
300
+ "total_children": len(children),
301
+ }
302
+
303
+ if errors:
304
+ result["errors"] = errors
305
+
306
+ logger.info(
307
+ f"Broadcast from {parent_session_id} sent to {sent_count}/{len(children)} children"
308
+ )
309
+
310
+ return result
311
+
312
+ except Exception as e:
313
+ logger.error(f"Failed to broadcast to children: {e}")
314
+ return {
315
+ "success": False,
316
+ "error": str(e),
317
+ }