omni-cortex 1.13.0__py3-none-any.whl → 1.15.0__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.
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/chat_service.py +61 -2
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/database.py +120 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/main.py +177 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/models.py +72 -0
- {omni_cortex-1.13.0.dist-info → omni_cortex-1.15.0.dist-info}/METADATA +1 -1
- omni_cortex-1.15.0.dist-info/RECORD +26 -0
- omni_cortex-1.13.0.dist-info/RECORD +0 -26
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/.env.example +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/backfill_summaries.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/image_service.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/logging_config.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/project_config.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/project_scanner.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/prompt_security.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/pyproject.toml +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/security.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/uv.lock +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/websocket_manager.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/post_tool_use.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/pre_tool_use.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/session_utils.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/stop.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/subagent_stop.py +0 -0
- {omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/user_prompt.py +0 -0
- {omni_cortex-1.13.0.dist-info → omni_cortex-1.15.0.dist-info}/WHEEL +0 -0
- {omni_cortex-1.13.0.dist-info → omni_cortex-1.15.0.dist-info}/entry_points.txt +0 -0
- {omni_cortex-1.13.0.dist-info → omni_cortex-1.15.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -42,9 +42,13 @@ def is_available() -> bool:
|
|
|
42
42
|
return False
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def build_style_context_prompt(style_profile: dict) -> str:
|
|
45
|
+
def build_style_context_prompt(style_profile: dict | None) -> str:
|
|
46
46
|
"""Build a prompt section describing user's communication style."""
|
|
47
47
|
|
|
48
|
+
# Return empty string if no style profile provided
|
|
49
|
+
if not style_profile:
|
|
50
|
+
return ""
|
|
51
|
+
|
|
48
52
|
# Handle both camelCase (new format) and snake_case (old format)
|
|
49
53
|
tone_dist = style_profile.get("toneDistribution") or style_profile.get("tone_distribution", {})
|
|
50
54
|
tone_list = ", ".join(tone_dist.keys()) if tone_dist else "neutral"
|
|
@@ -416,6 +420,8 @@ def build_compose_prompt(
|
|
|
416
420
|
template: Optional[str],
|
|
417
421
|
tone_level: int,
|
|
418
422
|
memory_context: str,
|
|
423
|
+
custom_instructions: Optional[str] = None,
|
|
424
|
+
include_explanation: bool = False,
|
|
419
425
|
) -> str:
|
|
420
426
|
"""Build the prompt for composing a response in user's style.
|
|
421
427
|
|
|
@@ -426,6 +432,8 @@ def build_compose_prompt(
|
|
|
426
432
|
template: Optional response template (answer, guide, redirect, acknowledge)
|
|
427
433
|
tone_level: Tone formality level (0-100)
|
|
428
434
|
memory_context: Relevant memories formatted as context
|
|
435
|
+
custom_instructions: Optional specific instructions from the user
|
|
436
|
+
include_explanation: Whether to explain the incoming message first
|
|
429
437
|
|
|
430
438
|
Returns:
|
|
431
439
|
Complete prompt for response generation
|
|
@@ -480,7 +488,37 @@ Use this information naturally in your response if relevant. Don't explicitly ci
|
|
|
480
488
|
|
|
481
489
|
"""
|
|
482
490
|
|
|
483
|
-
|
|
491
|
+
# Add custom instructions if provided
|
|
492
|
+
if custom_instructions:
|
|
493
|
+
prompt += f"""
|
|
494
|
+
## CUSTOM INSTRUCTIONS FROM USER
|
|
495
|
+
|
|
496
|
+
The user has provided these specific instructions for the response:
|
|
497
|
+
|
|
498
|
+
<custom_instructions>
|
|
499
|
+
{xml_escape(custom_instructions)}
|
|
500
|
+
</custom_instructions>
|
|
501
|
+
|
|
502
|
+
Please incorporate these requirements while maintaining the user's voice.
|
|
503
|
+
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
# Build task instructions based on explanation mode
|
|
507
|
+
if include_explanation:
|
|
508
|
+
prompt += """
|
|
509
|
+
**Your Task:**
|
|
510
|
+
1. FIRST, provide a clear explanation of what the incoming message means or is asking
|
|
511
|
+
Format: "**Understanding:** [your explanation in user's voice]"
|
|
512
|
+
2. THEN, write a response to the incoming message in YOUR voice
|
|
513
|
+
Format: "**Response:** [your response]"
|
|
514
|
+
3. Use the knowledge from your memories naturally if relevant
|
|
515
|
+
4. Match the tone level specified above
|
|
516
|
+
5. Follow the platform context guidelines
|
|
517
|
+
6. Sound exactly like something you would write yourself
|
|
518
|
+
|
|
519
|
+
Write the explanation and response now:"""
|
|
520
|
+
else:
|
|
521
|
+
prompt += """
|
|
484
522
|
**Your Task:**
|
|
485
523
|
1. Write a response to the incoming message in YOUR voice (the user's voice)
|
|
486
524
|
2. Use the knowledge from your memories naturally if relevant
|
|
@@ -501,6 +539,8 @@ async def compose_response(
|
|
|
501
539
|
tone_level: int = 50,
|
|
502
540
|
include_memories: bool = True,
|
|
503
541
|
style_profile: Optional[dict] = None,
|
|
542
|
+
custom_instructions: Optional[str] = None,
|
|
543
|
+
include_explanation: bool = False,
|
|
504
544
|
) -> dict:
|
|
505
545
|
"""Compose a response to an incoming message in the user's style.
|
|
506
546
|
|
|
@@ -512,6 +552,8 @@ async def compose_response(
|
|
|
512
552
|
tone_level: Tone formality level (0-100)
|
|
513
553
|
include_memories: Whether to include relevant memories
|
|
514
554
|
style_profile: User's style profile dictionary
|
|
555
|
+
custom_instructions: Optional specific instructions from the user
|
|
556
|
+
include_explanation: Whether to explain the incoming message first
|
|
515
557
|
|
|
516
558
|
Returns:
|
|
517
559
|
Dict with response, sources, and metadata
|
|
@@ -550,6 +592,8 @@ async def compose_response(
|
|
|
550
592
|
template=template,
|
|
551
593
|
tone_level=tone_level,
|
|
552
594
|
memory_context=memory_context,
|
|
595
|
+
custom_instructions=custom_instructions,
|
|
596
|
+
include_explanation=include_explanation,
|
|
553
597
|
)
|
|
554
598
|
|
|
555
599
|
try:
|
|
@@ -563,10 +607,25 @@ async def compose_response(
|
|
|
563
607
|
"response": f"Failed to generate response: {str(e)}",
|
|
564
608
|
"sources": sources,
|
|
565
609
|
"error": "generation_failed",
|
|
610
|
+
"explanation": None,
|
|
566
611
|
}
|
|
567
612
|
|
|
613
|
+
# Parse explanation if requested
|
|
614
|
+
explanation = None
|
|
615
|
+
if include_explanation:
|
|
616
|
+
# Try to extract explanation and response parts
|
|
617
|
+
import re
|
|
618
|
+
understanding_match = re.search(r'\*\*Understanding:\*\*\s*(.+?)(?=\*\*Response:\*\*)', composed_response, re.DOTALL)
|
|
619
|
+
response_match = re.search(r'\*\*Response:\*\*\s*(.+)', composed_response, re.DOTALL)
|
|
620
|
+
|
|
621
|
+
if understanding_match and response_match:
|
|
622
|
+
explanation = understanding_match.group(1).strip()
|
|
623
|
+
composed_response = response_match.group(1).strip()
|
|
624
|
+
# If parsing fails, leave explanation as None and return full response
|
|
625
|
+
|
|
568
626
|
return {
|
|
569
627
|
"response": composed_response,
|
|
570
628
|
"sources": sources,
|
|
571
629
|
"error": None,
|
|
630
|
+
"explanation": explanation,
|
|
572
631
|
}
|
|
@@ -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
|
{omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/main.py
RENAMED
|
@@ -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,
|
|
@@ -1146,6 +1151,8 @@ async def compose_response_endpoint(
|
|
|
1146
1151
|
tone_level=request.tone_level,
|
|
1147
1152
|
include_memories=request.include_memories,
|
|
1148
1153
|
style_profile=style_profile,
|
|
1154
|
+
custom_instructions=request.custom_instructions,
|
|
1155
|
+
include_explanation=request.include_explanation,
|
|
1149
1156
|
)
|
|
1150
1157
|
|
|
1151
1158
|
if result.get("error"):
|
|
@@ -1165,6 +1172,8 @@ async def compose_response_endpoint(
|
|
|
1165
1172
|
incoming_message=request.incoming_message,
|
|
1166
1173
|
context_type=request.context_type,
|
|
1167
1174
|
created_at=datetime.now().isoformat(),
|
|
1175
|
+
custom_instructions=request.custom_instructions,
|
|
1176
|
+
explanation=result.get("explanation"),
|
|
1168
1177
|
)
|
|
1169
1178
|
|
|
1170
1179
|
log_success("/api/compose-response", context=request.context_type, tone=request.tone_level)
|
|
@@ -1621,6 +1630,174 @@ async def export_memories(
|
|
|
1621
1630
|
raise HTTPException(status_code=400, detail=f"Unsupported format: {format}. Use json, markdown, or csv.")
|
|
1622
1631
|
|
|
1623
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
|
+
|
|
1624
1801
|
# --- Health Check ---
|
|
1625
1802
|
|
|
1626
1803
|
|
|
@@ -384,6 +384,8 @@ class ComposeRequest(BaseModel):
|
|
|
384
384
|
template: Optional[str] = None # answer, guide, redirect, acknowledge
|
|
385
385
|
tone_level: int = Field(default=50, ge=0, le=100) # 0=casual, 100=professional
|
|
386
386
|
include_memories: bool = Field(default=True)
|
|
387
|
+
custom_instructions: Optional[str] = Field(default=None, max_length=2000)
|
|
388
|
+
include_explanation: bool = Field(default=False)
|
|
387
389
|
|
|
388
390
|
|
|
389
391
|
class ComposeResponse(BaseModel):
|
|
@@ -398,3 +400,73 @@ class ComposeResponse(BaseModel):
|
|
|
398
400
|
incoming_message: str
|
|
399
401
|
context_type: str
|
|
400
402
|
created_at: str
|
|
403
|
+
custom_instructions: Optional[str] = None
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omni-cortex
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.15.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
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/hooks/post_tool_use.py,sha256=zdaKChi8zOghRlHswisCBSQE3kW1MtmM6AFfI_ivvpI,16581
|
|
2
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/hooks/pre_tool_use.py,sha256=3_V6Qw5m40eGrMmm5i94vINzeVxmcJvivdPa69H3AOI,8585
|
|
3
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/hooks/session_utils.py,sha256=3SKPCytqWuRPOupWdzmwBoKBDJqtLcT1Nle_pueDQUY,5746
|
|
4
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/hooks/stop.py,sha256=UroliJsyIS9_lj29-1d_r-80V4AfTMUFCaOjJZv3lwM,6976
|
|
5
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/hooks/subagent_stop.py,sha256=V9HQSFGNOfkg8ZCstPEy4h5V8BP4AbrVr8teFzN1kNk,3314
|
|
6
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/hooks/user_prompt.py,sha256=WNHJvhnkb9rXQ_HDpr6eLpM5vwy1Y1xl1EUoqyNC-x8,6859
|
|
7
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/.env.example,sha256=9xS7-UiWlMddRwzlyyyKNHAMlNTsgH-2sPV266guJpQ,372
|
|
8
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/backfill_summaries.py,sha256=ElchfcBv4pmVr2PsePCgFlCyuvf4_jDJj_C3AmMhu7U,8973
|
|
9
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/chat_service.py,sha256=iEDpAw8OVaHM2312VPcAM-w6dYabUpjaTvWl1jGhqi0,20948
|
|
10
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/database.py,sha256=byISCVgVziqFhJ8j8FC99ueTAoo-E0BJ6bk8KUvW3Mg,57610
|
|
11
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/image_service.py,sha256=NP6ojFpHb6iNTYRkXqYu1CL6WvooZpZ54mjLiWSWG_g,19205
|
|
12
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/logging_config.py,sha256=WnunFGET9zlsn9WBpVsio2zI7BiUQanE0xzAQQxIhII,3944
|
|
13
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/main.py,sha256=hTsX_wTXBxwsYwkWEttGXzszWYCjOufqgixEUec_oz4,65563
|
|
14
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/models.py,sha256=mP_szTXuVE4ZWHGubLeCt1-ltroO8OO0vrGC3IM6AJ0,13122
|
|
15
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/project_config.py,sha256=ZxGoeRpHvN5qQyf2hRxrAZiHrPSwdQp59f0di6O1LKM,4352
|
|
16
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/project_scanner.py,sha256=lwFXS8iJbOoxf7FAyo2TjH25neaMHiJ8B3jS57XxtDI,5713
|
|
17
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/prompt_security.py,sha256=LcdZhYy1CfpSq_4BPO6lMJ15phc2ZXLUSBAnAvODVCI,3423
|
|
18
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/pyproject.toml,sha256=9pbbGQXLe1Xd06nZAtDySCHIlfMWvPaB-C6tGZR6umc,502
|
|
19
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/security.py,sha256=nQsoPE0n5dtY9ive00d33W1gL48GgK7C5Ae0BK2oW2k,3479
|
|
20
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/uv.lock,sha256=miB9zGGSirBkjDE-OZTPCnv43Yc98xuAz_Ne8vTNFHg,186004
|
|
21
|
+
omni_cortex-1.15.0.data/data/share/omni-cortex/dashboard/backend/websocket_manager.py,sha256=gNQLd94AcC-InumGQmUolREhiogCzilYWpLN8SRZjHI,3645
|
|
22
|
+
omni_cortex-1.15.0.dist-info/METADATA,sha256=rqIep2oJO7t08h5JDpnUE4WTtU9Qgnt0rGRxxN5S5Yg,15712
|
|
23
|
+
omni_cortex-1.15.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
24
|
+
omni_cortex-1.15.0.dist-info/entry_points.txt,sha256=rohx4mFH2ffZmMb9QXPZmFf-ZGjA3jpKVDVeET-ttiM,150
|
|
25
|
+
omni_cortex-1.15.0.dist-info/licenses/LICENSE,sha256=oG_397owMmi-Umxp5sYocJ6RPohp9_bDNnnEu9OUphg,1072
|
|
26
|
+
omni_cortex-1.15.0.dist-info/RECORD,,
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/hooks/post_tool_use.py,sha256=zdaKChi8zOghRlHswisCBSQE3kW1MtmM6AFfI_ivvpI,16581
|
|
2
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/hooks/pre_tool_use.py,sha256=3_V6Qw5m40eGrMmm5i94vINzeVxmcJvivdPa69H3AOI,8585
|
|
3
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/hooks/session_utils.py,sha256=3SKPCytqWuRPOupWdzmwBoKBDJqtLcT1Nle_pueDQUY,5746
|
|
4
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/hooks/stop.py,sha256=UroliJsyIS9_lj29-1d_r-80V4AfTMUFCaOjJZv3lwM,6976
|
|
5
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/hooks/subagent_stop.py,sha256=V9HQSFGNOfkg8ZCstPEy4h5V8BP4AbrVr8teFzN1kNk,3314
|
|
6
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/hooks/user_prompt.py,sha256=WNHJvhnkb9rXQ_HDpr6eLpM5vwy1Y1xl1EUoqyNC-x8,6859
|
|
7
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/.env.example,sha256=9xS7-UiWlMddRwzlyyyKNHAMlNTsgH-2sPV266guJpQ,372
|
|
8
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/backfill_summaries.py,sha256=ElchfcBv4pmVr2PsePCgFlCyuvf4_jDJj_C3AmMhu7U,8973
|
|
9
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/chat_service.py,sha256=QGNxVX-9bJw4kot6mPieGD2QIbmzvPYSGDGOpv3p_-Y,18567
|
|
10
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/database.py,sha256=_sWqLjx_mWOxqNpfbv-bChtPfQkHzUNzly1pGu_zPKI,54199
|
|
11
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/image_service.py,sha256=NP6ojFpHb6iNTYRkXqYu1CL6WvooZpZ54mjLiWSWG_g,19205
|
|
12
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/logging_config.py,sha256=WnunFGET9zlsn9WBpVsio2zI7BiUQanE0xzAQQxIhII,3944
|
|
13
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/main.py,sha256=-GwRioHjuUaMiP1gNuyqzs6LUxvIgOUHyirCcfQ6pRs,59364
|
|
14
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/models.py,sha256=_gQoBaavttuRgLIvhCQsZ0zmuON6aKWbAFhdB1YFVbM,11164
|
|
15
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/project_config.py,sha256=ZxGoeRpHvN5qQyf2hRxrAZiHrPSwdQp59f0di6O1LKM,4352
|
|
16
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/project_scanner.py,sha256=lwFXS8iJbOoxf7FAyo2TjH25neaMHiJ8B3jS57XxtDI,5713
|
|
17
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/prompt_security.py,sha256=LcdZhYy1CfpSq_4BPO6lMJ15phc2ZXLUSBAnAvODVCI,3423
|
|
18
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/pyproject.toml,sha256=9pbbGQXLe1Xd06nZAtDySCHIlfMWvPaB-C6tGZR6umc,502
|
|
19
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/security.py,sha256=nQsoPE0n5dtY9ive00d33W1gL48GgK7C5Ae0BK2oW2k,3479
|
|
20
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/uv.lock,sha256=miB9zGGSirBkjDE-OZTPCnv43Yc98xuAz_Ne8vTNFHg,186004
|
|
21
|
-
omni_cortex-1.13.0.data/data/share/omni-cortex/dashboard/backend/websocket_manager.py,sha256=gNQLd94AcC-InumGQmUolREhiogCzilYWpLN8SRZjHI,3645
|
|
22
|
-
omni_cortex-1.13.0.dist-info/METADATA,sha256=N9ZCvUc2F0jnkuXvtXl1ISgDipNbwvtsMHritpaSDVo,15712
|
|
23
|
-
omni_cortex-1.13.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
24
|
-
omni_cortex-1.13.0.dist-info/entry_points.txt,sha256=rohx4mFH2ffZmMb9QXPZmFf-ZGjA3jpKVDVeET-ttiM,150
|
|
25
|
-
omni_cortex-1.13.0.dist-info/licenses/LICENSE,sha256=oG_397owMmi-Umxp5sYocJ6RPohp9_bDNnnEu9OUphg,1072
|
|
26
|
-
omni_cortex-1.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/dashboard/backend/uv.lock
RENAMED
|
File without changes
|
|
File without changes
|
{omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/post_tool_use.py
RENAMED
|
File without changes
|
{omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/pre_tool_use.py
RENAMED
|
File without changes
|
{omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/session_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/subagent_stop.py
RENAMED
|
File without changes
|
{omni_cortex-1.13.0.data → omni_cortex-1.15.0.data}/data/share/omni-cortex/hooks/user_prompt.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|