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
@@ -0,0 +1,68 @@
1
+ """
2
+ Type definitions and data classes for Codex adapter.
3
+
4
+ Extracted from codex.py as part of Phase 3 Strangler Fig decomposition.
5
+ These types are used throughout the Codex adapter implementation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Callable
11
+ from dataclasses import dataclass, field
12
+ from enum import Enum
13
+ from typing import Any
14
+
15
+
16
+ class CodexConnectionState(Enum):
17
+ """Connection state for the Codex app-server."""
18
+
19
+ DISCONNECTED = "disconnected"
20
+ CONNECTING = "connecting"
21
+ CONNECTED = "connected"
22
+ ERROR = "error"
23
+
24
+
25
+ @dataclass
26
+ class CodexThread:
27
+ """Represents a Codex conversation thread."""
28
+
29
+ id: str
30
+ preview: str = ""
31
+ model_provider: str = "openai"
32
+ created_at: int = 0
33
+
34
+
35
+ @dataclass
36
+ class CodexTurn:
37
+ """Represents a turn in a Codex conversation."""
38
+
39
+ id: str
40
+ thread_id: str
41
+ status: str = "pending"
42
+ items: list[dict[str, Any]] = field(default_factory=list)
43
+ error: str | None = None
44
+ usage: dict[str, int] | None = None
45
+
46
+
47
+ @dataclass
48
+ class CodexItem:
49
+ """Represents an item (message, tool call, etc.) in a turn."""
50
+
51
+ id: str
52
+ type: str # "reasoning", "agent_message", "command_execution", "user_message", etc.
53
+ content: str = ""
54
+ status: str = "pending"
55
+ metadata: dict[str, Any] = field(default_factory=dict)
56
+
57
+
58
+ # Type alias for notification handlers
59
+ NotificationHandler = Callable[[str, dict[str, Any]], None]
60
+
61
+
62
+ __all__ = [
63
+ "CodexConnectionState",
64
+ "CodexThread",
65
+ "CodexTurn",
66
+ "CodexItem",
67
+ "NotificationHandler",
68
+ ]
gobby/adapters/gemini.py CHANGED
@@ -76,21 +76,39 @@ class GeminiAdapter(BaseAdapter):
76
76
 
77
77
  # Tool name mapping: Gemini tool names -> normalized names
78
78
  # Gemini uses different tool names than Claude Code
79
+ # This enables workflows to use Claude Code naming conventions
79
80
  TOOL_MAP: dict[str, str] = {
81
+ # Shell/Bash
80
82
  "run_shell_command": "Bash",
81
83
  "RunShellCommand": "Bash",
84
+ "ShellTool": "Bash",
85
+ # File read
82
86
  "read_file": "Read",
83
87
  "ReadFile": "Read",
84
88
  "ReadFileTool": "Read",
89
+ # File write
85
90
  "write_file": "Write",
86
91
  "WriteFile": "Write",
87
92
  "WriteFileTool": "Write",
93
+ # File edit
88
94
  "edit_file": "Edit",
89
95
  "EditFile": "Edit",
90
96
  "EditFileTool": "Edit",
97
+ # Search/Glob/Grep
91
98
  "GlobTool": "Glob",
92
99
  "GrepTool": "Grep",
93
- "ShellTool": "Bash",
100
+ "search_file_content": "Grep",
101
+ "SearchText": "Grep",
102
+ # MCP tools (Gobby MCP server)
103
+ "call_tool": "mcp__gobby__call_tool",
104
+ "list_mcp_servers": "mcp__gobby__list_mcp_servers",
105
+ "list_tools": "mcp__gobby__list_tools",
106
+ "get_tool_schema": "mcp__gobby__get_tool_schema",
107
+ "search_tools": "mcp__gobby__search_tools",
108
+ "recommend_tools": "mcp__gobby__recommend_tools",
109
+ # Skill and agent tools
110
+ "activate_skill": "Skill",
111
+ "delegate_to_agent": "Task",
94
112
  }
95
113
 
96
114
  def __init__(self, hook_manager: "HookManager | None" = None):
@@ -135,6 +153,55 @@ class GeminiAdapter(BaseAdapter):
135
153
  """
136
154
  return self.TOOL_MAP.get(gemini_tool_name, gemini_tool_name)
137
155
 
