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.
- gobby/adapters/claude_code.py +13 -4
- gobby/adapters/codex.py +43 -3
- gobby/agents/runner.py +8 -0
- gobby/cli/__init__.py +6 -0
- gobby/cli/clones.py +419 -0
- gobby/cli/conductor.py +266 -0
- gobby/cli/installers/antigravity.py +3 -9
- gobby/cli/installers/claude.py +9 -9
- gobby/cli/installers/codex.py +2 -8
- gobby/cli/installers/gemini.py +2 -8
- gobby/cli/installers/shared.py +71 -8
- gobby/cli/skills.py +858 -0
- gobby/cli/tasks/ai.py +0 -440
- gobby/cli/tasks/crud.py +44 -6
- gobby/cli/tasks/main.py +0 -4
- gobby/cli/tui.py +2 -2
- gobby/cli/utils.py +3 -3
- gobby/clones/__init__.py +13 -0
- gobby/clones/git.py +547 -0
- gobby/conductor/__init__.py +16 -0
- gobby/conductor/alerts.py +135 -0
- gobby/conductor/loop.py +164 -0
- gobby/conductor/monitors/__init__.py +11 -0
- gobby/conductor/monitors/agents.py +116 -0
- gobby/conductor/monitors/tasks.py +155 -0
- gobby/conductor/pricing.py +234 -0
- gobby/conductor/token_tracker.py +160 -0
- gobby/config/app.py +63 -1
- gobby/config/search.py +110 -0
- gobby/config/servers.py +1 -1
- gobby/config/skills.py +43 -0
- gobby/config/tasks.py +6 -14
- gobby/hooks/event_handlers.py +145 -2
- gobby/hooks/hook_manager.py +48 -2
- gobby/hooks/skill_manager.py +130 -0
- gobby/install/claude/hooks/hook_dispatcher.py +4 -4
- gobby/install/codex/hooks/hook_dispatcher.py +1 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
- gobby/llm/claude.py +22 -34
- gobby/llm/claude_executor.py +46 -256
- gobby/llm/codex_executor.py +59 -291
- gobby/llm/executor.py +21 -0
- gobby/llm/gemini.py +134 -110
- gobby/llm/litellm_executor.py +143 -6
- gobby/llm/resolver.py +95 -33
- gobby/mcp_proxy/instructions.py +54 -0
- gobby/mcp_proxy/models.py +15 -0
- gobby/mcp_proxy/registries.py +68 -5
- gobby/mcp_proxy/server.py +33 -3
- gobby/mcp_proxy/services/tool_proxy.py +81 -1
- gobby/mcp_proxy/stdio.py +2 -1
- gobby/mcp_proxy/tools/__init__.py +0 -2
- gobby/mcp_proxy/tools/agent_messaging.py +317 -0
- gobby/mcp_proxy/tools/clones.py +903 -0
- gobby/mcp_proxy/tools/memory.py +1 -24
- gobby/mcp_proxy/tools/metrics.py +65 -1
- gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
- gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
- gobby/mcp_proxy/tools/session_messages.py +1 -2
- gobby/mcp_proxy/tools/skills/__init__.py +631 -0
- gobby/mcp_proxy/tools/task_orchestration.py +7 -0
- gobby/mcp_proxy/tools/task_readiness.py +14 -0
- gobby/mcp_proxy/tools/task_sync.py +1 -1
- gobby/mcp_proxy/tools/tasks/_context.py +0 -20
- gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
- gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +60 -29
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
- gobby/mcp_proxy/tools/workflows.py +1 -1
- gobby/mcp_proxy/tools/worktrees.py +5 -0
- gobby/memory/backends/__init__.py +6 -1
- gobby/memory/backends/mem0.py +6 -1
- gobby/memory/extractor.py +477 -0
- gobby/memory/manager.py +11 -2
- gobby/prompts/defaults/handoff/compact.md +63 -0
- gobby/prompts/defaults/handoff/session_end.md +57 -0
- gobby/prompts/defaults/memory/extract.md +61 -0
- gobby/runner.py +37 -16
- gobby/search/__init__.py +48 -6
- gobby/search/backends/__init__.py +159 -0
- gobby/search/backends/embedding.py +225 -0
- gobby/search/embeddings.py +238 -0
- gobby/search/models.py +148 -0
- gobby/search/unified.py +496 -0
- gobby/servers/http.py +23 -8
- gobby/servers/routes/admin.py +280 -0
- gobby/servers/routes/mcp/tools.py +241 -52
- gobby/servers/websocket.py +2 -2
- gobby/sessions/analyzer.py +2 -0
- gobby/sessions/transcripts/base.py +1 -0
- gobby/sessions/transcripts/claude.py +64 -5
- gobby/skills/__init__.py +91 -0
- gobby/skills/loader.py +685 -0
- gobby/skills/manager.py +384 -0
- gobby/skills/parser.py +258 -0
- gobby/skills/search.py +463 -0
- gobby/skills/sync.py +119 -0
- gobby/skills/updater.py +385 -0
- gobby/skills/validator.py +368 -0
- gobby/storage/clones.py +378 -0
- gobby/storage/database.py +1 -1
- gobby/storage/memories.py +43 -13
- gobby/storage/migrations.py +180 -6
- gobby/storage/sessions.py +73 -0
- gobby/storage/skills.py +749 -0
- gobby/storage/tasks/_crud.py +4 -4
- gobby/storage/tasks/_lifecycle.py +41 -6
- gobby/storage/tasks/_manager.py +14 -5
- gobby/storage/tasks/_models.py +8 -3
- gobby/sync/memories.py +39 -4
- gobby/sync/tasks.py +83 -6
- gobby/tasks/__init__.py +1 -2
- gobby/tasks/validation.py +24 -15
- gobby/tui/api_client.py +4 -7
- gobby/tui/app.py +5 -3
- gobby/tui/screens/orchestrator.py +1 -2
- gobby/tui/screens/tasks.py +2 -4
- gobby/tui/ws_client.py +1 -1
- gobby/utils/daemon_client.py +2 -2
- gobby/workflows/actions.py +84 -2
- gobby/workflows/context_actions.py +43 -0
- gobby/workflows/detection_helpers.py +115 -31
- gobby/workflows/engine.py +13 -2
- gobby/workflows/lifecycle_evaluator.py +29 -1
- gobby/workflows/loader.py +19 -6
- gobby/workflows/memory_actions.py +74 -0
- gobby/workflows/summary_actions.py +17 -0
- gobby/workflows/task_enforcement_actions.py +448 -6
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/METADATA +82 -21
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/RECORD +136 -107
- gobby/install/codex/prompts/forget.md +0 -7
- gobby/install/codex/prompts/memories.md +0 -7
- gobby/install/codex/prompts/recall.md +0 -7
- gobby/install/codex/prompts/remember.md +0 -13
- gobby/llm/gemini_executor.py +0 -339
- gobby/mcp_proxy/tools/task_expansion.py +0 -591
- gobby/tasks/context.py +0 -747
- gobby/tasks/criteria.py +0 -342
- gobby/tasks/expansion.py +0 -626
- gobby/tasks/prompts/expand.py +0 -327
- gobby/tasks/research.py +0 -421
- gobby/tasks/tdd.py +0 -352
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/WHEEL +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/licenses/LICENSE.md +0 -0
- {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
|
+
}
|