gobby 0.2.6__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/__init__.py +1 -1
- gobby/adapters/__init__.py +2 -1
- gobby/adapters/claude_code.py +96 -35
- gobby/adapters/codex_impl/__init__.py +28 -0
- gobby/adapters/codex_impl/adapter.py +722 -0
- gobby/adapters/codex_impl/client.py +679 -0
- gobby/adapters/codex_impl/protocol.py +20 -0
- gobby/adapters/codex_impl/types.py +68 -0
- gobby/adapters/gemini.py +140 -38
- gobby/agents/definitions.py +11 -1
- gobby/agents/isolation.py +525 -0
- gobby/agents/registry.py +11 -0
- gobby/agents/sandbox.py +261 -0
- gobby/agents/session.py +1 -0
- gobby/agents/spawn.py +42 -287
- gobby/agents/spawn_executor.py +415 -0
- gobby/agents/spawners/__init__.py +24 -0
- gobby/agents/spawners/command_builder.py +189 -0
- gobby/agents/spawners/embedded.py +21 -2
- gobby/agents/spawners/headless.py +21 -2
- gobby/agents/spawners/macos.py +26 -1
- gobby/agents/spawners/prompt_manager.py +125 -0
- gobby/cli/__init__.py +0 -2
- gobby/cli/install.py +4 -4
- gobby/cli/installers/claude.py +6 -0
- gobby/cli/installers/gemini.py +6 -0
- gobby/cli/installers/shared.py +103 -4
- gobby/cli/memory.py +185 -0
- gobby/cli/sessions.py +1 -1
- gobby/cli/utils.py +9 -2
- gobby/clones/git.py +177 -0
- gobby/config/__init__.py +12 -97
- gobby/config/app.py +10 -94
- gobby/config/extensions.py +2 -2
- gobby/config/features.py +7 -130
- gobby/config/skills.py +31 -0
- gobby/config/tasks.py +4 -28
- gobby/hooks/__init__.py +0 -13
- gobby/hooks/event_handlers.py +150 -8
- gobby/hooks/hook_manager.py +21 -3
- gobby/hooks/plugins.py +1 -1
- gobby/hooks/webhooks.py +1 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +74 -15
- gobby/llm/resolver.py +3 -2
- gobby/mcp_proxy/importer.py +62 -4
- gobby/mcp_proxy/instructions.py +4 -2
- gobby/mcp_proxy/registries.py +22 -8
- gobby/mcp_proxy/services/recommendation.py +43 -11
- gobby/mcp_proxy/tools/agent_messaging.py +93 -44
- gobby/mcp_proxy/tools/agents.py +76 -740
- gobby/mcp_proxy/tools/artifacts.py +43 -9
- gobby/mcp_proxy/tools/clones.py +0 -385
- gobby/mcp_proxy/tools/memory.py +2 -2
- gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
- gobby/mcp_proxy/tools/sessions/_commits.py +239 -0
- gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
- gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
- gobby/mcp_proxy/tools/sessions/_handoff.py +503 -0
- gobby/mcp_proxy/tools/sessions/_messages.py +166 -0
- gobby/mcp_proxy/tools/skills/__init__.py +14 -29
- gobby/mcp_proxy/tools/spawn_agent.py +455 -0
- 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 +79 -30
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +1 -1
- gobby/mcp_proxy/tools/tasks/_session.py +22 -7
- gobby/mcp_proxy/tools/workflows.py +84 -34
- gobby/mcp_proxy/tools/worktrees.py +32 -350
- gobby/memory/extractor.py +15 -1
- gobby/memory/ingestion/__init__.py +5 -0
- gobby/memory/ingestion/multimodal.py +221 -0
- gobby/memory/manager.py +62 -283
- gobby/memory/search/__init__.py +10 -0
- gobby/memory/search/coordinator.py +248 -0
- gobby/memory/services/__init__.py +5 -0
- gobby/memory/services/crossref.py +142 -0
- gobby/prompts/loader.py +5 -2
- gobby/runner.py +13 -0
- gobby/servers/http.py +1 -4
- gobby/servers/routes/admin.py +14 -0
- gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
- gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
- gobby/servers/routes/mcp/endpoints/execution.py +568 -0
- gobby/servers/routes/mcp/endpoints/registry.py +378 -0
- gobby/servers/routes/mcp/endpoints/server.py +304 -0
- gobby/servers/routes/mcp/hooks.py +51 -4
- gobby/servers/routes/mcp/tools.py +48 -1506
- gobby/servers/websocket.py +57 -1
- gobby/sessions/analyzer.py +2 -2
- gobby/sessions/lifecycle.py +1 -1
- gobby/sessions/manager.py +9 -0
- gobby/sessions/processor.py +10 -0
- gobby/sessions/transcripts/base.py +1 -0
- gobby/sessions/transcripts/claude.py +15 -5
- gobby/sessions/transcripts/gemini.py +100 -34
- gobby/skills/parser.py +30 -2
- gobby/storage/database.py +9 -2
- gobby/storage/memories.py +32 -21
- gobby/storage/migrations.py +174 -368
- gobby/storage/sessions.py +45 -7
- gobby/storage/skills.py +80 -7
- gobby/storage/tasks/_lifecycle.py +18 -3
- gobby/sync/memories.py +1 -1
- gobby/tasks/external_validator.py +1 -1
- gobby/tasks/validation.py +22 -20
- gobby/tools/summarizer.py +91 -10
- gobby/utils/project_context.py +2 -3
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +221 -1217
- gobby/workflows/artifact_actions.py +31 -0
- gobby/workflows/autonomous_actions.py +11 -0
- gobby/workflows/context_actions.py +50 -1
- gobby/workflows/detection_helpers.py +38 -24
- gobby/workflows/enforcement/__init__.py +47 -0
- gobby/workflows/enforcement/blocking.py +281 -0
- gobby/workflows/enforcement/commit_policy.py +283 -0
- gobby/workflows/enforcement/handlers.py +269 -0
- gobby/workflows/enforcement/task_policy.py +542 -0
- gobby/workflows/engine.py +93 -0
- gobby/workflows/evaluator.py +110 -0
- gobby/workflows/git_utils.py +106 -0
- gobby/workflows/hooks.py +41 -0
- gobby/workflows/llm_actions.py +30 -0
- gobby/workflows/mcp_actions.py +20 -1
- gobby/workflows/memory_actions.py +91 -0
- gobby/workflows/safe_evaluator.py +191 -0
- gobby/workflows/session_actions.py +44 -0
- gobby/workflows/state_actions.py +60 -1
- gobby/workflows/stop_signal_actions.py +55 -0
- gobby/workflows/summary_actions.py +217 -51
- gobby/workflows/task_sync_actions.py +347 -0
- gobby/workflows/todo_actions.py +34 -1
- gobby/workflows/webhook_actions.py +185 -0
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/METADATA +6 -1
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/RECORD +139 -163
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/WHEEL +1 -1
- gobby/adapters/codex.py +0 -1332
- gobby/cli/tui.py +0 -34
- gobby/install/claude/commands/gobby/bug.md +0 -51
- gobby/install/claude/commands/gobby/chore.md +0 -51
- gobby/install/claude/commands/gobby/epic.md +0 -52
- gobby/install/claude/commands/gobby/eval.md +0 -235
- gobby/install/claude/commands/gobby/feat.md +0 -49
- gobby/install/claude/commands/gobby/nit.md +0 -52
- gobby/install/claude/commands/gobby/ref.md +0 -52
- gobby/mcp_proxy/tools/session_messages.py +0 -1055
- gobby/prompts/defaults/expansion/system.md +0 -119
- gobby/prompts/defaults/expansion/user.md +0 -48
- gobby/prompts/defaults/external_validation/agent.md +0 -72
- gobby/prompts/defaults/external_validation/external.md +0 -63
- gobby/prompts/defaults/external_validation/spawn.md +0 -83
- gobby/prompts/defaults/external_validation/system.md +0 -6
- gobby/prompts/defaults/features/import_mcp.md +0 -22
- gobby/prompts/defaults/features/import_mcp_github.md +0 -17
- gobby/prompts/defaults/features/import_mcp_search.md +0 -16
- gobby/prompts/defaults/features/recommend_tools.md +0 -32
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
- gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
- gobby/prompts/defaults/features/server_description.md +0 -20
- gobby/prompts/defaults/features/server_description_system.md +0 -6
- gobby/prompts/defaults/features/task_description.md +0 -31
- gobby/prompts/defaults/features/task_description_system.md +0 -6
- gobby/prompts/defaults/features/tool_summary.md +0 -17
- gobby/prompts/defaults/features/tool_summary_system.md +0 -6
- gobby/prompts/defaults/handoff/compact.md +0 -63
- gobby/prompts/defaults/handoff/session_end.md +0 -57
- gobby/prompts/defaults/memory/extract.md +0 -61
- gobby/prompts/defaults/research/step.md +0 -58
- gobby/prompts/defaults/validation/criteria.md +0 -47
- gobby/prompts/defaults/validation/validate.md +0 -38
- gobby/storage/migrations_legacy.py +0 -1359
- 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/workflows/task_enforcement_actions.py +0 -1343
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/top_level.txt +0 -0
|
@@ -65,13 +65,13 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
65
65
|
try:
|
|
66
66
|
resolved_id = resolve_task_id_for_mcp(ctx.task_manager, task_id)
|
|
67
67
|
except TaskNotFoundError as e:
|
|
68
|
-
return {"error": str(e)}
|
|
68
|
+
return {"success": False, "error": str(e)}
|
|
69
69
|
except ValueError as e:
|
|
70
|
-
return {"error": str(e)}
|
|
70
|
+
return {"success": False, "error": str(e)}
|
|
71
71
|
|
|
72
72
|
task = ctx.task_manager.get_task(resolved_id)
|
|
73
73
|
if not task:
|
|
74
|
-
return {"error": f"Task {task_id} not found"}
|
|
74
|
+
return {"success": False, "error": f"Task {task_id} not found"}
|
|
75
75
|
|
|
76
76
|
# Link commit if provided (convenience for link + close in one call)
|
|
77
77
|
if commit_sha:
|
|
@@ -85,6 +85,7 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
85
85
|
commit_result = validate_commit_requirements(task, reason, repo_path)
|
|
86
86
|
if not commit_result.can_close:
|
|
87
87
|
return {
|
|
88
|
+
"success": False,
|
|
88
89
|
"error": commit_result.error_type,
|
|
89
90
|
"message": commit_result.message,
|
|
90
91
|
}
|
|
@@ -92,11 +93,48 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
92
93
|
# Auto-skip validation for certain close reasons
|
|
93
94
|
should_skip = skip_validation or reason.lower() in SKIP_REASONS
|
|
94
95
|
|
|
96
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
97
|
+
resolved_session_id = session_id
|
|
98
|
+
if session_id:
|
|
99
|
+
try:
|
|
100
|
+
resolved_session_id = ctx.resolve_session_id(session_id)
|
|
101
|
+
except ValueError:
|
|
102
|
+
pass # Fall back to raw value if resolution fails
|
|
103
|
+
|
|
104
|
+
# Enforce commits if session had edits
|
|
105
|
+
if resolved_session_id and not should_skip:
|
|
106
|
+
try:
|
|
107
|
+
from gobby.storage.sessions import LocalSessionManager
|
|
108
|
+
|
|
109
|
+
session_manager = LocalSessionManager(ctx.task_manager.db)
|
|
110
|
+
session = session_manager.get(resolved_session_id)
|
|
111
|
+
|
|
112
|
+
# Check if task has commits (including the one being linked right now)
|
|
113
|
+
has_commits = bool(task.commits) or bool(commit_sha)
|
|
114
|
+
|
|
115
|
+
if session and session.had_edits and not has_commits:
|
|
116
|
+
return {
|
|
117
|
+
"success": False,
|
|
118
|
+
"error": "missing_commits_for_edits",
|
|
119
|
+
"message": (
|
|
120
|
+
"This session made edits but no commits are linked to the task. "
|
|
121
|
+
"You must commit your changes and link them to the task before closing."
|
|
122
|
+
),
|
|
123
|
+
"suggestion": (
|
|
124
|
+
"Commit your changes with `[#task_id]` in the message, "
|
|
125
|
+
"or pass `commit_sha` to `close_task`."
|
|
126
|
+
),
|
|
127
|
+
}
|
|
128
|
+
except Exception:
|
|
129
|
+
# Don't block close on internal error
|
|
130
|
+
pass # nosec B110 - best-effort session edit check
|
|
131
|
+
|
|
95
132
|
if not should_skip:
|
|
96
133
|
# Check if task has children (is a parent task)
|
|
97
134
|
parent_result = validate_parent_task(ctx, resolved_id)
|
|
98
135
|
if not parent_result.can_close:
|
|
99
|
-
response = {
|
|
136
|
+
response: dict[str, Any] = {
|
|
137
|
+
"success": False,
|
|
100
138
|
"error": parent_result.error_type,
|
|
101
139
|
"message": parent_result.message,
|
|
102
140
|
}
|
|
@@ -127,6 +165,7 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
127
165
|
)
|
|
128
166
|
if not llm_result.can_close:
|
|
129
167
|
response = {
|
|
168
|
+
"success": False,
|
|
130
169
|
"error": llm_result.error_type,
|
|
131
170
|
"message": llm_result.message,
|
|
132
171
|
}
|
|
@@ -154,9 +193,9 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
154
193
|
)
|
|
155
194
|
|
|
156
195
|
# Auto-link session if provided
|
|
157
|
-
if
|
|
196
|
+
if resolved_session_id:
|
|
158
197
|
try:
|
|
159
|
-
ctx.session_task_manager.link_task(
|
|
198
|
+
ctx.session_task_manager.link_task(resolved_session_id, resolved_id, "review")
|
|
160
199
|
except Exception:
|
|
161
200
|
pass # nosec B110 - best-effort linking
|
|
162
201
|
|
|
@@ -177,15 +216,15 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
177
216
|
ctx.task_manager.close_task(
|
|
178
217
|
resolved_id,
|
|
179
218
|
reason=reason,
|
|
180
|
-
closed_in_session_id=
|
|
219
|
+
closed_in_session_id=resolved_session_id,
|
|
181
220
|
closed_commit_sha=current_commit_sha,
|
|
182
221
|
validation_override_reason=override_justification if store_override else None,
|
|
183
222
|
)
|
|
184
223
|
|
|
185
224
|
# Auto-link session if provided
|
|
186
|
-
if
|
|
225
|
+
if resolved_session_id:
|
|
187
226
|
try:
|
|
188
|
-
ctx.session_task_manager.link_task(
|
|
227
|
+
ctx.session_task_manager.link_task(resolved_session_id, resolved_id, "closed")
|
|
189
228
|
except Exception:
|
|
190
229
|
pass # nosec B110 - best-effort linking, don't fail the close
|
|
191
230
|
|
|
@@ -193,9 +232,9 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
193
232
|
# Respects the clear_task_on_close variable (defaults to True if not set)
|
|
194
233
|
# This is done here because Claude Code's post-tool-use hook doesn't include
|
|
195
234
|
# the tool result, so the detection_helpers can't verify close succeeded
|
|
196
|
-
if
|
|
235
|
+
if resolved_session_id:
|
|
197
236
|
try:
|
|
198
|
-
state = ctx.workflow_state_manager.get_state(
|
|
237
|
+
state = ctx.workflow_state_manager.get_state(resolved_session_id)
|
|
199
238
|
if state and state.variables.get("claimed_task_id") == resolved_id:
|
|
200
239
|
# Check if clear_task_on_close is enabled (default: True)
|
|
201
240
|
clear_on_close = state.variables.get("clear_task_on_close", True)
|
|
@@ -257,7 +296,7 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
257
296
|
},
|
|
258
297
|
"session_id": {
|
|
259
298
|
"type": "string",
|
|
260
|
-
"description": "Your session ID (
|
|
299
|
+
"description": "Your session ID (accepts #N, N, UUID, or prefix). Pass this to track which session closed the task.",
|
|
261
300
|
"default": None,
|
|
262
301
|
},
|
|
263
302
|
"override_justification": {
|
|
@@ -292,7 +331,7 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
292
331
|
try:
|
|
293
332
|
resolved_id = resolve_task_id_for_mcp(ctx.task_manager, task_id)
|
|
294
333
|
except (TaskNotFoundError, ValueError) as e:
|
|
295
|
-
return {"error": str(e)}
|
|
334
|
+
return {"success": False, "error": str(e)}
|
|
296
335
|
|
|
297
336
|
try:
|
|
298
337
|
ctx.task_manager.reopen_task(resolved_id, reason=reason)
|
|
@@ -313,7 +352,7 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
313
352
|
|
|
314
353
|
return {}
|
|
315
354
|
except ValueError as e:
|
|
316
|
-
return {"error": str(e)}
|
|
355
|
+
return {"success": False, "error": str(e)}
|
|
317
356
|
|
|
318
357
|
registry.register(
|
|
319
358
|
name="reopen_task",
|
|
@@ -345,22 +384,23 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
345
384
|
try:
|
|
346
385
|
resolved_id = resolve_task_id_for_mcp(ctx.task_manager, task_id)
|
|
347
386
|
except (TaskNotFoundError, ValueError) as e:
|
|
348
|
-
return {"error": str(e)}
|
|
387
|
+
return {"success": False, "error": str(e)}
|
|
349
388
|
|
|
350
389
|
# Get task before deleting to capture seq_num for ref
|
|
351
390
|
task = ctx.task_manager.get_task(resolved_id)
|
|
352
391
|
if not task:
|
|
353
|
-
return {"error": f"Task {task_id} not found"}
|
|
392
|
+
return {"success": False, "error": f"Task {task_id} not found"}
|
|
354
393
|
ref = f"#{task.seq_num}" if task.seq_num else resolved_id[:8]
|
|
355
394
|
|
|
356
395
|
try:
|
|
357
396
|
deleted = ctx.task_manager.delete_task(resolved_id, cascade=cascade, unlink=unlink)
|
|
358
397
|
if not deleted:
|
|
359
|
-
return {"error": f"Task {task_id} not found"}
|
|
398
|
+
return {"success": False, "error": f"Task {task_id} not found"}
|
|
360
399
|
except ValueError as e:
|
|
361
400
|
error_msg = str(e)
|
|
362
401
|
if "dependent task(s)" in error_msg:
|
|
363
402
|
return {
|
|
403
|
+
"success": False,
|
|
364
404
|
"error": "has_dependents",
|
|
365
405
|
"message": error_msg,
|
|
366
406
|
"suggestion": f"Use cascade=True to delete task {ref} and its dependents, "
|
|
@@ -368,11 +408,12 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
368
408
|
}
|
|
369
409
|
elif "has children" in error_msg:
|
|
370
410
|
return {
|
|
411
|
+
"success": False,
|
|
371
412
|
"error": "has_children",
|
|
372
413
|
"message": error_msg,
|
|
373
414
|
"suggestion": f"Use cascade=True to delete task {ref} and all its subtasks.",
|
|
374
415
|
}
|
|
375
|
-
return {"error": error_msg}
|
|
416
|
+
return {"success": False, "error": error_msg}
|
|
376
417
|
|
|
377
418
|
return {
|
|
378
419
|
"ref": ref,
|
|
@@ -413,10 +454,10 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
413
454
|
try:
|
|
414
455
|
resolved_id = resolve_task_id_for_mcp(ctx.task_manager, task_id)
|
|
415
456
|
except (TaskNotFoundError, ValueError) as e:
|
|
416
|
-
return {"error": str(e)}
|
|
457
|
+
return {"success": False, "error": str(e)}
|
|
417
458
|
task = ctx.task_manager.add_label(resolved_id, label)
|
|
418
459
|
if not task:
|
|
419
|
-
return {"error": f"Task {task_id} not found"}
|
|
460
|
+
return {"success": False, "error": f"Task {task_id} not found"}
|
|
420
461
|
return {}
|
|
421
462
|
|
|
422
463
|
registry.register(
|
|
@@ -441,10 +482,10 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
441
482
|
try:
|
|
442
483
|
resolved_id = resolve_task_id_for_mcp(ctx.task_manager, task_id)
|
|
443
484
|
except (TaskNotFoundError, ValueError) as e:
|
|
444
|
-
return {"error": str(e)}
|
|
485
|
+
return {"success": False, "error": str(e)}
|
|
445
486
|
task = ctx.task_manager.remove_label(resolved_id, label)
|
|
446
487
|
if not task:
|
|
447
|
-
return {"error": f"Task {task_id} not found"}
|
|
488
|
+
return {"success": False, "error": f"Task {task_id} not found"}
|
|
448
489
|
return {}
|
|
449
490
|
|
|
450
491
|
registry.register(
|
|
@@ -487,17 +528,25 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
487
528
|
try:
|
|
488
529
|
resolved_id = resolve_task_id_for_mcp(ctx.task_manager, task_id)
|
|
489
530
|
except TaskNotFoundError as e:
|
|
490
|
-
return {"error": str(e)}
|
|
531
|
+
return {"success": False, "error": str(e)}
|
|
491
532
|
except ValueError as e:
|
|
492
|
-
return {"error": str(e)}
|
|
533
|
+
return {"success": False, "error": str(e)}
|
|
493
534
|
|
|
494
535
|
task = ctx.task_manager.get_task(resolved_id)
|
|
495
536
|
if not task:
|
|
496
|
-
return {"error": f"Task {task_id} not found"}
|
|
537
|
+
return {"success": False, "error": f"Task {task_id} not found"}
|
|
538
|
+
|
|
539
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
540
|
+
resolved_session_id = session_id
|
|
541
|
+
try:
|
|
542
|
+
resolved_session_id = ctx.resolve_session_id(session_id)
|
|
543
|
+
except ValueError:
|
|
544
|
+
pass # Fall back to raw value if resolution fails
|
|
497
545
|
|
|
498
546
|
# Check if already claimed by another session
|
|
499
|
-
if task.assignee and task.assignee !=
|
|
547
|
+
if task.assignee and task.assignee != resolved_session_id and not force:
|
|
500
548
|
return {
|
|
549
|
+
"success": False,
|
|
501
550
|
"error": "Task already claimed by another session",
|
|
502
551
|
"claimed_by": task.assignee,
|
|
503
552
|
"message": f"Task is already claimed by session '{task.assignee}'. Use force=True to override.",
|
|
@@ -506,15 +555,15 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
506
555
|
# Update task with assignee and status in single atomic call
|
|
507
556
|
updated = ctx.task_manager.update_task(
|
|
508
557
|
resolved_id,
|
|
509
|
-
assignee=
|
|
558
|
+
assignee=resolved_session_id,
|
|
510
559
|
status="in_progress",
|
|
511
560
|
)
|
|
512
561
|
if not updated:
|
|
513
|
-
return {"error": f"Failed to claim task {task_id}"}
|
|
562
|
+
return {"success": False, "error": f"Failed to claim task {task_id}"}
|
|
514
563
|
|
|
515
564
|
# Link task to session (best-effort, don't fail the claim if this fails)
|
|
516
565
|
try:
|
|
517
|
-
ctx.session_task_manager.link_task(
|
|
566
|
+
ctx.session_task_manager.link_task(resolved_session_id, resolved_id, "claimed")
|
|
518
567
|
except Exception:
|
|
519
568
|
pass # nosec B110 - best-effort linking
|
|
520
569
|
|
|
@@ -532,7 +581,7 @@ def create_lifecycle_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
532
581
|
},
|
|
533
582
|
"session_id": {
|
|
534
583
|
"type": "string",
|
|
535
|
-
"description": "Your session ID (
|
|
584
|
+
"description": "Your session ID (accepts #N, N, UUID, or prefix). The session claiming the task.",
|
|
536
585
|
},
|
|
537
586
|
"force": {
|
|
538
587
|
"type": "boolean",
|
|
@@ -53,7 +53,7 @@ def validate_commit_requirements(
|
|
|
53
53
|
can_close=False,
|
|
54
54
|
error_type="no_commits_linked",
|
|
55
55
|
message=(
|
|
56
|
-
"
|
|
56
|
+
"\nA commit is required before closing this task.\n\n"
|
|
57
57
|
"**Normal flow:**\n"
|
|
58
58
|
'1. Commit your changes: git commit -m "[#N] description"\n'
|
|
59
59
|
'2. Close with commit_sha: close_task(task_id="#N", commit_sha="<sha>")\n\n'
|
|
@@ -40,15 +40,21 @@ def create_session_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
40
40
|
except (TaskNotFoundError, ValueError) as e:
|
|
41
41
|
return {"error": str(e)}
|
|
42
42
|
|
|
43
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
43
44
|
try:
|
|
44
|
-
ctx.
|
|
45
|
+
resolved_session_id = ctx.resolve_session_id(session_id)
|
|
46
|
+
except ValueError as e:
|
|
47
|
+
return {"error": f"Invalid session_id '{session_id}': {e}"}
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
ctx.session_task_manager.link_task(resolved_session_id, resolved_id, action)
|
|
45
51
|
return {}
|
|
46
52
|
except ValueError as e:
|
|
47
53
|
return {"error": str(e)}
|
|
48
54
|
|
|
49
55
|
registry.register(
|
|
50
56
|
name="link_task_to_session",
|
|
51
|
-
description="Link a task to a session.",
|
|
57
|
+
description="Link a task to a session. Accepts #N, N, UUID, or prefix for session_id.",
|
|
52
58
|
input_schema={
|
|
53
59
|
"type": "object",
|
|
54
60
|
"properties": {
|
|
@@ -58,7 +64,7 @@ def create_session_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
58
64
|
},
|
|
59
65
|
"session_id": {
|
|
60
66
|
"type": "string",
|
|
61
|
-
"description": "Session
|
|
67
|
+
"description": "Session reference (accepts #N, N, UUID, or prefix)",
|
|
62
68
|
"default": None,
|
|
63
69
|
},
|
|
64
70
|
"action": {
|
|
@@ -74,16 +80,25 @@ def create_session_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
|
74
80
|
|
|
75
81
|
def get_session_tasks(session_id: str) -> dict[str, Any]:
|
|
76
82
|
"""Get all tasks associated with a session."""
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
84
|
+
try:
|
|
85
|
+
resolved_session_id = ctx.resolve_session_id(session_id)
|
|
86
|
+
except ValueError as e:
|
|
87
|
+
return {"error": f"Invalid session_id '{session_id}': {e}"}
|
|
88
|
+
|
|
89
|
+
tasks = ctx.session_task_manager.get_session_tasks(resolved_session_id)
|
|
90
|
+
return {"session_id": resolved_session_id, "tasks": tasks}
|
|
79
91
|
|
|
80
92
|
registry.register(
|
|
81
93
|
name="get_session_tasks",
|
|
82
|
-
description="Get all tasks associated with a session.",
|
|
94
|
+
description="Get all tasks associated with a session. Accepts #N, N, UUID, or prefix for session_id.",
|
|
83
95
|
input_schema={
|
|
84
96
|
"type": "object",
|
|
85
97
|
"properties": {
|
|
86
|
-
"session_id": {
|
|
98
|
+
"session_id": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "Session reference (accepts #N, N, UUID, or prefix)",
|
|
101
|
+
},
|
|
87
102
|
},
|
|
88
103
|
"required": ["session_id"],
|
|
89
104
|
},
|