156
+ def _normalize_event_data(self, input_data: dict[str, Any]) -> dict[str, Any]:
157
+ """Normalize Gemini event data for CLI-agnostic processing.
158
+
159
+ This method enriches the input_data with normalized fields so downstream
160
+ code doesn't need to handle Gemini-specific formats.
161
+
162
+ Normalizations performed:
163
+ 1. mcp_context.server_name/tool_name → mcp_server/mcp_tool (top-level)
164
+ 2. tool_response → tool_output
165
+ 3. function_name → tool_name (if not already present)
166
+ 4. parameters/args → tool_input (if not already present)
167
+
168
+ Args:
169
+ input_data: Raw input data from Gemini CLI
170
+
171
+ Returns:
172
+ Enriched data dict with normalized fields added
173
+ """
174
+ # Start with a copy to avoid mutating original
175
+ data = dict(input_data)
176
+
177
+ # 1. Flatten mcp_context to top-level mcp_server/mcp_tool
178
+ mcp_context = data.get("mcp_context")
179
+ if mcp_context and isinstance(mcp_context, dict):
180
+ if "mcp_server" not in data:
181
+ data["mcp_server"] = mcp_context.get("server_name")
182
+ if "mcp_tool" not in data:
183
+ data["mcp_tool"] = mcp_context.get("tool_name")
184
+
185
+ # 2. Normalize tool_response → tool_output
186
+ if "tool_response" in data and "tool_output" not in data:
187
+ data["tool_output"] = data["tool_response"]
188
+
189
+ # 3. Normalize function_name → tool_name
190
+ if "function_name" in data and "tool_name" not in data:
191
+ data["tool_name"] = self.normalize_tool_name(data["function_name"])
192
+ elif "tool_name" in data:
193
+ # Normalize existing tool_name
194
+ data["tool_name"] = self.normalize_tool_name(data["tool_name"])
195
+
196
+ # 4. Normalize parameters/args → tool_input
197
+ if "tool_input" not in data:
198
+ if "parameters" in data:
199
+ data["tool_input"] = data["parameters"]
200
+ elif "args" in data:
201
+ data["tool_input"] = data["args"]
202
+
203
+ return data
204
+
138
205
  def translate_to_hook_event(self, native_event: dict[str, Any]) -> HookEvent:
139
206
  """Convert Gemini CLI native event to unified HookEvent.
140
207
 
@@ -202,6 +269,10 @@ class GeminiAdapter(BaseAdapter):
202
269
  else:
203
270
  metadata = {}
204
271
 
272
+ # Normalize event data for CLI-agnostic processing
273
+ # This allows downstream code to use consistent field names
274
+ normalized_data = self._normalize_event_data(input_data)
275
+
205
276
  return HookEvent(
206
277
  event_type=event_type,
207
278
  session_id=session_id,
@@ -209,7 +280,7 @@ class GeminiAdapter(BaseAdapter):
209
280
  timestamp=timestamp,
210
281
  machine_id=machine_id,
211
282
  cwd=input_data.get("cwd"),
212
- data=input_data,
283
+ data=normalized_data,
213
284
  metadata=metadata,
214
285
  )
215
286
 
@@ -254,46 +325,77 @@ class GeminiAdapter(BaseAdapter):
254
325
  if response.context:
255
326
  hook_specific["additionalContext"] = response.context
256
327
 
257
- # Add session/terminal context for SessionStart only
258
- if hook_type == "SessionStart" and response.metadata:
328
+ # Add session/terminal context for hooks that support additionalContext
329
+ # Parity with Claude Code: inject on SessionStart, BeforeAgent, BeforeTool, AfterTool
330
+ hooks_with_context = {"SessionStart", "BeforeAgent", "BeforeTool", "AfterTool"}
331
+ if hook_type in hooks_with_context and response.metadata:
259
332
  session_id = response.metadata.get("session_id")
333
+ session_ref = response.metadata.get("session_ref")
334
+ external_id = response.metadata.get("external_id")
335
+ is_first_hook = response.metadata.get("_first_hook_for_session", False)
336
+
260
337
  if session_id:
261
338
  hook_event_name = self.HOOK_EVENT_NAME_MAP.get(hook_type, "Unknown")
262
- context_lines = [f"session_id: {session_id}"]
263
- if response.metadata.get("parent_session_id"):
264
- context_lines.append(
265
- f"parent_session_id: {response.metadata['parent_session_id']}"
339
+
340
+ if is_first_hook:
341
+ # First hook: inject full metadata (~60-100 tokens)
342
+ context_lines = []
343
+ if session_ref:
344
+ context_lines.append(f"Gobby Session ID: {session_ref} (or {session_id})")
345
+ else:
346
+ context_lines.append(f"Gobby Session ID: {session_id}")
347
+ if external_id:
348
+ context_lines.append(
349
+ f"CLI-Specific Session ID (external_id): {external_id}"
350
+ )
351
+ if response.metadata.get("parent_session_id"):
352
+ context_lines.append(
353
+ f"parent_session_id: {response.metadata['parent_session_id']}"
354
+ )
355
+ if response.metadata.get("machine_id"):
356
+ context_lines.append(f"machine_id: {response.metadata['machine_id']}")
357
+ if response.metadata.get("project_id"):
358
+ context_lines.append(f"project_id: {response.metadata['project_id']}")
359
+ # Add terminal context (non-null values only)
360
+ if response.metadata.get("terminal_term_program"):
361
+ context_lines.append(
362
+ f"terminal: {response.metadata['terminal_term_program']}"
363
+ )
364
+ if response.metadata.get("terminal_tty"):
365
+ context_lines.append(f"tty: {response.metadata['terminal_tty']}")
366
+ if response.metadata.get("terminal_parent_pid"):
367
+ context_lines.append(
368
+ f"parent_pid: {response.metadata['terminal_parent_pid']}"
369
+ )
370
+ # Add terminal-specific session IDs
371
+ for key in [
372
+ "terminal_iterm_session_id",
373
+ "terminal_term_session_id",
374
+ "terminal_kitty_window_id",
375
+ "terminal_tmux_pane",
376
+ "terminal_vscode_terminal_id",
377
+ "terminal_alacritty_socket",
378
+ ]:
379
+ if response.metadata.get(key):
380
+ friendly_name = key.replace("terminal_", "").replace("_", " ")
381
+ context_lines.append(f"{friendly_name}: {response.metadata[key]}")
382
+
383
+ hook_specific["hookEventName"] = hook_event_name
384
+ # Append to existing additionalContext if present
385
+ existing = hook_specific.get("additionalContext", "")
386
+ new_context = "\n".join(context_lines)
387
+ hook_specific["additionalContext"] = (
388
+ f"{existing}\n{new_context}" if existing else new_context
266
389
  )
267
- if response.metadata.get("machine_id"):
268
- context_lines.append(f"machine_id: {response.metadata['machine_id']}")
269
- if response.metadata.get("project_id"):
270
- context_lines.append(f"project_id: {response.metadata['project_id']}")
271
- # Add terminal context (non-null values only)
272
- if response.metadata.get("terminal_term_program"):
273
- context_lines.append(f"terminal: {response.metadata['terminal_term_program']}")
274
- if response.metadata.get("terminal_tty"):
275
- context_lines.append(f"tty: {response.metadata['terminal_tty']}")
276
- if response.metadata.get("terminal_parent_pid"):
277
- context_lines.append(f"parent_pid: {response.metadata['terminal_parent_pid']}")
278
- # Add terminal-specific session IDs
279
- for key in [
280
- "terminal_iterm_session_id",
281
- "terminal_term_session_id",
282
- "terminal_kitty_window_id",
283
- "terminal_tmux_pane",
284
- "terminal_vscode_terminal_id",
285
- "terminal_alacritty_socket",
286
- ]:
287
- if response.metadata.get(key):
288
- friendly_name = key.replace("terminal_", "").replace("_", " ")
289
- context_lines.append(f"{friendly_name}: {response.metadata[key]}")
290
- hook_specific["hookEventName"] = hook_event_name
291
- # Append to existing additionalContext if present
292
- existing = hook_specific.get("additionalContext", "")
293
- new_context = "\n".join(context_lines)
294
- hook_specific["additionalContext"] = (
295
- f"{existing}\n{new_context}" if existing else new_context
296
- )
390
+ else:
391
+ # Subsequent hooks: inject minimal session ref only (~8 tokens)
392
+ if session_ref:
393
+ hook_specific["hookEventName"] = hook_event_name
394
+ existing = hook_specific.get("additionalContext", "")
395
+ minimal_context = f"Gobby Session ID: {session_ref}"
396
+ hook_specific["additionalContext"] = (
397
+ f"{existing}\n{minimal_context}" if existing else minimal_context
398
+ )
297
399
 
298
400
  # Handle BeforeModel-specific output (llm_request modification)
299
401
  if hook_type == "BeforeModel" and response.modify_args:
@@ -8,11 +8,12 @@ lifecycle behavior, solving recursion loops in delegation.
8
8
 
9
9
  import logging
10
10
  from pathlib import Path
11
- from typing import Any
11
+ from typing import Any, Literal
12
12
 
13
13
  import yaml
14
14
  from pydantic import BaseModel, Field
15
15
 
16
+ from gobby.agents.sandbox import SandboxConfig
16
17
  from gobby.utils.project_context import get_project_context
17
18
 
18
19
  logger = logging.getLogger(__name__)
@@ -29,6 +30,15 @@ class AgentDefinition(BaseModel):
29
30
  # Execution parameters
30
31
  model: str | None = None
31
32
  mode: str = "headless" # Default to headless for stability
33
+ provider: str = "claude" # Provider: claude, gemini, codex
34
+
35
+ # Isolation configuration
36
+ isolation: Literal["current", "worktree", "clone"] | None = None
37
+ branch_prefix: str | None = None
38
+ base_branch: str = "main"
39
+
40
+ # Sandbox configuration
41
+ sandbox: SandboxConfig | None = None
32
42
 
33
43
  # Workflow configuration
34
44
  workflow: str | None = None