gobby 0.2.5__py3-none-any.whl → 0.2.7__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 (244) hide show
  1. gobby/__init__.py +1 -1
  2. gobby/adapters/__init__.py +2 -1
  3. gobby/adapters/claude_code.py +13 -4
  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/agents/definitions.py +11 -1
  10. gobby/agents/isolation.py +395 -0
  11. gobby/agents/runner.py +8 -0
  12. gobby/agents/sandbox.py +261 -0
  13. gobby/agents/spawn.py +42 -287
  14. gobby/agents/spawn_executor.py +385 -0
  15. gobby/agents/spawners/__init__.py +24 -0
  16. gobby/agents/spawners/command_builder.py +189 -0
  17. gobby/agents/spawners/embedded.py +21 -2
  18. gobby/agents/spawners/headless.py +21 -2
  19. gobby/agents/spawners/prompt_manager.py +125 -0
  20. gobby/cli/__init__.py +6 -0
  21. gobby/cli/clones.py +419 -0
  22. gobby/cli/conductor.py +266 -0
  23. gobby/cli/install.py +4 -4
  24. gobby/cli/installers/antigravity.py +3 -9
  25. gobby/cli/installers/claude.py +15 -9
  26. gobby/cli/installers/codex.py +2 -8
  27. gobby/cli/installers/gemini.py +8 -8
  28. gobby/cli/installers/shared.py +175 -13
  29. gobby/cli/sessions.py +1 -1
  30. gobby/cli/skills.py +858 -0
  31. gobby/cli/tasks/ai.py +0 -440
  32. gobby/cli/tasks/crud.py +44 -6
  33. gobby/cli/tasks/main.py +0 -4
  34. gobby/cli/tui.py +2 -2
  35. gobby/cli/utils.py +12 -5
  36. gobby/clones/__init__.py +13 -0
  37. gobby/clones/git.py +547 -0
  38. gobby/conductor/__init__.py +16 -0
  39. gobby/conductor/alerts.py +135 -0
  40. gobby/conductor/loop.py +164 -0
  41. gobby/conductor/monitors/__init__.py +11 -0
  42. gobby/conductor/monitors/agents.py +116 -0
  43. gobby/conductor/monitors/tasks.py +155 -0
  44. gobby/conductor/pricing.py +234 -0
  45. gobby/conductor/token_tracker.py +160 -0
  46. gobby/config/__init__.py +12 -97
  47. gobby/config/app.py +69 -91
  48. gobby/config/extensions.py +2 -2
  49. gobby/config/features.py +7 -130
  50. gobby/config/search.py +110 -0
  51. gobby/config/servers.py +1 -1
  52. gobby/config/skills.py +43 -0
  53. gobby/config/tasks.py +9 -41
  54. gobby/hooks/__init__.py +0 -13
  55. gobby/hooks/event_handlers.py +188 -2
  56. gobby/hooks/hook_manager.py +50 -4
  57. gobby/hooks/plugins.py +1 -1
  58. gobby/hooks/skill_manager.py +130 -0
  59. gobby/hooks/webhooks.py +1 -1
  60. gobby/install/claude/hooks/hook_dispatcher.py +4 -4
  61. gobby/install/codex/hooks/hook_dispatcher.py +1 -1
  62. gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
  63. gobby/llm/claude.py +22 -34
  64. gobby/llm/claude_executor.py +46 -256
  65. gobby/llm/codex_executor.py +59 -291
  66. gobby/llm/executor.py +21 -0
  67. gobby/llm/gemini.py +134 -110
  68. gobby/llm/litellm_executor.py +143 -6
  69. gobby/llm/resolver.py +98 -35
  70. gobby/mcp_proxy/importer.py +62 -4
  71. gobby/mcp_proxy/instructions.py +56 -0
  72. gobby/mcp_proxy/models.py +15 -0
  73. gobby/mcp_proxy/registries.py +68 -8
  74. gobby/mcp_proxy/server.py +33 -3
  75. gobby/mcp_proxy/services/recommendation.py +43 -11
  76. gobby/mcp_proxy/services/tool_proxy.py +81 -1
  77. gobby/mcp_proxy/stdio.py +2 -1
  78. gobby/mcp_proxy/tools/__init__.py +0 -2
  79. gobby/mcp_proxy/tools/agent_messaging.py +317 -0
  80. gobby/mcp_proxy/tools/agents.py +31 -731
  81. gobby/mcp_proxy/tools/clones.py +518 -0
  82. gobby/mcp_proxy/tools/memory.py +3 -26
  83. gobby/mcp_proxy/tools/metrics.py +65 -1
  84. gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
  85. gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
  86. gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
  87. gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
  88. gobby/mcp_proxy/tools/sessions/_commits.py +232 -0
  89. gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
  90. gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
  91. gobby/mcp_proxy/tools/sessions/_handoff.py +499 -0
  92. gobby/mcp_proxy/tools/sessions/_messages.py +138 -0
  93. gobby/mcp_proxy/tools/skills/__init__.py +616 -0
  94. gobby/mcp_proxy/tools/spawn_agent.py +417 -0
  95. gobby/mcp_proxy/tools/task_orchestration.py +7 -0
  96. gobby/mcp_proxy/tools/task_readiness.py +14 -0
  97. gobby/mcp_proxy/tools/task_sync.py +1 -1
  98. gobby/mcp_proxy/tools/tasks/_context.py +0 -20
  99. gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
  100. gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
  101. gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
  102. gobby/mcp_proxy/tools/tasks/_lifecycle.py +110 -45
  103. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
  104. gobby/mcp_proxy/tools/workflows.py +1 -1
  105. gobby/mcp_proxy/tools/worktrees.py +0 -338
  106. gobby/memory/backends/__init__.py +6 -1
  107. gobby/memory/backends/mem0.py +6 -1
  108. gobby/memory/extractor.py +477 -0
  109. gobby/memory/ingestion/__init__.py +5 -0
  110. gobby/memory/ingestion/multimodal.py +221 -0
  111. gobby/memory/manager.py +73 -285
  112. gobby/memory/search/__init__.py +10 -0
  113. gobby/memory/search/coordinator.py +248 -0
  114. gobby/memory/services/__init__.py +5 -0
  115. gobby/memory/services/crossref.py +142 -0
  116. gobby/prompts/loader.py +5 -2
  117. gobby/runner.py +37 -16
  118. gobby/search/__init__.py +48 -6
  119. gobby/search/backends/__init__.py +159 -0
  120. gobby/search/backends/embedding.py +225 -0
  121. gobby/search/embeddings.py +238 -0
  122. gobby/search/models.py +148 -0
  123. gobby/search/unified.py +496 -0
  124. gobby/servers/http.py +24 -12
  125. gobby/servers/routes/admin.py +294 -0
  126. gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
  127. gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
  128. gobby/servers/routes/mcp/endpoints/execution.py +568 -0
  129. gobby/servers/routes/mcp/endpoints/registry.py +378 -0
  130. gobby/servers/routes/mcp/endpoints/server.py +304 -0
  131. gobby/servers/routes/mcp/hooks.py +1 -1
  132. gobby/servers/routes/mcp/tools.py +48 -1317
  133. gobby/servers/websocket.py +2 -2
  134. gobby/sessions/analyzer.py +2 -0
  135. gobby/sessions/lifecycle.py +1 -1
  136. gobby/sessions/processor.py +10 -0
  137. gobby/sessions/transcripts/base.py +2 -0
  138. gobby/sessions/transcripts/claude.py +79 -10
  139. gobby/skills/__init__.py +91 -0
  140. gobby/skills/loader.py +685 -0
  141. gobby/skills/manager.py +384 -0
  142. gobby/skills/parser.py +286 -0
  143. gobby/skills/search.py +463 -0
  144. gobby/skills/sync.py +119 -0
  145. gobby/skills/updater.py +385 -0
  146. gobby/skills/validator.py +368 -0
  147. gobby/storage/clones.py +378 -0
  148. gobby/storage/database.py +1 -1
  149. gobby/storage/memories.py +43 -13
  150. gobby/storage/migrations.py +162 -201
  151. gobby/storage/sessions.py +116 -7
  152. gobby/storage/skills.py +782 -0
  153. gobby/storage/tasks/_crud.py +4 -4
  154. gobby/storage/tasks/_lifecycle.py +57 -7
  155. gobby/storage/tasks/_manager.py +14 -5
  156. gobby/storage/tasks/_models.py +8 -3
  157. gobby/sync/memories.py +40 -5
  158. gobby/sync/tasks.py +83 -6
  159. gobby/tasks/__init__.py +1 -2
  160. gobby/tasks/external_validator.py +1 -1
  161. gobby/tasks/validation.py +46 -35
  162. gobby/tools/summarizer.py +91 -10
  163. gobby/tui/api_client.py +4 -7
  164. gobby/tui/app.py +5 -3
  165. gobby/tui/screens/orchestrator.py +1 -2
  166. gobby/tui/screens/tasks.py +2 -4
  167. gobby/tui/ws_client.py +1 -1
  168. gobby/utils/daemon_client.py +2 -2
  169. gobby/utils/project_context.py +2 -3
  170. gobby/utils/status.py +13 -0
  171. gobby/workflows/actions.py +221 -1135
  172. gobby/workflows/artifact_actions.py +31 -0
  173. gobby/workflows/autonomous_actions.py +11 -0
  174. gobby/workflows/context_actions.py +93 -1
  175. gobby/workflows/detection_helpers.py +115 -31
  176. gobby/workflows/enforcement/__init__.py +47 -0
  177. gobby/workflows/enforcement/blocking.py +269 -0
  178. gobby/workflows/enforcement/commit_policy.py +283 -0
  179. gobby/workflows/enforcement/handlers.py +269 -0
  180. gobby/workflows/{task_enforcement_actions.py → enforcement/task_policy.py} +29 -388
  181. gobby/workflows/engine.py +13 -2
  182. gobby/workflows/git_utils.py +106 -0
  183. gobby/workflows/lifecycle_evaluator.py +29 -1
  184. gobby/workflows/llm_actions.py +30 -0
  185. gobby/workflows/loader.py +19 -6
  186. gobby/workflows/mcp_actions.py +20 -1
  187. gobby/workflows/memory_actions.py +154 -0
  188. gobby/workflows/safe_evaluator.py +183 -0
  189. gobby/workflows/session_actions.py +44 -0
  190. gobby/workflows/state_actions.py +60 -1
  191. gobby/workflows/stop_signal_actions.py +55 -0
  192. gobby/workflows/summary_actions.py +111 -1
  193. gobby/workflows/task_sync_actions.py +347 -0
  194. gobby/workflows/todo_actions.py +34 -1
  195. gobby/workflows/webhook_actions.py +185 -0
  196. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/METADATA +87 -21
  197. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/RECORD +201 -172
  198. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/WHEEL +1 -1
  199. gobby/adapters/codex.py +0 -1292
  200. gobby/install/claude/commands/gobby/bug.md +0 -51
  201. gobby/install/claude/commands/gobby/chore.md +0 -51
  202. gobby/install/claude/commands/gobby/epic.md +0 -52
  203. gobby/install/claude/commands/gobby/eval.md +0 -235
  204. gobby/install/claude/commands/gobby/feat.md +0 -49
  205. gobby/install/claude/commands/gobby/nit.md +0 -52
  206. gobby/install/claude/commands/gobby/ref.md +0 -52
  207. gobby/install/codex/prompts/forget.md +0 -7
  208. gobby/install/codex/prompts/memories.md +0 -7
  209. gobby/install/codex/prompts/recall.md +0 -7
  210. gobby/install/codex/prompts/remember.md +0 -13
  211. gobby/llm/gemini_executor.py +0 -339
  212. gobby/mcp_proxy/tools/session_messages.py +0 -1056
  213. gobby/mcp_proxy/tools/task_expansion.py +0 -591
  214. gobby/prompts/defaults/expansion/system.md +0 -119
  215. gobby/prompts/defaults/expansion/user.md +0 -48
  216. gobby/prompts/defaults/external_validation/agent.md +0 -72
  217. gobby/prompts/defaults/external_validation/external.md +0 -63
  218. gobby/prompts/defaults/external_validation/spawn.md +0 -83
  219. gobby/prompts/defaults/external_validation/system.md +0 -6
  220. gobby/prompts/defaults/features/import_mcp.md +0 -22
  221. gobby/prompts/defaults/features/import_mcp_github.md +0 -17
  222. gobby/prompts/defaults/features/import_mcp_search.md +0 -16
  223. gobby/prompts/defaults/features/recommend_tools.md +0 -32
  224. gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
  225. gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
  226. gobby/prompts/defaults/features/server_description.md +0 -20
  227. gobby/prompts/defaults/features/server_description_system.md +0 -6
  228. gobby/prompts/defaults/features/task_description.md +0 -31
  229. gobby/prompts/defaults/features/task_description_system.md +0 -6
  230. gobby/prompts/defaults/features/tool_summary.md +0 -17
  231. gobby/prompts/defaults/features/tool_summary_system.md +0 -6
  232. gobby/prompts/defaults/research/step.md +0 -58
  233. gobby/prompts/defaults/validation/criteria.md +0 -47
  234. gobby/prompts/defaults/validation/validate.md +0 -38
  235. gobby/storage/migrations_legacy.py +0 -1359
  236. gobby/tasks/context.py +0 -747
  237. gobby/tasks/criteria.py +0 -342
  238. gobby/tasks/expansion.py +0 -626
  239. gobby/tasks/prompts/expand.py +0 -327
  240. gobby/tasks/research.py +0 -421
  241. gobby/tasks/tdd.py +0 -352
  242. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/entry_points.txt +0 -0
  243. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/licenses/LICENSE.md +0 -0
  244. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,232 @@
