gobby 0.2.5__py3-none-any.whl → 0.2.6__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 (148) hide show
  1. gobby/adapters/claude_code.py +13 -4
  2. gobby/adapters/codex.py +43 -3
  3. gobby/agents/runner.py +8 -0
  4. gobby/cli/__init__.py +6 -0
  5. gobby/cli/clones.py +419 -0
  6. gobby/cli/conductor.py +266 -0
  7. gobby/cli/installers/antigravity.py +3 -9
  8. gobby/cli/installers/claude.py +9 -9
  9. gobby/cli/installers/codex.py +2 -8
  10. gobby/cli/installers/gemini.py +2 -8
  11. gobby/cli/installers/shared.py +71 -8
  12. gobby/cli/skills.py +858 -0
  13. gobby/cli/tasks/ai.py +0 -440
  14. gobby/cli/tasks/crud.py +44 -6
  15. gobby/cli/tasks/main.py +0 -4
  16. gobby/cli/tui.py +2 -2
  17. gobby/cli/utils.py +3 -3
  18. gobby/clones/__init__.py +13 -0
  19. gobby/clones/git.py +547 -0
  20. gobby/conductor/__init__.py +16 -0
  21. gobby/conductor/alerts.py +135 -0
  22. gobby/conductor/loop.py +164 -0
  23. gobby/conductor/monitors/__init__.py +11 -0
  24. gobby/conductor/monitors/agents.py +116 -0
  25. gobby/conductor/monitors/tasks.py +155 -0
  26. gobby/conductor/pricing.py +234 -0
  27. gobby/conductor/token_tracker.py +160 -0
  28. gobby/config/app.py +63 -1
  29. gobby/config/search.py +110 -0
  30. gobby/config/servers.py +1 -1
  31. gobby/config/skills.py +43 -0
  32. gobby/config/tasks.py +6 -14
  33. gobby/hooks/event_handlers.py +145 -2
  34. gobby/hooks/hook_manager.py +48 -2
  35. gobby/hooks/skill_manager.py +130 -0
  36. gobby/install/claude/hooks/hook_dispatcher.py +4 -4
  37. gobby/install/codex/hooks/hook_dispatcher.py +1 -1
  38. gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
  39. gobby/llm/claude.py +22 -34
  40. gobby/llm/claude_executor.py +46 -256
  41. gobby/llm/codex_executor.py +59 -291
  42. gobby/llm/executor.py +21 -0
  43. gobby/llm/gemini.py +134 -110
  44. gobby/llm/litellm_executor.py +143 -6
  45. gobby/llm/resolver.py +95 -33
  46. gobby/mcp_proxy/instructions.py +54 -0
  47. gobby/mcp_proxy/models.py +15 -0
  48. gobby/mcp_proxy/registries.py +68 -5
  49. gobby/mcp_proxy/server.py +33 -3
  50. gobby/mcp_proxy/services/tool_proxy.py +81 -1
  51. gobby/mcp_proxy/stdio.py +2 -1
  52. gobby/mcp_proxy/tools/__init__.py +0 -2
  53. gobby/mcp_proxy/tools/agent_messaging.py +317 -0
  54. gobby/mcp_proxy/tools/clones.py +903 -0
  55. gobby/mcp_proxy/tools/memory.py +1 -24
  56. gobby/mcp_proxy/tools/metrics.py +65 -1
  57. gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
  58. gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
  59. gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
  60. gobby/mcp_proxy/tools/session_messages.py +1 -2
  61. gobby/mcp_proxy/tools/skills/__init__.py +631 -0
  62. gobby/mcp_proxy/tools/task_orchestration.py +7 -0
  63. gobby/mcp_proxy/tools/task_readiness.py +14 -0
  64. gobby/mcp_proxy/tools/task_sync.py +1 -1
  65. gobby/mcp_proxy/tools/tasks/_context.py +0 -20
  66. gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
  67. gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
  68. gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
  69. gobby/mcp_proxy/tools/tasks/_lifecycle.py +60 -29
  70. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
  71. gobby/mcp_proxy/tools/workflows.py +1 -1
  72. gobby/mcp_proxy/tools/worktrees.py +5 -0
  73. gobby/memory/backends/__init__.py +6 -1
  74. gobby/memory/backends/mem0.py +6 -1
  75. gobby/memory/extractor.py +477 -0
  76. gobby/memory/manager.py +11 -2
  77. gobby/prompts/defaults/handoff/compact.md +63 -0
  78. gobby/prompts/defaults/handoff/session_end.md +57 -0
  79. gobby/prompts/defaults/memory/extract.md +61 -0
  80. gobby/runner.py +37 -16
  81. gobby/search/__init__.py +48 -6
  82. gobby/search/backends/__init__.py +159 -0
  83. gobby/search/backends/embedding.py +225 -0
  84. gobby/search/embeddings.py +238 -0
  85. gobby/search/models.py +148 -0
  86. gobby/search/unified.py +496 -0
  87. gobby/servers/http.py +23 -8
  88. gobby/servers/routes/admin.py +280 -0
  89. gobby/servers/routes/mcp/tools.py +241 -52
  90. gobby/servers/websocket.py +2 -2
  91. gobby/sessions/analyzer.py +2 -0
  92. gobby/sessions/transcripts/base.py +1 -0
  93. gobby/sessions/transcripts/claude.py +64 -5
  94. gobby/skills/__init__.py +91 -0
  95. gobby/skills/loader.py +685 -0
  96. gobby/skills/manager.py +384 -0
  97. gobby/skills/parser.py +258 -0
  98. gobby/skills/search.py +463 -0
  99. gobby/skills/sync.py +119 -0
  100. gobby/skills/updater.py +385 -0
  101. gobby/skills/validator.py +368 -0
  102. gobby/storage/clones.py +378 -0
  103. gobby/storage/database.py +1 -1
  104. gobby/storage/memories.py +43 -13
  105. gobby/storage/migrations.py +180 -6
  106. gobby/storage/sessions.py +73 -0
  107. gobby/storage/skills.py +749 -0
  108. gobby/storage/tasks/_crud.py +4 -4
  109. gobby/storage/tasks/_lifecycle.py +41 -6
  110. gobby/storage/tasks/_manager.py +14 -5
  111. gobby/storage/tasks/_models.py +8 -3
  112. gobby/sync/memories.py +39 -4
  113. gobby/sync/tasks.py +83 -6
  114. gobby/tasks/__init__.py +1 -2
  115. gobby/tasks/validation.py +24 -15
  116. gobby/tui/api_client.py +4 -7
  117. gobby/tui/app.py +5 -3
  118. gobby/tui/screens/orchestrator.py +1 -2
  119. gobby/tui/screens/tasks.py +2 -4
  120. gobby/tui/ws_client.py +1 -1
  121. gobby/utils/daemon_client.py +2 -2
  122. gobby/workflows/actions.py +84 -2
  123. gobby/workflows/context_actions.py +43 -0
  124. gobby/workflows/detection_helpers.py +115 -31
  125. gobby/workflows/engine.py +13 -2
  126. gobby/workflows/lifecycle_evaluator.py +29 -1
  127. gobby/workflows/loader.py +19 -6
  128. gobby/workflows/memory_actions.py +74 -0
  129. gobby/workflows/summary_actions.py +17 -0
  130. gobby/workflows/task_enforcement_actions.py +448 -6
  131. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/METADATA +82 -21
  132. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/RECORD +136 -107
  133. gobby/install/codex/prompts/forget.md +0 -7
  134. gobby/install/codex/prompts/memories.md +0 -7
  135. gobby/install/codex/prompts/recall.md +0 -7
  136. gobby/install/codex/prompts/remember.md +0 -13
  137. gobby/llm/gemini_executor.py +0 -339
  138. gobby/mcp_proxy/tools/task_expansion.py +0 -591
  139. gobby/tasks/context.py +0 -747
  140. gobby/tasks/criteria.py +0 -342
  141. gobby/tasks/expansion.py +0 -626
  142. gobby/tasks/prompts/expand.py +0 -327
  143. gobby/tasks/research.py +0 -421
  144. gobby/tasks/tdd.py +0 -352
  145. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/WHEEL +0 -0
  146. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/entry_points.txt +0 -0
  147. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/licenses/LICENSE.md +0 -0
  148. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Any
