omni-cortex 1.14.0__tar.gz → 1.16.0__tar.gz
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.
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/.gitignore +1 -1
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/PKG-INFO +1 -1
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/database.py +120 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/main.py +173 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/models.py +68 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/__init__.py +1 -1
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/pyproject.toml +1 -1
- omni_cortex-1.16.0/scripts/generate_storage_architecture_pdf.py +572 -0
- omni_cortex-1.16.0/scripts/update_docs_pdfs.py +1263 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/LICENSE +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/README.md +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/.env.example +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/backfill_summaries.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/chat_service.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/image_service.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/logging_config.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/project_config.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/project_scanner.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/prompt_security.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/pyproject.toml +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/security.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/uv.lock +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/dashboard/backend/websocket_manager.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/hooks/post_tool_use.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/hooks/pre_tool_use.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/hooks/session_utils.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/hooks/stop.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/hooks/subagent_stop.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/hooks/user_prompt.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/categorization/__init__.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/categorization/auto_tags.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/categorization/auto_type.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/config.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/dashboard.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/database/__init__.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/database/connection.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/database/migrations.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/database/schema.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/database/sync.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/decay/__init__.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/decay/importance.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/embeddings/__init__.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/embeddings/local.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/models/__init__.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/models/activity.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/models/agent.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/models/memory.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/models/relationship.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/models/session.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/resources/__init__.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/search/__init__.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/search/hybrid.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/search/keyword.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/search/ranking.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/search/semantic.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/server.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/setup.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/tools/__init__.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/tools/activities.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/tools/memories.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/tools/sessions.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/tools/utilities.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/utils/__init__.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/utils/formatting.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/utils/ids.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/utils/timestamps.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/omni_cortex/utils/truncation.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/scripts/check-venv.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/scripts/import_ken_memories.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/scripts/populate_session_data.py +0 -0
- {omni_cortex-1.14.0 → omni_cortex-1.16.0}/scripts/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omni-cortex
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.16.0
|
|
4
4
|
Summary: Give Claude Code a perfect memory - auto-logs everything, searches smartly, and gets smarter over time
|
|
5
5
|
Project-URL: Homepage, https://github.com/AllCytes/Omni-Cortex
|
|
6
6
|
Project-URL: Repository, https://github.com/AllCytes/Omni-Cortex
|
|
@@ -1651,3 +1651,123 @@ def compute_style_profile_from_messages(db_path: str) -> Optional[dict]:
|
|
|
1651
1651
|
"styleMarkers": style_markers,
|
|
1652
1652
|
"sampleMessages": sample_messages,
|
|
1653
1653
|
}
|
|
1654
|
+
|
|
1655
|
+
|
|
1656
|
+
# --- Agent Query Functions ---
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
def get_agents(
|
|
1660
|
+
db_path: str,
|
|
1661
|
+
agent_type: Optional[str] = None,
|
|
1662
|
+
limit: int = 50,
|
|
1663
|
+
active_only: bool = False
|
|
1664
|
+
) -> list[dict]:
|
|
1665
|
+
"""Get agents with recent activity counts."""
|
|
1666
|
+
conn = get_connection(db_path)
|
|
1667
|
+
|
|
1668
|
+
query = """
|
|
1669
|
+
SELECT
|
|
1670
|
+
a.*,
|
|
1671
|
+
COALESCE(recent.count, 0) as recent_activity_count,
|
|
1672
|
+
CASE WHEN a.last_seen > datetime('now', '-5 minutes') THEN 1 ELSE 0 END as is_active
|
|
1673
|
+
FROM agents a
|
|
1674
|
+
LEFT JOIN (
|
|
1675
|
+
SELECT agent_id, COUNT(*) as count
|
|
1676
|
+
FROM activities
|
|
1677
|
+
WHERE timestamp > datetime('now', '-1 hour')
|
|
1678
|
+
GROUP BY agent_id
|
|
1679
|
+
) recent ON recent.agent_id = a.id
|
|
1680
|
+
WHERE 1=1
|
|
1681
|
+
"""
|
|
1682
|
+
params = []
|
|
1683
|
+
|
|
1684
|
+
if agent_type:
|
|
1685
|
+
query += " AND a.type = ?"
|
|
1686
|
+
params.append(agent_type)
|
|
1687
|
+
|
|
1688
|
+
if active_only:
|
|
1689
|
+
query += " AND a.last_seen > datetime('now', '-5 minutes')"
|
|
1690
|
+
|
|
1691
|
+
query += " ORDER BY a.last_seen DESC LIMIT ?"
|
|
1692
|
+
params.append(limit)
|
|
1693
|
+
|
|
1694
|
+
cursor = conn.execute(query, params)
|
|
1695
|
+
results = [dict(row) for row in cursor.fetchall()]
|
|
1696
|
+
conn.close()
|
|
1697
|
+
return results
|
|
1698
|
+
|
|
1699
|
+
|
|
1700
|
+
def get_agent_by_id(db_path: str, agent_id: str) -> Optional[dict]:
|
|
1701
|
+
"""Get single agent by ID."""
|
|
1702
|
+
conn = get_connection(db_path)
|
|
1703
|
+
|
|
1704
|
+
cursor = conn.execute("""
|
|
1705
|
+
SELECT
|
|
1706
|
+
a.*,
|
|
1707
|
+
CASE WHEN a.last_seen > datetime('now', '-5 minutes') THEN 1 ELSE 0 END as is_active
|
|
1708
|
+
FROM agents a
|
|
1709
|
+
WHERE a.id = ?
|
|
1710
|
+
""", (agent_id,))
|
|
1711
|
+
|
|
1712
|
+
row = cursor.fetchone()
|
|
1713
|
+
conn.close()
|
|
1714
|
+
return dict(row) if row else None
|
|
1715
|
+
|
|
1716
|
+
|
|
1717
|
+
def get_agent_tool_breakdown(db_path: str, agent_id: str) -> list[dict]:
|
|
1718
|
+
"""Get tool usage breakdown for an agent."""
|
|
1719
|
+
conn = get_connection(db_path)
|
|
1720
|
+
|
|
1721
|
+
cursor = conn.execute("""
|
|
1722
|
+
SELECT
|
|
1723
|
+
tool_name,
|
|
1724
|
+
COUNT(*) as count,
|
|
1725
|
+
AVG(duration_ms) as avg_duration_ms,
|
|
1726
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as success_rate
|
|
1727
|
+
FROM activities
|
|
1728
|
+
WHERE agent_id = ? AND tool_name IS NOT NULL
|
|
1729
|
+
GROUP BY tool_name
|
|
1730
|
+
ORDER BY count DESC
|
|
1731
|
+
""", (agent_id,))
|
|
1732
|
+
|
|
1733
|
+
results = [dict(row) for row in cursor.fetchall()]
|
|
1734
|
+
conn.close()
|
|
1735
|
+
return results
|
|
1736
|
+
|
|
1737
|
+
|
|
1738
|
+
def get_agent_files_touched(db_path: str, agent_id: str, limit: int = 50) -> list[str]:
|
|
1739
|
+
"""Get list of files an agent has touched."""
|
|
1740
|
+
conn = get_connection(db_path)
|
|
1741
|
+
|
|
1742
|
+
# Files from file_path column
|
|
1743
|
+
cursor = conn.execute("""
|
|
1744
|
+
SELECT DISTINCT file_path
|
|
1745
|
+
FROM activities
|
|
1746
|
+
WHERE agent_id = ?
|
|
1747
|
+
AND file_path IS NOT NULL
|
|
1748
|
+
AND file_path != ''
|
|
1749
|
+
LIMIT ?
|
|
1750
|
+
""", (agent_id, limit))
|
|
1751
|
+
|
|
1752
|
+
files = [row[0] for row in cursor.fetchall()]
|
|
1753
|
+
conn.close()
|
|
1754
|
+
return files
|
|
1755
|
+
|
|
1756
|
+
|
|
1757
|
+
def get_agent_parent(db_path: str, agent_id: str) -> Optional[str]:
|
|
1758
|
+
"""Find which agent spawned this subagent (via Task tool)."""
|
|
1759
|
+
conn = get_connection(db_path)
|
|
1760
|
+
|
|
1761
|
+
# Look for Task tool call that created this agent
|
|
1762
|
+
cursor = conn.execute("""
|
|
1763
|
+
SELECT agent_id
|
|
1764
|
+
FROM activities
|
|
1765
|
+
WHERE tool_name = 'Task'
|
|
1766
|
+
AND tool_output LIKE ?
|
|
1767
|
+
ORDER BY timestamp DESC
|
|
1768
|
+
LIMIT 1
|
|
1769
|
+
""", (f'%{agent_id}%',))
|
|
1770
|
+
|
|
1771
|
+
row = cursor.fetchone()
|
|
1772
|
+
conn.close()
|
|
1773
|
+
return row[0] if row else None
|
|
@@ -39,6 +39,11 @@ from database import (
|
|
|
39
39
|
get_activities,
|
|
40
40
|
get_activity_detail,
|
|
41
41
|
get_activity_heatmap,
|
|
42
|
+
get_agents,
|
|
43
|
+
get_agent_by_id,
|
|
44
|
+
get_agent_files_touched,
|
|
45
|
+
get_agent_parent,
|
|
46
|
+
get_agent_tool_breakdown,
|
|
42
47
|
get_all_tags,
|
|
43
48
|
get_command_usage,
|
|
44
49
|
get_mcp_usage,
|
|
@@ -1625,6 +1630,174 @@ async def export_memories(
|
|
|
1625
1630
|
raise HTTPException(status_code=400, detail=f"Unsupported format: {format}. Use json, markdown, or csv.")
|
|
1626
1631
|
|
|
1627
1632
|
|
|
1633
|
+
# --- Agent Endpoints ---
|
|
1634
|
+
|
|
1635
|
+
|
|
1636
|
+
@app.get("/api/agents")
|
|
1637
|
+
async def list_agents(
|
|
1638
|
+
project: str = Query(..., description="Path to the database file"),
|
|
1639
|
+
type: Optional[str] = Query(None, description="Filter by agent type: main, subagent, tool"),
|
|
1640
|
+
active_only: bool = Query(False, description="Show only active agents (last 5 minutes)"),
|
|
1641
|
+
limit: int = Query(50, ge=1, le=200),
|
|
1642
|
+
):
|
|
1643
|
+
"""List all agents with filtering."""
|
|
1644
|
+
if not Path(project).exists():
|
|
1645
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
1646
|
+
|
|
1647
|
+
agents = get_agents(project, agent_type=type, limit=limit, active_only=active_only)
|
|
1648
|
+
return {"agents": agents, "count": len(agents)}
|
|
1649
|
+
|
|
1650
|
+
|
|
1651
|
+
@app.get("/api/agents/{agent_id}")
|
|
1652
|
+
async def get_agent(
|
|
1653
|
+
agent_id: str,
|
|
1654
|
+
project: str = Query(..., description="Path to the database file"),
|
|
1655
|
+
):
|
|
1656
|
+
"""Get single agent details."""
|
|
1657
|
+
if not Path(project).exists():
|
|
1658
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
1659
|
+
|
|
1660
|
+
agent = get_agent_by_id(project, agent_id)
|
|
1661
|
+
if not agent:
|
|
1662
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
1663
|
+
|
|
1664
|
+
return agent
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
@app.get("/api/agents/{agent_id}/stats")
|
|
1668
|
+
async def get_agent_stats_endpoint(
|
|
1669
|
+
agent_id: str,
|
|
1670
|
+
project: str = Query(..., description="Path to the database file"),
|
|
1671
|
+
):
|
|
1672
|
+
"""Get detailed stats for an agent."""
|
|
1673
|
+
if not Path(project).exists():
|
|
1674
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
1675
|
+
|
|
1676
|
+
agent = get_agent_by_id(project, agent_id)
|
|
1677
|
+
if not agent:
|
|
1678
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
1679
|
+
|
|
1680
|
+
tool_breakdown = get_agent_tool_breakdown(project, agent_id)
|
|
1681
|
+
files_touched = get_agent_files_touched(project, agent_id)
|
|
1682
|
+
parent_agent = get_agent_parent(project, agent_id) if agent.get('type') == 'subagent' else None
|
|
1683
|
+
|
|
1684
|
+
return {
|
|
1685
|
+
"agent": agent,
|
|
1686
|
+
"tool_breakdown": tool_breakdown,
|
|
1687
|
+
"files_touched": files_touched,
|
|
1688
|
+
"parent_agent_id": parent_agent,
|
|
1689
|
+
"adw_phase": None # Will be populated in Part 2 with ADW integration
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
|
|
1693
|
+
# --- ADW Endpoints ---
|
|
1694
|
+
|
|
1695
|
+
|
|
1696
|
+
def scan_adw_folder() -> list[dict]:
|
|
1697
|
+
"""Scan agents/ folder for ADW runs."""
|
|
1698
|
+
agents_dir = Path("agents")
|
|
1699
|
+
if not agents_dir.exists():
|
|
1700
|
+
return []
|
|
1701
|
+
|
|
1702
|
+
adw_runs = []
|
|
1703
|
+
for adw_dir in agents_dir.iterdir():
|
|
1704
|
+
if adw_dir.is_dir() and adw_dir.name.startswith("adw_"):
|
|
1705
|
+
state_file = adw_dir / "adw_state.json"
|
|
1706
|
+
if state_file.exists():
|
|
1707
|
+
try:
|
|
1708
|
+
state = json.loads(state_file.read_text())
|
|
1709
|
+
adw_runs.append({
|
|
1710
|
+
"adw_id": state.get("adw_id", adw_dir.name),
|
|
1711
|
+
"created_at": state.get("created_at"),
|
|
1712
|
+
"status": state.get("status", "unknown"),
|
|
1713
|
+
"current_phase": state.get("current_phase", "unknown"),
|
|
1714
|
+
"phases_completed": len(state.get("completed_phases", [])),
|
|
1715
|
+
"phases_total": 4 # plan, build, validate, release
|
|
1716
|
+
})
|
|
1717
|
+
except json.JSONDecodeError:
|
|
1718
|
+
pass
|
|
1719
|
+
|
|
1720
|
+
# Sort by created_at descending
|
|
1721
|
+
adw_runs.sort(key=lambda x: x.get("created_at", ""), reverse=True)
|
|
1722
|
+
return adw_runs
|
|
1723
|
+
|
|
1724
|
+
|
|
1725
|
+
def get_adw_state_with_agents(adw_id: str, db_path: str) -> Optional[dict]:
|
|
1726
|
+
"""Get ADW state with correlated agent activity."""
|
|
1727
|
+
adw_dir = Path(f"agents/{adw_id}")
|
|
1728
|
+
state_file = adw_dir / "adw_state.json"
|
|
1729
|
+
|
|
1730
|
+
if not state_file.exists():
|
|
1731
|
+
return None
|
|
1732
|
+
|
|
1733
|
+
state = json.loads(state_file.read_text())
|
|
1734
|
+
|
|
1735
|
+
# Build phase info with agent correlation
|
|
1736
|
+
phases = []
|
|
1737
|
+
all_phases = ["plan", "build", "validate", "release"]
|
|
1738
|
+
completed = state.get("completed_phases", [])
|
|
1739
|
+
current = state.get("current_phase")
|
|
1740
|
+
|
|
1741
|
+
for phase_name in all_phases:
|
|
1742
|
+
phase_dir = adw_dir / phase_name
|
|
1743
|
+
|
|
1744
|
+
# Determine status
|
|
1745
|
+
if phase_name in completed:
|
|
1746
|
+
status = "completed"
|
|
1747
|
+
elif phase_name == current:
|
|
1748
|
+
status = "running"
|
|
1749
|
+
else:
|
|
1750
|
+
status = "pending"
|
|
1751
|
+
|
|
1752
|
+
# Find agents that ran in this phase (from output files)
|
|
1753
|
+
agent_ids = []
|
|
1754
|
+
if phase_dir.exists():
|
|
1755
|
+
for output_file in phase_dir.glob("*_output.jsonl"):
|
|
1756
|
+
agent_name = output_file.stem.replace("_output", "")
|
|
1757
|
+
agent_ids.append(agent_name)
|
|
1758
|
+
|
|
1759
|
+
phases.append({
|
|
1760
|
+
"name": phase_name,
|
|
1761
|
+
"status": status,
|
|
1762
|
+
"agent_ids": agent_ids,
|
|
1763
|
+
"duration_seconds": None # Could be computed from timestamps if needed
|
|
1764
|
+
})
|
|
1765
|
+
|
|
1766
|
+
return {
|
|
1767
|
+
"adw_id": state.get("adw_id", adw_id),
|
|
1768
|
+
"task_description": state.get("task_description", ""),
|
|
1769
|
+
"created_at": state.get("created_at"),
|
|
1770
|
+
"current_phase": current,
|
|
1771
|
+
"completed_phases": completed,
|
|
1772
|
+
"status": state.get("status", "unknown"),
|
|
1773
|
+
"phases": phases,
|
|
1774
|
+
"project_path": state.get("project_path", "")
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
@app.get("/api/adw/list")
|
|
1779
|
+
async def list_adw_runs(limit: int = Query(20, ge=1, le=100)):
|
|
1780
|
+
"""List all ADW runs from agents/ folder."""
|
|
1781
|
+
adw_runs = scan_adw_folder()[:limit]
|
|
1782
|
+
return {"adw_runs": adw_runs, "count": len(adw_runs)}
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
@app.get("/api/adw/{adw_id}")
|
|
1786
|
+
async def get_adw_details(
|
|
1787
|
+
adw_id: str,
|
|
1788
|
+
project: str = Query(..., description="Path to the database file"),
|
|
1789
|
+
):
|
|
1790
|
+
"""Get ADW state with agent correlation."""
|
|
1791
|
+
if not Path(project).exists():
|
|
1792
|
+
raise HTTPException(status_code=404, detail="Database not found")
|
|
1793
|
+
|
|
1794
|
+
state = get_adw_state_with_agents(adw_id, project)
|
|
1795
|
+
if not state:
|
|
1796
|
+
raise HTTPException(status_code=404, detail="ADW not found")
|
|
1797
|
+
|
|
1798
|
+
return state
|
|
1799
|
+
|
|
1800
|
+
|
|
1628
1801
|
# --- Health Check ---
|
|
1629
1802
|
|
|
1630
1803
|
|
|
@@ -402,3 +402,71 @@ class ComposeResponse(BaseModel):
|
|
|
402
402
|
created_at: str
|
|
403
403
|
custom_instructions: Optional[str] = None
|
|
404
404
|
explanation: Optional[str] = None
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
# --- Agent & ADW Models ---
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class Agent(BaseModel):
|
|
411
|
+
"""Agent from the agents table."""
|
|
412
|
+
|
|
413
|
+
id: str
|
|
414
|
+
name: Optional[str] = None
|
|
415
|
+
type: str # 'main', 'subagent', 'tool'
|
|
416
|
+
first_seen: datetime
|
|
417
|
+
last_seen: datetime
|
|
418
|
+
total_activities: int
|
|
419
|
+
recent_activity_count: int = 0 # Activities in last hour
|
|
420
|
+
is_active: bool = False # Has activity in last 5 minutes
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class AgentToolStats(BaseModel):
|
|
424
|
+
"""Tool usage breakdown for an agent."""
|
|
425
|
+
|
|
426
|
+
tool_name: str
|
|
427
|
+
count: int
|
|
428
|
+
avg_duration_ms: float
|
|
429
|
+
success_rate: float
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class AgentStats(BaseModel):
|
|
433
|
+
"""Detailed stats for a single agent."""
|
|
434
|
+
|
|
435
|
+
agent: Agent
|
|
436
|
+
tool_breakdown: list[AgentToolStats]
|
|
437
|
+
files_touched: list[str]
|
|
438
|
+
parent_agent_id: Optional[str] = None # If subagent, who spawned it
|
|
439
|
+
adw_phase: Optional[str] = None # Which ADW phase this agent ran in
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class ADWPhaseInfo(BaseModel):
|
|
443
|
+
"""Info about a single ADW phase."""
|
|
444
|
+
|
|
445
|
+
name: str # 'plan', 'build', 'validate', 'release'
|
|
446
|
+
status: str # 'pending', 'running', 'completed', 'failed', 'skipped'
|
|
447
|
+
duration_seconds: Optional[float] = None
|
|
448
|
+
agent_ids: list[str] = [] # Agents that ran in this phase
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class ADWState(BaseModel):
|
|
452
|
+
"""Full ADW state with agent correlation."""
|
|
453
|
+
|
|
454
|
+
adw_id: str
|
|
455
|
+
task_description: str
|
|
456
|
+
created_at: datetime
|
|
457
|
+
current_phase: str
|
|
458
|
+
completed_phases: list[str]
|
|
459
|
+
status: str # 'running', 'completed', 'failed'
|
|
460
|
+
phases: list[ADWPhaseInfo]
|
|
461
|
+
project_path: str
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class ADWListItem(BaseModel):
|
|
465
|
+
"""Summary for ADW list."""
|
|
466
|
+
|
|
467
|
+
adw_id: str
|
|
468
|
+
created_at: datetime
|
|
469
|
+
status: str
|
|
470
|
+
current_phase: str
|
|
471
|
+
phases_completed: int
|
|
472
|
+
phases_total: int
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "omni-cortex"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.16.0"
|
|
8
8
|
description = "Give Claude Code a perfect memory - auto-logs everything, searches smartly, and gets smarter over time"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|