gobby 0.2.7__py3-none-any.whl → 0.2.8__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 +96 -35
- gobby/adapters/gemini.py +140 -38
- gobby/agents/isolation.py +130 -0
- gobby/agents/registry.py +11 -0
- gobby/agents/session.py +1 -0
- gobby/agents/spawn_executor.py +43 -13
- gobby/agents/spawners/macos.py +26 -1
- gobby/cli/__init__.py +0 -2
- gobby/cli/memory.py +185 -0
- gobby/clones/git.py +177 -0
- gobby/config/skills.py +31 -0
- gobby/hooks/event_handlers.py +109 -10
- gobby/hooks/hook_manager.py +19 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +74 -15
- gobby/mcp_proxy/instructions.py +2 -2
- gobby/mcp_proxy/registries.py +21 -4
- gobby/mcp_proxy/tools/agent_messaging.py +93 -44
- gobby/mcp_proxy/tools/agents.py +45 -9
- gobby/mcp_proxy/tools/artifacts.py +43 -9
- gobby/mcp_proxy/tools/sessions/_commits.py +31 -24
- gobby/mcp_proxy/tools/sessions/_crud.py +5 -5
- gobby/mcp_proxy/tools/sessions/_handoff.py +45 -41
- gobby/mcp_proxy/tools/sessions/_messages.py +35 -7
- gobby/mcp_proxy/tools/spawn_agent.py +44 -6
- gobby/mcp_proxy/tools/tasks/_context.py +18 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +13 -6
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +29 -14
- gobby/mcp_proxy/tools/tasks/_session.py +22 -7
- gobby/mcp_proxy/tools/workflows.py +84 -34
- gobby/mcp_proxy/tools/worktrees.py +32 -7
- gobby/memory/extractor.py +15 -1
- gobby/runner.py +13 -0
- gobby/servers/routes/mcp/hooks.py +50 -3
- gobby/servers/websocket.py +57 -1
- gobby/sessions/analyzer.py +2 -2
- gobby/sessions/manager.py +9 -0
- gobby/sessions/transcripts/gemini.py +100 -34
- gobby/storage/database.py +9 -2
- gobby/storage/memories.py +32 -21
- gobby/storage/migrations.py +23 -4
- gobby/storage/sessions.py +4 -2
- gobby/storage/skills.py +43 -3
- gobby/workflows/detection_helpers.py +38 -24
- gobby/workflows/enforcement/blocking.py +13 -1
- gobby/workflows/engine.py +93 -0
- gobby/workflows/evaluator.py +110 -0
- gobby/workflows/hooks.py +41 -0
- gobby/workflows/memory_actions.py +11 -0
- gobby/workflows/safe_evaluator.py +8 -0
- gobby/workflows/summary_actions.py +123 -50
- {gobby-0.2.7.dist-info → gobby-0.2.8.dist-info}/METADATA +1 -1
- {gobby-0.2.7.dist-info → gobby-0.2.8.dist-info}/RECORD +56 -80
- gobby/cli/tui.py +0 -34
- gobby/tui/__init__.py +0 -5
- gobby/tui/api_client.py +0 -278
- gobby/tui/app.py +0 -329
- gobby/tui/screens/__init__.py +0 -25
- gobby/tui/screens/agents.py +0 -333
- gobby/tui/screens/chat.py +0 -450
- gobby/tui/screens/dashboard.py +0 -377
- gobby/tui/screens/memory.py +0 -305
- gobby/tui/screens/metrics.py +0 -231
- gobby/tui/screens/orchestrator.py +0 -903
- gobby/tui/screens/sessions.py +0 -412
- gobby/tui/screens/tasks.py +0 -440
- gobby/tui/screens/workflows.py +0 -289
- gobby/tui/screens/worktrees.py +0 -174
- gobby/tui/widgets/__init__.py +0 -21
- gobby/tui/widgets/chat.py +0 -210
- gobby/tui/widgets/conductor.py +0 -104
- gobby/tui/widgets/menu.py +0 -132
- gobby/tui/widgets/message_panel.py +0 -160
- gobby/tui/widgets/review_gate.py +0 -224
- gobby/tui/widgets/task_tree.py +0 -99
- gobby/tui/widgets/token_budget.py +0 -166
- gobby/tui/ws_client.py +0 -258
- {gobby-0.2.7.dist-info → gobby-0.2.8.dist-info}/WHEEL +0 -0
- {gobby-0.2.7.dist-info → gobby-0.2.8.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.7.dist-info → gobby-0.2.8.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.7.dist-info → gobby-0.2.8.dist-info}/top_level.txt +0 -0
|
@@ -6,9 +6,10 @@ Provides messaging capabilities between parent and child sessions:
|
|
|
6
6
|
- send_to_child: Parent sends message to a specific child
|
|
7
7
|
- poll_messages: Check for incoming messages
|
|
8
8
|
- mark_message_read: Mark a message as read
|
|
9
|
-
- broadcast_to_children: Send message to all
|
|
9
|
+
- broadcast_to_children: Send message to all children (active in database)
|
|
10
10
|
|
|
11
|
-
These tools resolve session relationships from
|
|
11
|
+
These tools resolve session relationships from the database (LocalSessionManager),
|
|
12
|
+
which is the authoritative source for parent_session_id relationships.
|
|
12
13
|
"""
|
|
13
14
|
|
|
14
15
|
from __future__ import annotations
|
|
@@ -17,9 +18,9 @@ import logging
|
|
|
17
18
|
from typing import TYPE_CHECKING, Any
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
|
-
from gobby.agents.registry import RunningAgentRegistry
|
|
21
21
|
from gobby.mcp_proxy.tools.internal import InternalToolRegistry
|
|
22
22
|
from gobby.storage.inter_session_messages import InterSessionMessageManager
|
|
23
|
+
from gobby.storage.sessions import LocalSessionManager
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
26
|
|
|
@@ -27,7 +28,7 @@ logger = logging.getLogger(__name__)
|
|
|
27
28
|
def add_messaging_tools(
|
|
28
29
|
registry: InternalToolRegistry,
|
|
29
30
|
message_manager: InterSessionMessageManager,
|
|
30
|
-
|
|
31
|
+
session_manager: LocalSessionManager,
|
|
31
32
|
) -> None:
|
|
32
33
|
"""
|
|
33
34
|
Add inter-agent messaging tools to an existing registry.
|
|
@@ -35,12 +36,20 @@ def add_messaging_tools(
|
|
|
35
36
|
Args:
|
|
36
37
|
registry: The InternalToolRegistry to add tools to (typically gobby-agents)
|
|
37
38
|
message_manager: InterSessionMessageManager for persisting messages
|
|
38
|
-
|
|
39
|
+
session_manager: LocalSessionManager for resolving parent/child relationships
|
|
40
|
+
(database is the authoritative source for session relationships)
|
|
39
41
|
"""
|
|
42
|
+
from gobby.utils.project_context import get_project_context
|
|
43
|
+
|
|
44
|
+
def _resolve_session_id(ref: str) -> str:
|
|
45
|
+
"""Resolve session reference (#N, N, UUID, or prefix) to UUID."""
|
|
46
|
+
project_ctx = get_project_context()
|
|
47
|
+
project_id = project_ctx.get("id") if project_ctx else None
|
|
48
|
+
return session_manager.resolve_session_reference(ref, project_id)
|
|
40
49
|
|
|
41
50
|
@registry.tool(
|
|
42
51
|
name="send_to_parent",
|
|
43
|
-
description="Send a message from a child session to its parent session.",
|
|
52
|
+
description="Send a message from a child session to its parent session. Accepts #N, N, UUID, or prefix for session_id.",
|
|
44
53
|
)
|
|
45
54
|
async def send_to_parent(
|
|
46
55
|
session_id: str,
|
|
@@ -54,7 +63,7 @@ def add_messaging_tools(
|
|
|
54
63
|
or requests back to its parent session.
|
|
55
64
|
|
|
56
65
|
Args:
|
|
57
|
-
session_id:
|
|
66
|
+
session_id: Session reference (accepts #N, N, UUID, or prefix) for the current (child) session
|
|
58
67
|
content: Message content to send
|
|
59
68
|
priority: Message priority ("normal" or "urgent")
|
|
60
69
|
|
|
@@ -62,30 +71,41 @@ def add_messaging_tools(
|
|
|
62
71
|
Dict with success status and message details
|
|
63
72
|
"""
|
|
64
73
|
try:
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
75
|
+
try:
|
|
76
|
+
resolved_session_id = _resolve_session_id(session_id)
|
|
77
|
+
except ValueError as e:
|
|
78
|
+
return {"success": False, "error": str(e)}
|
|
79
|
+
|
|
80
|
+
# Look up session in database (authoritative source for relationships)
|
|
81
|
+
session = session_manager.get(resolved_session_id)
|
|
82
|
+
if not session:
|
|
68
83
|
return {
|
|
69
84
|
"success": False,
|
|
70
|
-
"error": f"Session {
|
|
85
|
+
"error": f"Session {resolved_session_id} not found",
|
|
71
86
|
}
|
|
72
87
|
|
|
73
|
-
parent_session_id =
|
|
88
|
+
parent_session_id = session.parent_session_id
|
|
74
89
|
if not parent_session_id:
|
|
75
90
|
return {
|
|
76
91
|
"success": False,
|
|
77
|
-
"error": "No parent session
|
|
92
|
+
"error": "No parent session for this session",
|
|
78
93
|
}
|
|
79
94
|
|
|
80
95
|
# Create the message
|
|
81
96
|
msg = message_manager.create_message(
|
|
82
|
-
from_session=
|
|
97
|
+
from_session=resolved_session_id,
|
|
83
98
|
to_session=parent_session_id,
|
|
84
99
|
content=content,
|
|
85
100
|
priority=priority,
|
|
86
101
|
)
|
|
87
102
|
|
|
88
|
-
logger.info(
|
|
103
|
+
logger.info(
|
|
104
|
+
"Message sent from %s to parent %s: %s",
|
|
105
|
+
resolved_session_id,
|
|
106
|
+
parent_session_id,
|
|
107
|
+
msg.id,
|
|
108
|
+
)
|
|
89
109
|
|
|
90
110
|
return {
|
|
91
111
|
"success": True,
|
|
@@ -94,7 +114,7 @@ def add_messaging_tools(
|
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
except Exception as e:
|
|
97
|
-
logger.error(
|
|
117
|
+
logger.error("Failed to send message to parent: %s", e)
|
|
98
118
|
return {
|
|
99
119
|
"success": False,
|
|
100
120
|
"error": str(e),
|
|
@@ -102,7 +122,7 @@ def add_messaging_tools(
|
|
|
102
122
|
|
|
103
123
|
@registry.tool(
|
|
104
124
|
name="send_to_child",
|
|
105
|
-
description="Send a message from a parent session to a specific child session.",
|
|
125
|
+
description="Send a message from a parent session to a specific child session. Accepts #N, N, UUID, or prefix for session IDs.",
|
|
106
126
|
)
|
|
107
127
|
async def send_to_child(
|
|
108
128
|
parent_session_id: str,
|
|
@@ -117,8 +137,8 @@ def add_messaging_tools(
|
|
|
117
137
|
updates, or coordination messages to a spawned child.
|
|
118
138
|
|
|
119
139
|
Args:
|
|
120
|
-
parent_session_id:
|
|
121
|
-
child_session_id:
|
|
140
|
+
parent_session_id: Session reference (accepts #N, N, UUID, or prefix) for the parent (sender)
|
|
141
|
+
child_session_id: Session reference (accepts #N, N, UUID, or prefix) for the child (recipient)
|
|
122
142
|
content: Message content to send
|
|
123
143
|
priority: Message priority ("normal" or "urgent")
|
|
124
144
|
|
|
@@ -126,33 +146,43 @@ def add_messaging_tools(
|
|
|
126
146
|
Dict with success status and message details
|
|
127
147
|
"""
|
|
128
148
|
try:
|
|
129
|
-
#
|
|
130
|
-
|
|
131
|
-
|
|
149
|
+
# Resolve session IDs to UUIDs (accepts #N, N, UUID, or prefix)
|
|
150
|
+
try:
|
|
151
|
+
resolved_parent_id = _resolve_session_id(parent_session_id)
|
|
152
|
+
resolved_child_id = _resolve_session_id(child_session_id)
|
|
153
|
+
except ValueError as e:
|
|
154
|
+
return {"success": False, "error": str(e)}
|
|
155
|
+
|
|
156
|
+
# Verify the child exists in database and belongs to this parent
|
|
157
|
+
child_session = session_manager.get(resolved_child_id)
|
|
158
|
+
if not child_session:
|
|
132
159
|
return {
|
|
133
160
|
"success": False,
|
|
134
|
-
"error": f"Child session {
|
|
161
|
+
"error": f"Child session {resolved_child_id} not found",
|
|
135
162
|
}
|
|
136
163
|
|
|
137
|
-
if
|
|
164
|
+
if child_session.parent_session_id != resolved_parent_id:
|
|
138
165
|
return {
|
|
139
166
|
"success": False,
|
|
140
167
|
"error": (
|
|
141
|
-
f"Session {
|
|
142
|
-
f"Actual parent: {
|
|
168
|
+
f"Session {resolved_child_id} is not a child of {resolved_parent_id}. "
|
|
169
|
+
f"Actual parent: {child_session.parent_session_id}"
|
|
143
170
|
),
|
|
144
171
|
}
|
|
145
172
|
|
|
146
173
|
# Create the message
|
|
147
174
|
msg = message_manager.create_message(
|
|
148
|
-
from_session=
|
|
149
|
-
to_session=
|
|
175
|
+
from_session=resolved_parent_id,
|
|
176
|
+
to_session=resolved_child_id,
|
|
150
177
|
content=content,
|
|
151
178
|
priority=priority,
|
|
152
179
|
)
|
|
153
180
|
|
|
154
181
|
logger.info(
|
|
155
|
-
|
|
182
|
+
"Message sent from %s to child %s: %s",
|
|
183
|
+
resolved_parent_id,
|
|
184
|
+
resolved_child_id,
|
|
185
|
+
msg.id,
|
|
156
186
|
)
|
|
157
187
|
|
|
158
188
|
return {
|
|
@@ -161,7 +191,7 @@ def add_messaging_tools(
|
|
|
161
191
|
}
|
|
162
192
|
|
|
163
193
|
except Exception as e:
|
|
164
|
-
logger.error(
|
|
194
|
+
logger.error("Failed to send message to child: %s", e)
|
|
165
195
|
return {
|
|
166
196
|
"success": False,
|
|
167
197
|
"error": str(e),
|
|
@@ -169,7 +199,7 @@ def add_messaging_tools(
|
|
|
169
199
|
|
|
170
200
|
@registry.tool(
|
|
171
201
|
name="poll_messages",
|
|
172
|
-
description="Poll for messages sent to this session.",
|
|
202
|
+
description="Poll for messages sent to this session. Accepts #N, N, UUID, or prefix for session_id.",
|
|
173
203
|
)
|
|
174
204
|
async def poll_messages(
|
|
175
205
|
session_id: str,
|
|
@@ -182,15 +212,21 @@ def add_messaging_tools(
|
|
|
182
212
|
By default, returns only unread messages.
|
|
183
213
|
|
|
184
214
|
Args:
|
|
185
|
-
session_id:
|
|
215
|
+
session_id: Session reference (accepts #N, N, UUID, or prefix) to check messages for
|
|
186
216
|
unread_only: If True, only return unread messages (default: True)
|
|
187
217
|
|
|
188
218
|
Returns:
|
|
189
219
|
Dict with success status and list of messages
|
|
190
220
|
"""
|
|
191
221
|
try:
|
|
222
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
223
|
+
try:
|
|
224
|
+
resolved_session_id = _resolve_session_id(session_id)
|
|
225
|
+
except ValueError as e:
|
|
226
|
+
return {"success": False, "error": str(e)}
|
|
227
|
+
|
|
192
228
|
messages = message_manager.get_messages(
|
|
193
|
-
to_session=
|
|
229
|
+
to_session=resolved_session_id,
|
|
194
230
|
unread_only=unread_only,
|
|
195
231
|
)
|
|
196
232
|
|
|
@@ -248,7 +284,7 @@ def add_messaging_tools(
|
|
|
248
284
|
|
|
249
285
|
@registry.tool(
|
|
250
286
|
name="broadcast_to_children",
|
|
251
|
-
description="Broadcast a message to all
|
|
287
|
+
description="Broadcast a message to all active child sessions. Accepts #N, N, UUID, or prefix for session_id.",
|
|
252
288
|
)
|
|
253
289
|
async def broadcast_to_children(
|
|
254
290
|
parent_session_id: str,
|
|
@@ -256,13 +292,14 @@ def add_messaging_tools(
|
|
|
256
292
|
priority: str = "normal",
|
|
257
293
|
) -> dict[str, Any]:
|
|
258
294
|
"""
|
|
259
|
-
Broadcast a message to all
|
|
295
|
+
Broadcast a message to all active children.
|
|
260
296
|
|
|
261
|
-
Send the same message to all child sessions spawned by this parent
|
|
297
|
+
Send the same message to all child sessions spawned by this parent
|
|
298
|
+
that are currently active in the database.
|
|
262
299
|
Useful for coordination or shutdown signals.
|
|
263
300
|
|
|
264
301
|
Args:
|
|
265
|
-
parent_session_id:
|
|
302
|
+
parent_session_id: Session reference (accepts #N, N, UUID, or prefix) for the parent
|
|
266
303
|
content: Message content to broadcast
|
|
267
304
|
priority: Message priority ("normal" or "urgent")
|
|
268
305
|
|
|
@@ -270,13 +307,22 @@ def add_messaging_tools(
|
|
|
270
307
|
Dict with success status and count of messages sent
|
|
271
308
|
"""
|
|
272
309
|
try:
|
|
273
|
-
|
|
310
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
311
|
+
try:
|
|
312
|
+
resolved_parent_id = _resolve_session_id(parent_session_id)
|
|
313
|
+
except ValueError as e:
|
|
314
|
+
return {"success": False, "error": str(e)}
|
|
315
|
+
|
|
316
|
+
# Get all children from database
|
|
317
|
+
all_children = session_manager.find_children(resolved_parent_id)
|
|
318
|
+
# Filter to active children only
|
|
319
|
+
children = [c for c in all_children if c.status == "active"]
|
|
274
320
|
|
|
275
321
|
if not children:
|
|
276
322
|
return {
|
|
277
323
|
"success": True,
|
|
278
324
|
"sent_count": 0,
|
|
279
|
-
"message": "No
|
|
325
|
+
"message": "No active children found",
|
|
280
326
|
}
|
|
281
327
|
|
|
282
328
|
sent_count = 0
|
|
@@ -285,14 +331,14 @@ def add_messaging_tools(
|
|
|
285
331
|
for child in children:
|
|
286
332
|
try:
|
|
287
333
|
message_manager.create_message(
|
|
288
|
-
from_session=
|
|
289
|
-
to_session=child.
|
|
334
|
+
from_session=resolved_parent_id,
|
|
335
|
+
to_session=child.id,
|
|
290
336
|
content=content,
|
|
291
337
|
priority=priority,
|
|
292
338
|
)
|
|
293
339
|
sent_count += 1
|
|
294
340
|
except Exception as e:
|
|
295
|
-
errors.append(f"{child.
|
|
341
|
+
errors.append(f"{child.id}: {e}")
|
|
296
342
|
|
|
297
343
|
result: dict[str, Any] = {
|
|
298
344
|
"success": True,
|
|
@@ -304,13 +350,16 @@ def add_messaging_tools(
|
|
|
304
350
|
result["errors"] = errors
|
|
305
351
|
|
|
306
352
|
logger.info(
|
|
307
|
-
|
|
353
|
+
"Broadcast from %s sent to %d/%d children",
|
|
354
|
+
resolved_parent_id,
|
|
355
|
+
sent_count,
|
|
356
|
+
len(children),
|
|
308
357
|
)
|
|
309
358
|
|
|
310
359
|
return result
|
|
311
360
|
|
|
312
361
|
except Exception as e:
|
|
313
|
-
logger.error(
|
|
362
|
+
logger.error("Failed to broadcast to children: %s", e)
|
|
314
363
|
return {
|
|
315
364
|
"success": False,
|
|
316
365
|
"error": str(e),
|
gobby/mcp_proxy/tools/agents.py
CHANGED
|
@@ -32,6 +32,7 @@ def create_agents_registry(
|
|
|
32
32
|
runner: AgentRunner,
|
|
33
33
|
running_registry: RunningAgentRegistry | None = None,
|
|
34
34
|
workflow_state_manager: Any | None = None,
|
|
35
|
+
session_manager: Any | None = None,
|
|
35
36
|
# spawn_agent dependencies
|
|
36
37
|
agent_loader: Any | None = None,
|
|
37
38
|
task_manager: Any | None = None,
|
|
@@ -48,6 +49,7 @@ def create_agents_registry(
|
|
|
48
49
|
running_registry: Optional in-memory registry for running agents.
|
|
49
50
|
workflow_state_manager: Optional WorkflowStateManager for stopping workflows
|
|
50
51
|
when agents are killed. If not provided, workflow stop will be skipped.
|
|
52
|
+
session_manager: Optional LocalSessionManager for resolving session references.
|
|
51
53
|
agent_loader: Agent definition loader for spawn_agent.
|
|
52
54
|
task_manager: Task manager for spawn_agent task resolution.
|
|
53
55
|
worktree_storage: Worktree storage for spawn_agent isolation.
|
|
@@ -58,6 +60,16 @@ def create_agents_registry(
|
|
|
58
60
|
Returns:
|
|
59
61
|
InternalToolRegistry with all agent tools registered.
|
|
60
62
|
"""
|
|
63
|
+
from gobby.utils.project_context import get_project_context
|
|
64
|
+
|
|
65
|
+
def _resolve_session_id(ref: str) -> str:
|
|
66
|
+
"""Resolve session reference (#N, N, UUID, or prefix) to UUID."""
|
|
67
|
+
if session_manager is None:
|
|
68
|
+
return ref # No resolution available, return as-is
|
|
69
|
+
project_ctx = get_project_context()
|
|
70
|
+
project_id = project_ctx.get("id") if project_ctx else None
|
|
71
|
+
return str(session_manager.resolve_session_reference(ref, project_id))
|
|
72
|
+
|
|
61
73
|
registry = InternalToolRegistry(
|
|
62
74
|
name="gobby-agents",
|
|
63
75
|
description="Agent spawning - start, monitor, and manage subagents",
|
|
@@ -105,7 +117,7 @@ def create_agents_registry(
|
|
|
105
117
|
|
|
106
118
|
@registry.tool(
|
|
107
119
|
name="list_agents",
|
|
108
|
-
description="List agent runs for a session.",
|
|
120
|
+
description="List agent runs for a session. Accepts #N, N, UUID, or prefix for session_id.",
|
|
109
121
|
)
|
|
110
122
|
async def list_agents(
|
|
111
123
|
parent_session_id: str,
|
|
@@ -116,14 +128,20 @@ def create_agents_registry(
|
|
|
116
128
|
List agent runs for a session.
|
|
117
129
|
|
|
118
130
|
Args:
|
|
119
|
-
parent_session_id:
|
|
131
|
+
parent_session_id: Session reference (accepts #N, N, UUID, or prefix) for the parent.
|
|
120
132
|
status: Optional status filter (pending, running, success, error, timeout, cancelled).
|
|
121
133
|
limit: Maximum results (default: 20).
|
|
122
134
|
|
|
123
135
|
Returns:
|
|
124
136
|
Dict with list of agent runs.
|
|
125
137
|
"""
|
|
126
|
-
|
|
138
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
139
|
+
try:
|
|
140
|
+
resolved_parent_id = _resolve_session_id(parent_session_id)
|
|
141
|
+
except ValueError as e:
|
|
142
|
+
return {"success": False, "error": str(e)}
|
|
143
|
+
|
|
144
|
+
runs = runner.list_runs(resolved_parent_id, status=status, limit=limit)
|
|
127
145
|
|
|
128
146
|
return {
|
|
129
147
|
"success": True,
|
|
@@ -221,6 +239,12 @@ def create_agents_registry(
|
|
|
221
239
|
agent = agent_registry.get(run_id)
|
|
222
240
|
session_id = agent.session_id if agent else None
|
|
223
241
|
|
|
242
|
+
# Database fallback: if not in registry, look up from DB
|
|
243
|
+
if session_id is None:
|
|
244
|
+
db_run = runner.get_run(run_id)
|
|
245
|
+
if db_run and db_run.child_session_id:
|
|
246
|
+
session_id = db_run.child_session_id
|
|
247
|
+
|
|
224
248
|
# Kill via registry (run in thread to avoid blocking event loop)
|
|
225
249
|
import asyncio
|
|
226
250
|
|
|
@@ -245,7 +269,7 @@ def create_agents_registry(
|
|
|
245
269
|
|
|
246
270
|
@registry.tool(
|
|
247
271
|
name="can_spawn_agent",
|
|
248
|
-
description="Check if an agent can be spawned from the current session.",
|
|
272
|
+
description="Check if an agent can be spawned from the current session. Accepts #N, N, UUID, or prefix for session_id.",
|
|
249
273
|
)
|
|
250
274
|
async def can_spawn_agent(parent_session_id: str) -> dict[str, Any]:
|
|
251
275
|
"""
|
|
@@ -254,12 +278,18 @@ def create_agents_registry(
|
|
|
254
278
|
This checks the agent depth limit to prevent infinite nesting.
|
|
255
279
|
|
|
256
280
|
Args:
|
|
257
|
-
parent_session_id:
|
|
281
|
+
parent_session_id: Session reference (accepts #N, N, UUID, or prefix) for the session that would spawn the agent.
|
|
258
282
|
|
|
259
283
|
Returns:
|
|
260
284
|
Dict with can_spawn boolean and reason.
|
|
261
285
|
"""
|
|
262
|
-
|
|
286
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
287
|
+
try:
|
|
288
|
+
resolved_parent_id = _resolve_session_id(parent_session_id)
|
|
289
|
+
except ValueError as e:
|
|
290
|
+
return {"can_spawn": False, "reason": str(e)}
|
|
291
|
+
|
|
292
|
+
can_spawn, reason, _parent_depth = runner.can_spawn(resolved_parent_id)
|
|
263
293
|
return {
|
|
264
294
|
"can_spawn": can_spawn,
|
|
265
295
|
"reason": reason,
|
|
@@ -267,7 +297,7 @@ def create_agents_registry(
|
|
|
267
297
|
|
|
268
298
|
@registry.tool(
|
|
269
299
|
name="list_running_agents",
|
|
270
|
-
description="List all currently running agents (in-memory process state).",
|
|
300
|
+
description="List all currently running agents (in-memory process state). Accepts #N, N, UUID, or prefix for session_id.",
|
|
271
301
|
)
|
|
272
302
|
async def list_running_agents(
|
|
273
303
|
parent_session_id: str | None = None,
|
|
@@ -280,14 +310,19 @@ def create_agents_registry(
|
|
|
280
310
|
including PIDs and process handles not stored in the database.
|
|
281
311
|
|
|
282
312
|
Args:
|
|
283
|
-
parent_session_id: Optional filter by parent
|
|
313
|
+
parent_session_id: Optional session reference (accepts #N, N, UUID, or prefix) to filter by parent.
|
|
284
314
|
mode: Optional filter by execution mode (terminal, embedded, headless).
|
|
285
315
|
|
|
286
316
|
Returns:
|
|
287
317
|
Dict with list of running agents.
|
|
288
318
|
"""
|
|
289
319
|
if parent_session_id:
|
|
290
|
-
|
|
320
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
321
|
+
try:
|
|
322
|
+
resolved_parent_id = _resolve_session_id(parent_session_id)
|
|
323
|
+
except ValueError as e:
|
|
324
|
+
return {"success": False, "error": str(e)}
|
|
325
|
+
agents = agent_registry.list_by_parent(resolved_parent_id)
|
|
291
326
|
elif mode:
|
|
292
327
|
agents = agent_registry.list_by_mode(mode)
|
|
293
328
|
else:
|
|
@@ -394,6 +429,7 @@ def create_agents_registry(
|
|
|
394
429
|
git_manager=git_manager,
|
|
395
430
|
clone_storage=clone_storage,
|
|
396
431
|
clone_manager=clone_manager,
|
|
432
|
+
session_manager=session_manager,
|
|
397
433
|
)
|
|
398
434
|
|
|
399
435
|
# Merge spawn_agent tools into agents registry
|
|
@@ -25,6 +25,7 @@ if TYPE_CHECKING:
|
|
|
25
25
|
def create_artifacts_registry(
|
|
26
26
|
db: LocalDatabase | None = None,
|
|
27
27
|
artifact_manager: LocalArtifactManager | None = None,
|
|
28
|
+
session_manager: Any | None = None,
|
|
28
29
|
) -> InternalToolRegistry:
|
|
29
30
|
"""
|
|
30
31
|
Create an artifacts tool registry with all artifact-related tools.
|
|
@@ -32,10 +33,21 @@ def create_artifacts_registry(
|
|
|
32
33
|
Args:
|
|
33
34
|
db: LocalDatabase instance (used to create artifact_manager if not provided)
|
|
34
35
|
artifact_manager: LocalArtifactManager instance
|
|
36
|
+
session_manager: Session manager for resolving session references
|
|
35
37
|
|
|
36
38
|
Returns:
|
|
37
39
|
InternalToolRegistry with artifact tools registered
|
|
38
40
|
"""
|
|
41
|
+
from gobby.utils.project_context import get_project_context
|
|
42
|
+
|
|
43
|
+
def _resolve_session_id(ref: str) -> str:
|
|
44
|
+
"""Resolve session reference (#N, N, UUID, or prefix) to UUID."""
|
|
45
|
+
if session_manager is None:
|
|
46
|
+
return ref # No resolution available, return as-is
|
|
47
|
+
ctx = get_project_context()
|
|
48
|
+
project_id = ctx.get("id") if ctx else None
|
|
49
|
+
return str(session_manager.resolve_session_reference(ref, project_id))
|
|
50
|
+
|
|
39
51
|
# Create artifact manager if not provided
|
|
40
52
|
if artifact_manager is None:
|
|
41
53
|
if db is None:
|
|
@@ -55,7 +67,7 @@ def create_artifacts_registry(
|
|
|
55
67
|
|
|
56
68
|
@registry.tool(
|
|
57
69
|
name="search_artifacts",
|
|
58
|
-
description="Search artifacts by content using full-text search.",
|
|
70
|
+
description="Search artifacts by content using full-text search. Accepts #N, N, UUID, or prefix for session_id.",
|
|
59
71
|
)
|
|
60
72
|
def search_artifacts(
|
|
61
73
|
query: str,
|
|
@@ -68,7 +80,7 @@ def create_artifacts_registry(
|
|
|
68
80
|
|
|
69
81
|
Args:
|
|
70
82
|
query: Search query text
|
|
71
|
-
session_id: Optional session
|
|
83
|
+
session_id: Optional session reference (accepts #N, N, UUID, or prefix) to filter by
|
|
72
84
|
artifact_type: Optional artifact type to filter by (code, diff, error, etc.)
|
|
73
85
|
limit: Maximum number of results (default: 50)
|
|
74
86
|
|
|
@@ -78,10 +90,18 @@ def create_artifacts_registry(
|
|
|
78
90
|
if not query or not query.strip():
|
|
79
91
|
return {"success": True, "artifacts": [], "count": 0}
|
|
80
92
|
|
|
93
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
94
|
+
resolved_session_id = session_id
|
|
95
|
+
if session_id:
|
|
96
|
+
try:
|
|
97
|
+
resolved_session_id = _resolve_session_id(session_id)
|
|
98
|
+
except ValueError as e:
|
|
99
|
+
return {"success": False, "error": str(e), "artifacts": []}
|
|
100
|
+
|
|
81
101
|
try:
|
|
82
102
|
artifacts = _artifact_manager.search_artifacts(
|
|
83
103
|
query_text=query,
|
|
84
|
-
session_id=
|
|
104
|
+
session_id=resolved_session_id,
|
|
85
105
|
artifact_type=artifact_type,
|
|
86
106
|
limit=limit,
|
|
87
107
|
)
|
|
@@ -95,7 +115,7 @@ def create_artifacts_registry(
|
|
|
95
115
|
|
|
96
116
|
@registry.tool(
|
|
97
117
|
name="list_artifacts",
|
|
98
|
-
description="List artifacts with optional filters.",
|
|
118
|
+
description="List artifacts with optional filters. Accepts #N, N, UUID, or prefix for session_id.",
|
|
99
119
|
)
|
|
100
120
|
def list_artifacts(
|
|
101
121
|
session_id: str | None = None,
|
|
@@ -107,7 +127,7 @@ def create_artifacts_registry(
|
|
|
107
127
|
List artifacts with optional filters.
|
|
108
128
|
|
|
109
129
|
Args:
|
|
110
|
-
session_id: Optional session
|
|
130
|
+
session_id: Optional session reference (accepts #N, N, UUID, or prefix) to filter by
|
|
111
131
|
artifact_type: Optional artifact type to filter by
|
|
112
132
|
limit: Maximum number of results (default: 100)
|
|
113
133
|
offset: Offset for pagination (default: 0)
|
|
@@ -115,9 +135,17 @@ def create_artifacts_registry(
|
|
|
115
135
|
Returns:
|
|
116
136
|
Dict with success status and list of artifacts
|
|
117
137
|
"""
|
|
138
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
139
|
+
resolved_session_id = session_id
|
|
140
|
+
if session_id:
|
|
141
|
+
try:
|
|
142
|
+
resolved_session_id = _resolve_session_id(session_id)
|
|
143
|
+
except ValueError as e:
|
|
144
|
+
return {"success": False, "error": str(e), "artifacts": []}
|
|
145
|
+
|
|
118
146
|
try:
|
|
119
147
|
artifacts = _artifact_manager.list_artifacts(
|
|
120
|
-
session_id=
|
|
148
|
+
session_id=resolved_session_id,
|
|
121
149
|
artifact_type=artifact_type,
|
|
122
150
|
limit=limit,
|
|
123
151
|
offset=offset,
|
|
@@ -161,7 +189,7 @@ def create_artifacts_registry(
|
|
|
161
189
|
|
|
162
190
|
@registry.tool(
|
|
163
191
|
name="get_timeline",
|
|
164
|
-
description="Get artifacts for a session in chronological order.",
|
|
192
|
+
description="Get artifacts for a session in chronological order. Accepts #N, N, UUID, or prefix for session_id.",
|
|
165
193
|
)
|
|
166
194
|
def get_timeline(
|
|
167
195
|
session_id: str | None = None,
|
|
@@ -172,7 +200,7 @@ def create_artifacts_registry(
|
|
|
172
200
|
Get artifacts for a session in chronological order (oldest first).
|
|
173
201
|
|
|
174
202
|
Args:
|
|
175
|
-
session_id: Required session
|
|
203
|
+
session_id: Required session reference (accepts #N, N, UUID, or prefix) to get timeline for
|
|
176
204
|
artifact_type: Optional artifact type to filter by
|
|
177
205
|
limit: Maximum number of results (default: 100)
|
|
178
206
|
|
|
@@ -186,10 +214,16 @@ def create_artifacts_registry(
|
|
|
186
214
|
"artifacts": [],
|
|
187
215
|
}
|
|
188
216
|
|
|
217
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
218
|
+
try:
|
|
219
|
+
resolved_session_id = _resolve_session_id(session_id)
|
|
220
|
+
except ValueError as e:
|
|
221
|
+
return {"success": False, "error": str(e), "artifacts": []}
|
|
222
|
+
|
|
189
223
|
try:
|
|
190
224
|
# Get artifacts (list_artifacts returns newest first by default)
|
|
191
225
|
artifacts = _artifact_manager.list_artifacts(
|
|
192
|
-
session_id=
|
|
226
|
+
session_id=resolved_session_id,
|
|
193
227
|
artifact_type=artifact_type,
|
|
194
228
|
limit=limit,
|
|
195
229
|
offset=0,
|