13
13
  import psutil
14
14
  from fastapi import APIRouter
15
15
  from fastapi.responses import PlainTextResponse
16
+ from pydantic import BaseModel
16
17
 
17
18
  from gobby.utils.metrics import Counter, get_metrics_collector
18
19
  from gobby.utils.version import get_version
@@ -413,4 +414,283 @@ def create_admin_router(server: "HTTPServer") -> APIRouter:
413
414
 
414
415
  raise HTTPException(status_code=500, detail=str(e)) from e
415
416
 
417
+ # --- Test endpoints (for E2E testing) ---
418
+
419
+ class TestProjectRegisterRequest(BaseModel):
420
+ """Request model for registering a test project."""
421
+
422
+ project_id: str
423
+ name: str
424
+ repo_path: str | None = None
425
+
426
+ @router.post("/test/register-project")
427
+ async def register_test_project(request: TestProjectRegisterRequest) -> dict[str, Any]:
428
+ """
429
+ Register a test project in the database.
430
+
431
+ This endpoint is for E2E testing. It ensures the project exists
432
+ in the projects table so sessions can be created with valid project_ids.
433
+
434
+ Args:
435
+ request: Project registration details
436
+
437
+ Returns:
438
+ Registration confirmation
439
+ """
440
+ from fastapi import HTTPException
441
+
442
+ from gobby.storage.projects import LocalProjectManager
443
+
444
+ # Guard: Only available in test mode
445
+ if not server.test_mode:
446
+ raise HTTPException(
447
+ status_code=403, detail="Test endpoints only available in test mode"
448
+ )
449
+
450
+ start_time = time.perf_counter()
451
+ metrics = get_metrics_collector()
452
+ metrics.inc_counter("http_requests_total")
453
+
454
+ try:
455
+ # Use server's session manager database to avoid creating separate connections
456
+ if server.session_manager is None:
457
+ raise HTTPException(status_code=503, detail="Session manager not available")
458
+
459
+ db = server.session_manager.db
460
+
461
+ project_manager = LocalProjectManager(db)
462
+
463
+ # Check if project exists
464
+ existing = project_manager.get(request.project_id)
465
+ if existing:
466
+ return {
467
+ "status": "already_exists",
468
+ "project_id": existing.id,
469
+ "name": existing.name,
470
+ }
471
+
472
+ # Create the project with the specific ID
473
+ from datetime import UTC, datetime
474
+
475
+ now = datetime.now(UTC).isoformat()
476
+ db.execute(
477
+ """
478
+ INSERT INTO projects (id, name, repo_path, created_at, updated_at)
479
+ VALUES (?, ?, ?, ?, ?)
480
+ """,
481
+ (request.project_id, request.name, request.repo_path, now, now),
482
+ )
483
+
484
+ response_time_ms = (time.perf_counter() - start_time) * 1000
485
+
486
+ return {
487
+ "status": "success",
488
+ "message": f"Registered test project {request.project_id}",
489
+ "project_id": request.project_id,
490
+ "name": request.name,
491
+ "response_time_ms": response_time_ms,
492
+ }
493
+
494
+ except Exception as e:
495
+ metrics.inc_counter("http_requests_errors_total")
496
+ logger.error(f"Error registering test project: {e}", exc_info=True)
497
+ from fastapi import HTTPException
498
+
499
+ raise HTTPException(status_code=500, detail=str(e)) from e
500
+
501
+ class TestAgentRegisterRequest(BaseModel):
502
+ """Request model for registering a test agent."""
503
+
504
+ run_id: str
505
+ session_id: str
506
+ parent_session_id: str
507
+ mode: str = "terminal"
508
+
509
+ @router.post("/test/register-agent")
510
+ async def register_test_agent(request: TestAgentRegisterRequest) -> dict[str, Any]:
511
+ """
512
+ Register a test agent in the running agent registry.
513
+
514
+ This endpoint is for E2E testing of inter-agent messaging.
515
+ It allows tests to set up parent-child agent relationships
516
+ without actually spawning agent processes.
517
+
518
+ Args:
519
+ request: Agent registration details
520
+
521
+ Returns:
522
+ Registration confirmation
523
+ """
524
+ from fastapi import HTTPException
525
+
526
+ from gobby.agents.registry import RunningAgent, get_running_agent_registry
527
+
528
+ # Guard: Only available in test mode
529
+ if not server.test_mode:
530
+ raise HTTPException(
531
+ status_code=403, detail="Test endpoints only available in test mode"
532
+ )
533
+
534
+ start_time = time.perf_counter()
535
+ metrics = get_metrics_collector()
536
+ metrics.inc_counter("http_requests_total")
537
+
538
+ try:
539
+ registry = get_running_agent_registry()
540
+
541
+ # Create and register the agent
542
+ running_agent = RunningAgent(
543
+ run_id=request.run_id,
544
+ session_id=request.session_id,
545
+ parent_session_id=request.parent_session_id,
546
+ mode=request.mode,
547
+ )
548
+ registry.add(running_agent)
549
+
550
+ response_time_ms = (time.perf_counter() - start_time) * 1000
551
+
552
+ return {
553
+ "status": "success",
554
+ "message": f"Registered test agent {request.run_id}",
555
+ "agent": running_agent.to_dict(),
556
+ "response_time_ms": response_time_ms,
557
+ }
558
+
559
+ except Exception as e:
560
+ metrics.inc_counter("http_requests_errors_total")
561
+ logger.error(f"Error registering test agent: {e}", exc_info=True)
562
+ from fastapi import HTTPException
563
+
564
+ raise HTTPException(status_code=500, detail=str(e)) from e
565
+
566
+ @router.delete("/test/unregister-agent/{run_id}")
567
+ async def unregister_test_agent(run_id: str) -> dict[str, Any]:
568
+ """
569
+ Unregister a test agent from the running agent registry.
570
+
571
+ Args:
572
+ run_id: The agent run ID to remove
573
+
574
+ Returns:
575
+ Unregistration confirmation
576
+ """
577
+ from fastapi import HTTPException
578
+
579
+ from gobby.agents.registry import get_running_agent_registry
580
+
581
+ # Guard: Only available in test mode
582
+ if not server.test_mode:
583
+ raise HTTPException(
584
+ status_code=403, detail="Test endpoints only available in test mode"
585
+ )
586
+
587
+ start_time = time.perf_counter()
588
+ metrics = get_metrics_collector()
589
+ metrics.inc_counter("http_requests_total")
590
+
591
+ try:
592
+ registry = get_running_agent_registry()
593
+ agent = registry.remove(run_id)
594
+
595
+ response_time_ms = (time.perf_counter() - start_time) * 1000
596
+
597
+ if agent:
598
+ return {
599
+ "status": "success",
600
+ "message": f"Unregistered test agent {run_id}",
601
+ "response_time_ms": response_time_ms,
602
+ }
603
+ else:
604
+ return {
605
+ "status": "not_found",
606
+ "message": f"Agent {run_id} not found in registry",
607
+ "response_time_ms": response_time_ms,
608
+ }
609
+
610
+ except Exception as e:
611
+ metrics.inc_counter("http_requests_errors_total")
612
+ logger.error(f"Error unregistering test agent: {e}", exc_info=True)
613
+ from fastapi import HTTPException
614
+
615
+ raise HTTPException(status_code=500, detail=str(e)) from e
616
+
617
+ class TestSessionUsageRequest(BaseModel):
618
+ """Request body for setting test session usage."""
619
+
620
+ session_id: str
621
+ input_tokens: int = 0
622
+ output_tokens: int = 0
623
+ cache_creation_tokens: int = 0
624
+ cache_read_tokens: int = 0
625
+ total_cost_usd: float = 0.0
626
+
627
+ @router.post("/test/set-session-usage")
628
+ async def set_test_session_usage(request: TestSessionUsageRequest) -> dict[str, Any]:
629
+ """
630
+ Set usage statistics for a test session.
631
+
632
+ This endpoint is for E2E testing of token budget throttling.
633
+ It allows tests to set session usage values to simulate
634
+ budget consumption.
635
+
636
+ Args:
637
+ request: Session usage details
638
+
639
+ Returns:
640
+ Update confirmation
641
+ """
642
+ from fastapi import HTTPException
643
+
644
+ # Guard: Only available in test mode
645
+ if not server.test_mode:
646
+ raise HTTPException(
647
+ status_code=403, detail="Test endpoints only available in test mode"
648
+ )
649
+
650
+ start_time = time.perf_counter()
651
+ metrics = get_metrics_collector()
652
+ metrics.inc_counter("http_requests_total")
653
+
654
+ try:
655
+ if server.session_manager is None:
656
+ raise HTTPException(status_code=503, detail="Session manager not available")
657
+
658
+ success = server.session_manager.update_usage(
659
+ session_id=request.session_id,
660
+ input_tokens=request.input_tokens,
661
+ output_tokens=request.output_tokens,
662
+ cache_creation_tokens=request.cache_creation_tokens,
663
+ cache_read_tokens=request.cache_read_tokens,
664
+ total_cost_usd=request.total_cost_usd,
665
+ )
666
+
667
+ response_time_ms = (time.perf_counter() - start_time) * 1000
668
+
669
+ if success:
670
+ return {
671
+ "status": "success",
672
+ "session_id": request.session_id,
673
+ "usage_set": {
674
+ "input_tokens": request.input_tokens,
675
+ "output_tokens": request.output_tokens,
676
+ "cache_creation_tokens": request.cache_creation_tokens,
677
+ "cache_read_tokens": request.cache_read_tokens,
678
+ "total_cost_usd": request.total_cost_usd,
679
+ },
680
+ "response_time_ms": response_time_ms,
681
+ }
682
+ else:
683
+ return {
684
+ "status": "not_found",
685
+ "message": f"Session {request.session_id} not found",
686
+ "response_time_ms": response_time_ms,
687
+ }
688
+
689
+ except Exception as e:
690
+ metrics.inc_counter("http_requests_errors_total")
691
+ logger.error(f"Error setting test session usage: {e}", exc_info=True)
692
+ from fastapi import HTTPException
693
+
694
+ raise HTTPException(status_code=500, detail=str(e)) from e
695
+
416
696
  return router