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
gobby/storage/sessions.py CHANGED
@@ -47,10 +47,13 @@ class Session:
47
47
  usage_cache_creation_tokens: int = 0
48
48
  usage_cache_read_tokens: int = 0
49
49
  usage_total_cost_usd: float = 0.0
50
+ model: str | None = None # LLM model used (e.g., "claude-3-5-sonnet-20241022")
50
51
  # Terminal context (JSON blob with tty, parent_pid, term_session_id, etc.)
51
52
  terminal_context: dict[str, Any] | None = None
52
53
  # Global sequence number
53
54
  seq_num: int | None = None
55
+ # Edit history tracking
56
+ had_edits: bool = False
54
57
 
55
58
  @classmethod
56
59
  def from_row(cls, row: Any) -> Session:
@@ -82,8 +85,10 @@ class Session:
82
85
  usage_cache_creation_tokens=row["usage_cache_creation_tokens"] or 0,
83
86
  usage_cache_read_tokens=row["usage_cache_read_tokens"] or 0,
84
87
  usage_total_cost_usd=row["usage_total_cost_usd"] or 0.0,
88
+ model=row["model"] if "model" in row.keys() else None,
85
89
  terminal_context=cls._parse_terminal_context(row["terminal_context"]),
86
90
  seq_num=row["seq_num"] if "seq_num" in row.keys() else None,
91
+ had_edits=bool(row["had_edits"]) if "had_edits" in row.keys() else False,
87
92
  )
88
93
 
89
94
  @classmethod
@@ -133,6 +138,7 @@ class Session:
133
138
  "usage_cache_read_tokens": self.usage_cache_read_tokens,
134
139
  "usage_total_cost_usd": self.usage_total_cost_usd,
135
140
  "terminal_context": self.terminal_context,
141
+ "had_edits": self.had_edits,
136
142
  "created_at": self.created_at,
137
143
  "updated_at": self.updated_at,
138
144
  "seq_num": self.seq_num,
@@ -222,8 +228,11 @@ class LocalSessionManager:
222
228
  max_retries = 3
223
229
  for attempt in range(max_retries):
224
230
  try:
225
- # Get next seq_num (global)
226
- max_seq_row = self.db.fetchone("SELECT MAX(seq_num) as max_seq FROM sessions")
231
+ # Get next seq_num (per-project)
232
+ max_seq_row = self.db.fetchone(
233
+ "SELECT MAX(seq_num) as max_seq FROM sessions WHERE project_id = ?",
234
+ (project_id,),
235
+ )
227
236
  next_seq_num = ((max_seq_row["max_seq"] if max_seq_row else None) or 0) + 1
228
237
 
229
238
  self.db.execute(
@@ -232,9 +241,9 @@ class LocalSessionManager:
232
241
  id, external_id, machine_id, source, project_id, title,
233
242
  jsonl_path, git_branch, parent_session_id,
234
243
  agent_depth, spawned_by_agent_id, terminal_context,
235
- status, created_at, updated_at, seq_num
244
+ status, created_at, updated_at, seq_num, had_edits
236
245
  )
237
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?)
246
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, 0)
238
247
  """,
239
248
  (
240
249
  session_id,
@@ -277,18 +286,20 @@ class LocalSessionManager:
277
286
  row = self.db.fetchone("SELECT * FROM sessions WHERE id = ?", (session_id,))
278
287
  return Session.from_row(row) if row else None
279
288
 
280
- def resolve_session_reference(self, ref: str) -> str:
289
+ def resolve_session_reference(self, ref: str, project_id: str | None = None) -> str:
281
290
  """
282
291
  Resolve a session reference to a UUID.
283
292
 
284
293
  Supports:
285
- - #N: Global Sequence Number (e.g., #1)
294
+ - #N: Project-scoped Sequence Number (e.g., #1) - requires project_id
286
295
  - N: Integer string treated as #N (e.g., "1")
287
296
  - UUID: Full UUID
288
297
  - Prefix: UUID prefix (must be unambiguous)
289
298
 
290
299
  Args:
291
300
  ref: Session reference string
301
+ project_id: Project ID for project-scoped #N lookup.
302
+ If not provided, falls back to global lookup for backwards compat.
292
303
 
293
304
  Returns:
294
305
  Resolved Session UUID
@@ -306,7 +317,15 @@ class LocalSessionManager:
306
317
 
307
318
  if seq_num_ref.isdigit():
308
319
  seq_num = int(seq_num_ref)
