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.
- gobby/adapters/claude_code.py +13 -4
- gobby/adapters/codex.py +43 -3
- gobby/agents/runner.py +8 -0
- gobby/cli/__init__.py +6 -0
- gobby/cli/clones.py +419 -0
- gobby/cli/conductor.py +266 -0
- gobby/cli/installers/antigravity.py +3 -9
- gobby/cli/installers/claude.py +9 -9
- gobby/cli/installers/codex.py +2 -8
- gobby/cli/installers/gemini.py +2 -8
- gobby/cli/installers/shared.py +71 -8
- gobby/cli/skills.py +858 -0
- gobby/cli/tasks/ai.py +0 -440
- gobby/cli/tasks/crud.py +44 -6
- gobby/cli/tasks/main.py +0 -4
- gobby/cli/tui.py +2 -2
- gobby/cli/utils.py +3 -3
- gobby/clones/__init__.py +13 -0
- gobby/clones/git.py +547 -0
- gobby/conductor/__init__.py +16 -0
- gobby/conductor/alerts.py +135 -0
- gobby/conductor/loop.py +164 -0
- gobby/conductor/monitors/__init__.py +11 -0
- gobby/conductor/monitors/agents.py +116 -0
- gobby/conductor/monitors/tasks.py +155 -0
- gobby/conductor/pricing.py +234 -0
- gobby/conductor/token_tracker.py +160 -0
- gobby/config/app.py +63 -1
- gobby/config/search.py +110 -0
- gobby/config/servers.py +1 -1
- gobby/config/skills.py +43 -0
- gobby/config/tasks.py +6 -14
- gobby/hooks/event_handlers.py +145 -2
- gobby/hooks/hook_manager.py +48 -2
- gobby/hooks/skill_manager.py +130 -0
- gobby/install/claude/hooks/hook_dispatcher.py +4 -4
- gobby/install/codex/hooks/hook_dispatcher.py +1 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
- gobby/llm/claude.py +22 -34
- gobby/llm/claude_executor.py +46 -256
- gobby/llm/codex_executor.py +59 -291
- gobby/llm/executor.py +21 -0
- gobby/llm/gemini.py +134 -110
- gobby/llm/litellm_executor.py +143 -6
- gobby/llm/resolver.py +95 -33
- gobby/mcp_proxy/instructions.py +54 -0
- gobby/mcp_proxy/models.py +15 -0
- gobby/mcp_proxy/registries.py +68 -5
- gobby/mcp_proxy/server.py +33 -3
- gobby/mcp_proxy/services/tool_proxy.py +81 -1
- gobby/mcp_proxy/stdio.py +2 -1
- gobby/mcp_proxy/tools/__init__.py +0 -2
- gobby/mcp_proxy/tools/agent_messaging.py +317 -0
- gobby/mcp_proxy/tools/clones.py +903 -0
- gobby/mcp_proxy/tools/memory.py +1 -24
- gobby/mcp_proxy/tools/metrics.py +65 -1
- gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
- gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
- gobby/mcp_proxy/tools/session_messages.py +1 -2
- gobby/mcp_proxy/tools/skills/__init__.py +631 -0
- gobby/mcp_proxy/tools/task_orchestration.py +7 -0
- gobby/mcp_proxy/tools/task_readiness.py +14 -0
- gobby/mcp_proxy/tools/task_sync.py +1 -1
- gobby/mcp_proxy/tools/tasks/_context.py +0 -20
- gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
- gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +60 -29
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
- gobby/mcp_proxy/tools/workflows.py +1 -1
- gobby/mcp_proxy/tools/worktrees.py +5 -0
- gobby/memory/backends/__init__.py +6 -1
- gobby/memory/backends/mem0.py +6 -1
- gobby/memory/extractor.py +477 -0
- gobby/memory/manager.py +11 -2
- gobby/prompts/defaults/handoff/compact.md +63 -0
- gobby/prompts/defaults/handoff/session_end.md +57 -0
- gobby/prompts/defaults/memory/extract.md +61 -0
- gobby/runner.py +37 -16
- gobby/search/__init__.py +48 -6
- gobby/search/backends/__init__.py +159 -0
- gobby/search/backends/embedding.py +225 -0
- gobby/search/embeddings.py +238 -0
- gobby/search/models.py +148 -0
- gobby/search/unified.py +496 -0
- gobby/servers/http.py +23 -8
- gobby/servers/routes/admin.py +280 -0
- gobby/servers/routes/mcp/tools.py +241 -52
- gobby/servers/websocket.py +2 -2
- gobby/sessions/analyzer.py +2 -0
- gobby/sessions/transcripts/base.py +1 -0
- gobby/sessions/transcripts/claude.py +64 -5
- gobby/skills/__init__.py +91 -0
- gobby/skills/loader.py +685 -0
- gobby/skills/manager.py +384 -0
- gobby/skills/parser.py +258 -0
- gobby/skills/search.py +463 -0
- gobby/skills/sync.py +119 -0
- gobby/skills/updater.py +385 -0
- gobby/skills/validator.py +368 -0
- gobby/storage/clones.py +378 -0
- gobby/storage/database.py +1 -1
- gobby/storage/memories.py +43 -13
- gobby/storage/migrations.py +180 -6
- gobby/storage/sessions.py +73 -0
- gobby/storage/skills.py +749 -0
- gobby/storage/tasks/_crud.py +4 -4
- gobby/storage/tasks/_lifecycle.py +41 -6
- gobby/storage/tasks/_manager.py +14 -5
- gobby/storage/tasks/_models.py +8 -3
- gobby/sync/memories.py +39 -4
- gobby/sync/tasks.py +83 -6
- gobby/tasks/__init__.py +1 -2
- gobby/tasks/validation.py +24 -15
- gobby/tui/api_client.py +4 -7
- gobby/tui/app.py +5 -3
- gobby/tui/screens/orchestrator.py +1 -2
- gobby/tui/screens/tasks.py +2 -4
- gobby/tui/ws_client.py +1 -1
- gobby/utils/daemon_client.py +2 -2
- gobby/workflows/actions.py +84 -2
- gobby/workflows/context_actions.py +43 -0
- gobby/workflows/detection_helpers.py +115 -31
- gobby/workflows/engine.py +13 -2
- gobby/workflows/lifecycle_evaluator.py +29 -1
- gobby/workflows/loader.py +19 -6
- gobby/workflows/memory_actions.py +74 -0
- gobby/workflows/summary_actions.py +17 -0
- gobby/workflows/task_enforcement_actions.py +448 -6
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/METADATA +82 -21
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/RECORD +136 -107
- gobby/install/codex/prompts/forget.md +0 -7
- gobby/install/codex/prompts/memories.md +0 -7
- gobby/install/codex/prompts/recall.md +0 -7
- gobby/install/codex/prompts/remember.md +0 -13
- gobby/llm/gemini_executor.py +0 -339
- gobby/mcp_proxy/tools/task_expansion.py +0 -591
- gobby/tasks/context.py +0 -747
- gobby/tasks/criteria.py +0 -342
- gobby/tasks/expansion.py +0 -626
- gobby/tasks/prompts/expand.py +0 -327
- gobby/tasks/research.py +0 -421
- gobby/tasks/tdd.py +0 -352
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/WHEEL +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/top_level.txt +0 -0
gobby/servers/routes/admin.py
CHANGED
|
@@ -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
|