1
+ """Commits and workflow tools for session management.
2
+
3
+ This module contains MCP tools for:
4
+ - Getting session commits (get_session_commits)
5
+ - Marking autonomous loop complete (mark_loop_complete)
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from datetime import UTC
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ if TYPE_CHECKING:
14
+ from gobby.mcp_proxy.tools.internal import InternalToolRegistry
15
+ from gobby.storage.sessions import LocalSessionManager
16
+
17
+
18
+ def register_commits_tools(
19
+ registry: InternalToolRegistry,
20
+ session_manager: LocalSessionManager,
21
+ ) -> None:
22
+ """
23
+ Register commits and workflow tools with a registry.
24
+
25
+ Args:
26
+ registry: The InternalToolRegistry to register tools with
27
+ session_manager: LocalSessionManager instance for session operations
28
+ """
29
+
30
+ @registry.tool(
31
+ name="get_session_commits",
32
+ description="Get git commits made during a session timeframe.",
33
+ )
34
+ def get_session_commits(
35
+ session_id: str,
36
+ max_commits: int = 20,
37
+ ) -> dict[str, Any]:
38
+ """
39
+ Get git commits made during a session's active timeframe.
40
+
41
+ Uses session.created_at and session.updated_at to filter
42
+ git log within that timeframe.
43
+
44
+ Args:
45
+ session_id: Session ID
46
+ max_commits: Maximum commits to return (default 20)
47
+
48
+ Returns:
49
+ Session ID, list of commits, and count
50
+ """
51
+ import subprocess # nosec B404 - subprocess needed for git commands
52
+ from datetime import datetime
53
+ from pathlib import Path
54
+
55
+ if session_manager is None:
56
+ return {"error": "Session manager not available"}
57
+
58
+ # Get session
59
+ session = session_manager.get(session_id)
60
+ if not session:
61
+ # Try prefix match
62
+ sessions = session_manager.list(limit=100)
63
+ matches = [s for s in sessions if s.id.startswith(session_id)]
64
+ if len(matches) == 1:
65
+ session = matches[0]
66
+ elif len(matches) > 1:
67
+ return {
68
+ "error": f"Ambiguous session ID prefix '{session_id}'",
69
+ "matches": [s.id for s in matches[:5]],
70
+ }
71
+ else:
72
+ return {"error": f"Session {session_id} not found"}
73
+
74
+ # Get working directory from transcript path or project
75
+ cwd = None
76
+ if session.jsonl_path:
77
+ cwd = str(Path(session.jsonl_path).parent)
78
+
79
+ # Format timestamps for git --since/--until
80
+ # Git expects ISO format or relative dates
81
+ # Session timestamps may be ISO strings or datetime objects
82
+ if isinstance(session.created_at, str):
83
+ since_time = datetime.fromisoformat(session.created_at.replace("Z", "+00:00"))
84
+ else:
85
+ since_time = session.created_at
86
+
87
+ if session.updated_at:
88
+ if isinstance(session.updated_at, str):
89
+ until_time = datetime.fromisoformat(session.updated_at.replace("Z", "+00:00"))
90
+ else:
91
+ until_time = session.updated_at
92
+ else:
93
+ until_time = datetime.now(UTC)
94
+
95
+ # Format as ISO 8601 for git
96
+ since_str = since_time.strftime("%Y-%m-%dT%H:%M:%S")
97
+ until_str = until_time.strftime("%Y-%m-%dT%H:%M:%S")
98
+
99
+ try:
100
+ # Get commits within timeframe
101
+ cmd = [
102
+ "git",
103
+ "log",
104
+ f"--since={since_str}",
105
+ f"--until={until_str}",
106
+ f"-{max_commits}",
107
+ "--format=%H|%s|%aI", # hash|subject|author-date-iso
108
+ ]
109
+
110
+ result = subprocess.run( # nosec B603 - cmd built from hardcoded git arguments
111
+ cmd,
112
+ capture_output=True,
113
+ text=True,
114
+ timeout=10,
115
+ cwd=cwd,
116
+ )
117
+
118
+ if result.returncode != 0:
119
+ return {
120
+ "session_id": session.id,
121
+ "error": "Git command failed",
122
+ "stderr": result.stderr.strip(),
123
+ }
124
+
125
+ commits = []
126
+ for line in result.stdout.strip().split("\n"):
127
+ if "|" in line:
128
+ parts = line.split("|", 2)
129
+ if len(parts) >= 2:
130
+ commit = {
131
+ "hash": parts[0],
132
+ "message": parts[1],
133
+ }
134
+ if len(parts) >= 3:
135
+ commit["timestamp"] = parts[2]
136
+ commits.append(commit)
137
+
138
+ return {
139
+ "session_id": session.id,
140
+ "commits": commits,
141
+ "count": len(commits),
142
+ "timeframe": {
143
+ "since": since_str,
144
+ "until": until_str,
145
+ },
146
+ }
147
+
148
+ except subprocess.TimeoutExpired:
149
+ return {
150
+ "session_id": session.id,
151
+ "error": "Git command timed out",
152
+ }
153
+ except FileNotFoundError:
154
+ return {
155
+ "session_id": session.id,
156
+ "error": "Git not found or not a git repository",
157
+ }
158
+ except Exception as e:
159
+ return {
160
+ "session_id": session.id,
161
+ "error": f"Failed to get commits: {e!s}",
162
+ }
163
+
164
+ @registry.tool(
165
+ name="mark_loop_complete",
166
+ description="""Mark the autonomous loop as complete, preventing session chaining.
167
+
168
+ Args:
169
+ session_id: (REQUIRED) Your session ID. Get it from:
170
+ 1. Your injected context (look for 'session_id: xxx')
171
+ 2. Or call get_current(external_id, source) first""",
172
+ )
173
+ def mark_loop_complete(session_id: str) -> dict[str, Any]:
174
+ """
175
+ Mark the autonomous loop as complete for a session.
176
+
177
+ This sets stop_reason='completed' in the workflow state, which
178
+ signals the auto-loop workflow to NOT chain a new session
179
+ when this session ends.
180
+
181
+ Use this when:
182
+ - A task is fully complete and no more work is needed
183
+ - You want to exit the autonomous loop gracefully
184
+ - The user has explicitly asked to stop
185
+
186
+ Args:
187
+ session_id: Session ID (REQUIRED)
188
+
189
+ Returns:
190
+ Success status and session details
191
+ """
192
+ if not session_manager:
193
+ raise RuntimeError("Session manager not available")
194
+
195
+ # Find session - session_id is now required
196
+ session = session_manager.get(session_id)
197
+
198
+ if not session:
199
+ return {"error": f"Session {session_id} not found", "session_id": session_id}
200
+
201
+ # Load and update workflow state
202
+ from gobby.storage.database import LocalDatabase
203
+ from gobby.workflows.definitions import WorkflowState
204
+ from gobby.workflows.state_manager import WorkflowStateManager
205
+
206
+ db = LocalDatabase()
207
+ state_manager = WorkflowStateManager(db)
208
+
209
+ # Get or create state for session
210
+ state = state_manager.get_state(session.id)
211
+ if not state:
212
+ # Create minimal state just to hold the variable
213
+ state = WorkflowState(
214
+ session_id=session.id,
215
+ workflow_name="auto-loop",
216
+ step="active",
217
+ )
218
+
219
+ # Mark loop complete using the action function
220
+ from gobby.workflows.state_actions import mark_loop_complete as action_mark_complete
221
+
222
+ action_mark_complete(state)
223
+
224
+ # Save updated state
225
+ state_manager.save_state(state)
226
+
227
+ return {
228
+ "success": True,
229
+ "session_id": session.id,
230
+ "stop_reason": "completed",
231
+ "message": "Autonomous loop marked complete - session will not chain",
232
+ }
@@ -0,0 +1,253 @@
1
+ """Session CRUD tools for session management.
2
+
3
+ This module contains MCP tools for:
4
+ - Getting session details (get_session)
5
+ - Getting current session (get_current)
6
+ - Listing sessions (list_sessions)
7
+ - Session statistics (session_stats)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ if TYPE_CHECKING:
15
+ from gobby.mcp_proxy.tools.internal import InternalToolRegistry
16
+ from gobby.storage.sessions import LocalSessionManager
17
+
18
+
19
+ def register_crud_tools(
20
+ registry: InternalToolRegistry,
21
+ session_manager: LocalSessionManager,
22
+ ) -> None:
23
+ """
24
+ Register session CRUD tools with a registry.
25
+
26
+ Args:
27
+ registry: The InternalToolRegistry to register tools with
28
+ session_manager: LocalSessionManager instance for session operations
29
+ """
30
+
31
+ @registry.tool(
32
+ name="get_session",
33
+ description="Get session details by ID. Accepts #N (project-scoped ref), UUID, or prefix. Use the session_id from your injected context.",
34
+ )
35
+ def get_session(session_id: str) -> dict[str, Any]:
36
+ """
37
+ Get session details by session reference.
38
+
39
+ Your session_id is injected into your context at session start.
40
+ Look for 'Session Ref: #N' or 'session_id: xxx' in your system reminders.
41
+
42
+ Args:
43
+ session_id: Session reference - supports #N (project-scoped), UUID, or prefix
44
+
45
+ Returns:
46
+ Session dict with all fields, or error if not found
47
+ """
48
+ from gobby.utils.project_context import get_project_context
49
+
50
+ # Support #N format, UUID, and prefix matching
51
+ if session_manager is None:
52
+ return {"error": "Session manager not available"}
53
+
54
+ # Get project_id for project-scoped resolution
55
+ project_ctx = get_project_context()
56
+ project_id = project_ctx.get("id") if project_ctx else None
57
+
58
+ # Try to resolve session reference (#N, UUID, or prefix)
59
+ try:
60
+ resolved_id = session_manager.resolve_session_reference(session_id, project_id)
61
+ session = session_manager.get(resolved_id)
62
+ except ValueError:
63
+ session = None
64
+
65
+ if not session:
66
+ return {"error": f"Session {session_id} not found", "found": False}
67
+
68
+ return {
69
+ "found": True,
70
+ **session.to_dict(),
71
+ }
72
+
73
+ @registry.tool(
74
+ name="get_current",
75
+ description="""Get YOUR current session ID - the CORRECT way to look up your session.
76
+
77
+ Use this when session_id wasn't in your injected context. Pass your external_id
78
+ (from transcript path or GOBBY_SESSION_ID env) and source (claude, gemini, codex).
79
+
80
+ DO NOT use list_sessions to find your session - it won't work with multiple active sessions.""",
81
+ )
82
+ def get_current(
83
+ external_id: str,
84
+ source: str,
85
+ ) -> dict[str, Any]:
86
+ """
87
+ Look up your internal session_id from external_id and source.
88
+
89
+ The agent passes external_id (from injected context or GOBBY_SESSION_ID env var)
90
+ and source (claude, gemini, codex). project_id and machine_id are
91
+ auto-resolved from config files.
92
+
93
+ Args:
94
+ external_id: Your CLI's session ID (from context or GOBBY_SESSION_ID env)
95
+ source: CLI source - "claude", "gemini", or "codex"
96
+
97
+ Returns:
98
+ session_id: Internal Gobby session ID (use for parent_session_id, etc.)
99
+ Plus basic session metadata
100
+ """
101
+ from gobby.utils.machine_id import get_machine_id
102
+ from gobby.utils.project_context import get_project_context
103
+
104
+ if session_manager is None:
105
+ return {"error": "Session manager not available"}
106
+
107
+ # Auto-resolve context
108
+ machine_id = get_machine_id()
109
+ project_ctx = get_project_context()
110
+ project_id = project_ctx.get("id") if project_ctx else None
111
+
112
+ if not machine_id:
113
+ return {"error": "Could not determine machine_id"}
114
+ if not project_id:
115
+ return {"error": "Could not determine project_id (not in a gobby project?)"}
116
+
117
+ # Use find_by_external_id with full composite key (safe lookup)
118
+ session = session_manager.find_by_external_id(
119
+ external_id=external_id,
120
+ machine_id=machine_id,
121
+ project_id=project_id,
122
+ source=source,
123
+ )
124
+
125
+ if not session:
126
+ return {
127
+ "found": False,
128
+ "error": "Session not found",
129
+ "lookup": {
130
+ "external_id": external_id,
131
+ "source": source,
132
+ "project_id": project_id,
133
+ },
134
+ }
135
+
136
+ return {
137
+ "found": True,
138
+ "session_id": session.id,
139
+ "project_id": session.project_id,
140
+ "status": session.status,
141
+ "agent_run_id": session.agent_run_id,
142
+ }
143
+
144
+ @registry.tool(
145
+ name="list_sessions",
146
+ description="""List sessions with optional filtering.
147
+
148
+ WARNING: Do NOT use this to find your own session_id!
149
+ - `list_sessions(status="active", limit=1)` will NOT reliably return YOUR session
150
+ - Multiple sessions can be active simultaneously (parallel agents, multiple terminals)
151
+ - Use `get_current(external_id, source)` instead - it uses your unique session key
152
+
153
+ This tool is for browsing/listing sessions, not for self-identification.""",
154
+ )
155
+ def list_sessions(
156
+ project_id: str | None = None,
157
+ status: str | None = None,
158
+ source: str | None = None,
159
+ limit: int = 20,
160
+ ) -> dict[str, Any]:
161
+ """
162
+ List sessions with filters.
163
+
164
+ Args:
165
+ project_id: Filter by project ID
166
+ status: Filter by status (active, paused, expired, archived, handoff_ready)
167
+ source: Filter by CLI source (claude, gemini, codex)
168
+ limit: Max results (default 20)
169
+
170
+ Returns:
171
+ List of sessions and count
172
+ """
173
+ if session_manager is None:
174
+ return {"error": "Session manager not available"}
175
+
176
+ sessions = session_manager.list(
177
+ project_id=project_id,
178
+ status=status,
179
+ source=source,
180
+ limit=limit,
181
+ )
182
+
183
+ total = session_manager.count(
184
+ project_id=project_id,
185
+ status=status,
186
+ source=source,
187
+ )
188
+
189
+ # Detect likely misuse pattern: trying to find own session
190
+ if status == "active" and limit == 1:
191
+ return {
192
+ "warning": (
193
+ "list_sessions(status='active', limit=1) will NOT reliably get YOUR session_id! "
194
+ "Multiple sessions can be active simultaneously. "
195
+ "Use get_current(external_id='<your-external-id>', source='claude') instead."
196
+ ),
197
+ "hint": "Your external_id is in your transcript path: /path/to/<external_id>.jsonl",
198
+ "sessions": [s.to_dict() for s in sessions],
199
+ "count": len(sessions),
200
+ "total": total,
201
+ "limit": limit,
202
+ "filters": {
203
+ "project_id": project_id,
204
+ "status": status,
205
+ "source": source,
206
+ },
207
+ }
208
+
209
+ return {
210
+ "sessions": [s.to_dict() for s in sessions],
211
+ "count": len(sessions),
212
+ "total": total,
213
+ "limit": limit,
214
+ "filters": {
215
+ "project_id": project_id,
216
+ "status": status,
217
+ "source": source,
218
+ },
219
+ }
220
+
221
+ @registry.tool(
222
+ name="session_stats",
223
+ description="Get session statistics for a project.",
224
+ )
225
+ def session_stats(project_id: str | None = None) -> dict[str, Any]:
226
+ """
227
+ Get session statistics.
228
+
229
+ Args:
230
+ project_id: Filter by project ID (optional)
231
+
232
+ Returns:
233
+ Statistics including total, by_status, by_source
234
+ """
235
+ if session_manager is None:
236
+ return {"error": "Session manager not available"}
237
+
238
+ total = session_manager.count(project_id=project_id)
239
+ by_status = session_manager.count_by_status()
240
+
241
+ # Count by source
242
+ by_source: dict[str, int] = {}
243
+ for src in ["claude_code", "gemini", "codex"]:
244
+ count = session_manager.count(project_id=project_id, source=src)
245
+ if count > 0:
246
+ by_source[src] = count
247
+
248
+ return {
249
+ "total": total,
250
+ "by_status": by_status,
251
+ "by_source": by_source,
252
+ "project_id": project_id,
253
+ }
@@ -0,0 +1,63 @@
1
+ """Factory function for creating the session messages tool registry.
2
+
3
+ Orchestrates the creation of all session tool sub-registries and merges them
4
+ into a unified registry.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ from gobby.mcp_proxy.tools.internal import InternalToolRegistry
12
+ from gobby.mcp_proxy.tools.sessions._commits import register_commits_tools
13
+ from gobby.mcp_proxy.tools.sessions._crud import register_crud_tools
14
+ from gobby.mcp_proxy.tools.sessions._handoff import register_handoff_tools
15
+ from gobby.mcp_proxy.tools.sessions._messages import register_message_tools
16
+
17
+ if TYPE_CHECKING:
18
+ from gobby.storage.session_messages import LocalSessionMessageManager
19
+ from gobby.storage.sessions import LocalSessionManager
20
+
21
+ __all__ = ["create_session_messages_registry"]
22
+
23
+
24
+ def create_session_messages_registry(
25
+ message_manager: LocalSessionMessageManager | None = None,
26
+ session_manager: LocalSessionManager | None = None,
27
+ ) -> InternalToolRegistry:
28
+ """
29
+ Create a sessions tool registry with session and message tools.
30
+
31
+ Args:
32
+ message_manager: LocalSessionMessageManager instance for message operations
33
+ session_manager: LocalSessionManager instance for session CRUD
34
+
35
+ Returns:
36
+ InternalToolRegistry with all session tools registered
37
+ """
38
+ registry = InternalToolRegistry(
39
+ name="gobby-sessions",
40
+ description="Session management and message querying - CRUD, retrieval, search",
41
+ )
42
+
43
+ # --- Message Tools ---
44
+ # Only register if message_manager is available
45
+ if message_manager is not None:
46
+ register_message_tools(registry, message_manager)
47
+
48
+ # --- Handoff Tools ---
49
+ # Only register if session_manager is available
50
+ if session_manager is not None:
51
+ register_handoff_tools(registry, session_manager)
52
+
53
+ # --- Session CRUD Tools ---
54
+ # Only register if session_manager is available
55
+ if session_manager is not None:
56
+ register_crud_tools(registry, session_manager)
57
+
58
+ # --- Commits Tools ---
59
+ # Only register if session_manager is available
60
+ if session_manager is not None:
61
+ register_commits_tools(registry, session_manager)
62
+
63
+ return registry