309
- row = self.db.fetchone("SELECT id FROM sessions WHERE seq_num = ?", (seq_num,))
320
+ if project_id:
321
+ # Project-scoped lookup
322
+ row = self.db.fetchone(
323
+ "SELECT id FROM sessions WHERE project_id = ? AND seq_num = ?",
324
+ (project_id, seq_num),
325
+ )
326
+ else:
327
+ # Fallback to global lookup for backwards compat
328
+ row = self.db.fetchone("SELECT id FROM sessions WHERE seq_num = ?", (seq_num,))
310
329
  if not row:
311
330
  raise ValueError(f"Session #{seq_num} not found")
312
331
  return str(row["id"])
@@ -424,6 +443,15 @@ class LocalSessionManager:
424
443
  )
425
444
  return self.get(session_id)
426
445
 
446
+ def mark_had_edits(self, session_id: str) -> Session | None:
447
+ """Mark session as having edits."""
448
+ now = datetime.now(UTC).isoformat()
449
+ self.db.execute(
450
+ "UPDATE sessions SET had_edits = 1, updated_at = ? WHERE id = ?",
451
+ (now, session_id),
452
+ )
453
+ return self.get(session_id)
454
+
427
455
  def update_title(self, session_id: str, title: str) -> Session | None:
428
456
  """Update session title."""
429
457
  now = datetime.now(UTC).isoformat()
@@ -433,6 +461,16 @@ class LocalSessionManager:
433
461
  )
434
462
  return self.get(session_id)
435
463
 
464
+ def update_model(self, session_id: str, model: str) -> Session | None:
465
+ """Update session model (LLM model used)."""
466
+ now = datetime.now(UTC).isoformat()
467
+ with self.db.transaction():
468
+ self.db.execute(
469
+ "UPDATE sessions SET model = ?, updated_at = ? WHERE id = ?",
470
+ (model, now, session_id),
471
+ )
472
+ return self.get(session_id)
473
+
436
474
  def update_summary(
437
475
  self,
438
476
  session_id: str,
@@ -773,6 +811,77 @@ class LocalSessionManager:
773
811
  logger.error(f"Failed to update session usage {session_id}: {e}")
774
812
  return False
775
813
 
814
+ def add_cost(self, session_id: str, cost_usd: float) -> bool:
815
+ """
816
+ Add cost to the session's usage_total_cost_usd.
817
+
818
+ This is used for internal agent runs that track cost via CostInfo.
819
+ Unlike update_usage which overwrites, this method adds to the existing cost.
820
+
821
+ Args:
822
+ session_id: Session ID to update.
823
+ cost_usd: Cost in USD to add.
824
+
825
+ Returns:
826
+ True if update succeeded, False otherwise.
827
+ """
828
+ if cost_usd <= 0:
829
+ return True # Nothing to add
830
+
831
+ query = """
832
+ UPDATE sessions
833
+ SET
834
+ usage_total_cost_usd = COALESCE(usage_total_cost_usd, 0) + ?,
835
+ updated_at = datetime('now')
836
+ WHERE id = ?
837
+ """
838
+ try:
839
+ with self.db.transaction():
840
+ cursor = self.db.execute(query, (cost_usd, session_id))
841
+ return cursor.rowcount > 0
842
+ except Exception as e:
843
+ logger.error(f"Failed to add cost to session {session_id}: {e}")
844
+ return False
845
+
846
+ def get_sessions_since(
847
+ self, since: datetime, project_id: str | None = None
848
+ ) -> builtins.list[Session]:
849
+ """
850
+ Get sessions created since a given timestamp.
851
+
852
+ Used for aggregating usage over a time period (e.g., daily budget tracking).
853
+
854
+ Args:
855
+ since: Datetime to query from (sessions created after this time)
856
+ project_id: Optional project ID to filter by
857
+
858
+ Returns:
859
+ List of sessions created since the given timestamp
860
+ """
861
+ since_str = since.isoformat()
862
+
863
+ if project_id:
864
+ rows = self.db.fetchall(
865
+ """
866
+ SELECT * FROM sessions
867
+ WHERE created_at >= ?
868
+ AND project_id = ?
869
+ ORDER BY created_at DESC
870
+ """,
871
+ (since_str, project_id),
872
+ )
873
+ else:
874
+ rows = self.db.fetchall(
875
+ """
876
+ SELECT * FROM sessions
877
+ WHERE created_at >= ?
878
+ ORDER BY created_at DESC
879
+ """,
880
+ (since_str,),
881
+ )
882
+
883
+ return [Session.from_row(row) for row in rows]
884
+
776
885
  def update_terminal_pickup_metadata(
777
886
  self,
778
887
  session_id: str,