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.
Files changed (198) hide show
  1. gobby/__init__.py +1 -1
  2. gobby/adapters/__init__.py +2 -1
  3. gobby/adapters/claude_code.py +96 -35
  4. gobby/adapters/codex_impl/__init__.py +28 -0
  5. gobby/adapters/codex_impl/adapter.py +722 -0
  6. gobby/adapters/codex_impl/client.py +679 -0
  7. gobby/adapters/codex_impl/protocol.py +20 -0
  8. gobby/adapters/codex_impl/types.py +68 -0
  9. gobby/adapters/gemini.py +140 -38
  10. gobby/agents/definitions.py +11 -1
  11. gobby/agents/isolation.py +525 -0
  12. gobby/agents/registry.py +11 -0
  13. gobby/agents/sandbox.py +261 -0
  14. gobby/agents/session.py +1 -0
  15. gobby/agents/spawn.py +42 -287
  16. gobby/agents/spawn_executor.py +415 -0
  17. gobby/agents/spawners/__init__.py +24 -0
  18. gobby/agents/spawners/command_builder.py +189 -0
  19. gobby/agents/spawners/embedded.py +21 -2
  20. gobby/agents/spawners/headless.py +21 -2
  21. gobby/agents/spawners/macos.py +26 -1
  22. gobby/agents/spawners/prompt_manager.py +125 -0
  23. gobby/cli/__init__.py +0 -2
  24. gobby/cli/install.py +4 -4
  25. gobby/cli/installers/claude.py +6 -0
  26. gobby/cli/installers/gemini.py +6 -0
  27. gobby/cli/installers/shared.py +103 -4
  28. gobby/cli/memory.py +185 -0
  29. gobby/cli/sessions.py +1 -1
  30. gobby/cli/utils.py +9 -2
  31. gobby/clones/git.py +177 -0
  32. gobby/config/__init__.py +12 -97
  33. gobby/config/app.py +10 -94
  34. gobby/config/extensions.py +2 -2
  35. gobby/config/features.py +7 -130
  36. gobby/config/skills.py +31 -0
  37. gobby/config/tasks.py +4 -28
  38. gobby/hooks/__init__.py +0 -13
  39. gobby/hooks/event_handlers.py +150 -8
  40. gobby/hooks/hook_manager.py +21 -3
  41. gobby/hooks/plugins.py +1 -1
  42. gobby/hooks/webhooks.py +1 -1
  43. gobby/install/gemini/hooks/hook_dispatcher.py +74 -15
  44. gobby/llm/resolver.py +3 -2
  45. gobby/mcp_proxy/importer.py +62 -4
  46. gobby/mcp_proxy/instructions.py +4 -2
  47. gobby/mcp_proxy/registries.py +22 -8
  48. gobby/mcp_proxy/services/recommendation.py +43 -11
  49. gobby/mcp_proxy/tools/agent_messaging.py +93 -44
  50. gobby/mcp_proxy/tools/agents.py +76 -740
  51. gobby/mcp_proxy/tools/artifacts.py +43 -9
  52. gobby/mcp_proxy/tools/clones.py +0 -385
  53. gobby/mcp_proxy/tools/memory.py +2 -2
  54. gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
  55. gobby/mcp_proxy/tools/sessions/_commits.py +239 -0
  56. gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
  57. gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
  58. gobby/mcp_proxy/tools/sessions/_handoff.py +503 -0
  59. gobby/mcp_proxy/tools/sessions/_messages.py +166 -0
  60. gobby/mcp_proxy/tools/skills/__init__.py +14 -29
  61. gobby/mcp_proxy/tools/spawn_agent.py +455 -0
  62. gobby/mcp_proxy/tools/tasks/_context.py +18 -0
  63. gobby/mcp_proxy/tools/tasks/_crud.py +13 -6
  64. gobby/mcp_proxy/tools/tasks/_lifecycle.py +79 -30
  65. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +1 -1
  66. gobby/mcp_proxy/tools/tasks/_session.py +22 -7
  67. gobby/mcp_proxy/tools/workflows.py +84 -34
  68. gobby/mcp_proxy/tools/worktrees.py +32 -350
  69. gobby/memory/extractor.py +15 -1
  70. gobby/memory/ingestion/__init__.py +5 -0
  71. gobby/memory/ingestion/multimodal.py +221 -0
  72. gobby/memory/manager.py +62 -283
  73. gobby/memory/search/__init__.py +10 -0
  74. gobby/memory/search/coordinator.py +248 -0
  75. gobby/memory/services/__init__.py +5 -0
  76. gobby/memory/services/crossref.py +142 -0
  77. gobby/prompts/loader.py +5 -2
  78. gobby/runner.py +13 -0
  79. gobby/servers/http.py +1 -4
  80. gobby/servers/routes/admin.py +14 -0
  81. gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
  82. gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
  83. gobby/servers/routes/mcp/endpoints/execution.py +568 -0
  84. gobby/servers/routes/mcp/endpoints/registry.py +378 -0
  85. gobby/servers/routes/mcp/endpoints/server.py +304 -0
  86. gobby/servers/routes/mcp/hooks.py +51 -4
  87. gobby/servers/routes/mcp/tools.py +48 -1506
  88. gobby/servers/websocket.py +57 -1
  89. gobby/sessions/analyzer.py +2 -2
  90. gobby/sessions/lifecycle.py +1 -1
  91. gobby/sessions/manager.py +9 -0
  92. gobby/sessions/processor.py +10 -0
  93. gobby/sessions/transcripts/base.py +1 -0
  94. gobby/sessions/transcripts/claude.py +15 -5
  95. gobby/sessions/transcripts/gemini.py +100 -34
  96. gobby/skills/parser.py +30 -2
  97. gobby/storage/database.py +9 -2
  98. gobby/storage/memories.py +32 -21
  99. gobby/storage/migrations.py +174 -368
  100. gobby/storage/sessions.py +45 -7
  101. gobby/storage/skills.py +80 -7
  102. gobby/storage/tasks/_lifecycle.py +18 -3
  103. gobby/sync/memories.py +1 -1
  104. gobby/tasks/external_validator.py +1 -1
  105. gobby/tasks/validation.py +22 -20
  106. gobby/tools/summarizer.py +91 -10
  107. gobby/utils/project_context.py +2 -3
  108. gobby/utils/status.py +13 -0
  109. gobby/workflows/actions.py +221 -1217
  110. gobby/workflows/artifact_actions.py +31 -0
  111. gobby/workflows/autonomous_actions.py +11 -0
  112. gobby/workflows/context_actions.py +50 -1
  113. gobby/workflows/detection_helpers.py +38 -24
  114. gobby/workflows/enforcement/__init__.py +47 -0
  115. gobby/workflows/enforcement/blocking.py +281 -0
  116. gobby/workflows/enforcement/commit_policy.py +283 -0
  117. gobby/workflows/enforcement/handlers.py +269 -0
  118. gobby/workflows/enforcement/task_policy.py +542 -0
  119. gobby/workflows/engine.py +93 -0
  120. gobby/workflows/evaluator.py +110 -0
  121. gobby/workflows/git_utils.py +106 -0
  122. gobby/workflows/hooks.py +41 -0
  123. gobby/workflows/llm_actions.py +30 -0
  124. gobby/workflows/mcp_actions.py +20 -1
  125. gobby/workflows/memory_actions.py +91 -0
  126. gobby/workflows/safe_evaluator.py +191 -0
  127. gobby/workflows/session_actions.py +44 -0
  128. gobby/workflows/state_actions.py +60 -1
  129. gobby/workflows/stop_signal_actions.py +55 -0
  130. gobby/workflows/summary_actions.py +217 -51
  131. gobby/workflows/task_sync_actions.py +347 -0
  132. gobby/workflows/todo_actions.py +34 -1
  133. gobby/workflows/webhook_actions.py +185 -0
  134. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/METADATA +6 -1
  135. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/RECORD +139 -163
  136. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/WHEEL +1 -1
  137. gobby/adapters/codex.py +0 -1332
  138. gobby/cli/tui.py +0 -34
  139. gobby/install/claude/commands/gobby/bug.md +0 -51
  140. gobby/install/claude/commands/gobby/chore.md +0 -51
  141. gobby/install/claude/commands/gobby/epic.md +0 -52
  142. gobby/install/claude/commands/gobby/eval.md +0 -235
  143. gobby/install/claude/commands/gobby/feat.md +0 -49
  144. gobby/install/claude/commands/gobby/nit.md +0 -52
  145. gobby/install/claude/commands/gobby/ref.md +0 -52
  146. gobby/mcp_proxy/tools/session_messages.py +0 -1055
  147. gobby/prompts/defaults/expansion/system.md +0 -119
  148. gobby/prompts/defaults/expansion/user.md +0 -48
  149. gobby/prompts/defaults/external_validation/agent.md +0 -72
  150. gobby/prompts/defaults/external_validation/external.md +0 -63
  151. gobby/prompts/defaults/external_validation/spawn.md +0 -83
  152. gobby/prompts/defaults/external_validation/system.md +0 -6
  153. gobby/prompts/defaults/features/import_mcp.md +0 -22
  154. gobby/prompts/defaults/features/import_mcp_github.md +0 -17
  155. gobby/prompts/defaults/features/import_mcp_search.md +0 -16
  156. gobby/prompts/defaults/features/recommend_tools.md +0 -32
  157. gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
  158. gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
  159. gobby/prompts/defaults/features/server_description.md +0 -20
  160. gobby/prompts/defaults/features/server_description_system.md +0 -6
  161. gobby/prompts/defaults/features/task_description.md +0 -31
  162. gobby/prompts/defaults/features/task_description_system.md +0 -6
  163. gobby/prompts/defaults/features/tool_summary.md +0 -17
  164. gobby/prompts/defaults/features/tool_summary_system.md +0 -6
  165. gobby/prompts/defaults/handoff/compact.md +0 -63
  166. gobby/prompts/defaults/handoff/session_end.md +0 -57
  167. gobby/prompts/defaults/memory/extract.md +0 -61
  168. gobby/prompts/defaults/research/step.md +0 -58
  169. gobby/prompts/defaults/validation/criteria.md +0 -47
  170. gobby/prompts/defaults/validation/validate.md +0 -38
  171. gobby/storage/migrations_legacy.py +0 -1359
  172. gobby/tui/__init__.py +0 -5
  173. gobby/tui/api_client.py +0 -278
  174. gobby/tui/app.py +0 -329
  175. gobby/tui/screens/__init__.py +0 -25
  176. gobby/tui/screens/agents.py +0 -333
  177. gobby/tui/screens/chat.py +0 -450
  178. gobby/tui/screens/dashboard.py +0 -377
  179. gobby/tui/screens/memory.py +0 -305
  180. gobby/tui/screens/metrics.py +0 -231
  181. gobby/tui/screens/orchestrator.py +0 -903
  182. gobby/tui/screens/sessions.py +0 -412
  183. gobby/tui/screens/tasks.py +0 -440
  184. gobby/tui/screens/workflows.py +0 -289
  185. gobby/tui/screens/worktrees.py +0 -174
  186. gobby/tui/widgets/__init__.py +0 -21
  187. gobby/tui/widgets/chat.py +0 -210
  188. gobby/tui/widgets/conductor.py +0 -104
  189. gobby/tui/widgets/menu.py +0 -132
  190. gobby/tui/widgets/message_panel.py +0 -160
  191. gobby/tui/widgets/review_gate.py +0 -224
  192. gobby/tui/widgets/task_tree.py +0 -99
  193. gobby/tui/widgets/token_budget.py +0 -166
  194. gobby/tui/ws_client.py +0 -258
  195. gobby/workflows/task_enforcement_actions.py +0 -1343
  196. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/entry_points.txt +0 -0
  197. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/licenses/LICENSE.md +0 -0
  198. {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 session_id:
196
+ if resolved_session_id:
158
197
  try:
159
- ctx.session_task_manager.link_task(session_id, resolved_id, "review")
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=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 session_id:
225
+ if resolved_session_id:
187
226
  try:
188
- ctx.session_task_manager.link_task(session_id, resolved_id, "closed")
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 session_id:
235
+ if resolved_session_id:
197
236
  try:
198
- state = ctx.workflow_state_manager.get_state(session_id)
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 (from system context). Pass this to track which session closed the task.",
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 != session_id and not force:
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=session_id,
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(session_id, resolved_id, "claimed")
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 (from system context). The session claiming the task.",
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
- "A commit is required before closing this task.\n\n"
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.session_task_manager.link_task(session_id, resolved_id, action)
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 ID (optional, defaults to linking context if available)",
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
- tasks = ctx.session_task_manager.get_session_tasks(session_id)
78
- return {"session_id": session_id, "tasks": tasks}
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": {"type": "string", "description": "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
  },