feat: Add Alateya, Clan, Eonarch agents + fix gateway-router connection
## Agents Added - Alateya: R&D, biotech, innovations - Clan (Spirit): Community spirit agent - Eonarch: Consciousness evolution agent ## Changes - docker-compose.node1.yml: Added tokens for all 3 new agents - gateway-bot/http_api.py: Added configs and webhook endpoints - gateway-bot/clan_prompt.txt: New prompt file - gateway-bot/eonarch_prompt.txt: New prompt file ## Fixes - Fixed ROUTER_URL from :9102 to :8000 (internal container port) - All 9 Telegram agents now working ## Documentation - Created PROJECT-MASTER-INDEX.md - single entry point - Added various status documents and scripts Tokens configured: - Helion, NUTRA, Agromatrix (existing) - Alateya, Clan, Eonarch (new) - Druid, GreenFood, DAARWIZZ (configured)
This commit is contained in:
177
services/memory-service/identity_endpoints.py
Normal file
177
services/memory-service/identity_endpoints.py
Normal file
@@ -0,0 +1,177 @@
|
||||
# Identity Endpoints for Account Linking (Telegram ↔ Energy Union)
|
||||
# This file is appended to main.py
|
||||
|
||||
import secrets
|
||||
from datetime import datetime, timedelta
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# IDENTITY & ACCOUNT LINKING
|
||||
# ============================================================================
|
||||
|
||||
class LinkStartRequest(BaseModel):
|
||||
account_id: str # UUID as string
|
||||
ttl_minutes: int = 10
|
||||
|
||||
|
||||
class LinkStartResponse(BaseModel):
|
||||
link_code: str
|
||||
expires_at: datetime
|
||||
|
||||
|
||||
class ResolveResponse(BaseModel):
|
||||
account_id: Optional[str] = None
|
||||
linked: bool = False
|
||||
linked_at: Optional[datetime] = None
|
||||
|
||||
|
||||
@app.post("/identity/link/start", response_model=LinkStartResponse)
|
||||
async def start_link(request: LinkStartRequest):
|
||||
"""
|
||||
Generate a one-time link code for account linking.
|
||||
This is called from Energy Union dashboard when user clicks "Link Telegram".
|
||||
"""
|
||||
try:
|
||||
# Generate secure random code
|
||||
link_code = secrets.token_urlsafe(16)[:20].upper()
|
||||
expires_at = datetime.utcnow() + timedelta(minutes=request.ttl_minutes)
|
||||
|
||||
# Store in database
|
||||
await db.pool.execute(
|
||||
"""
|
||||
INSERT INTO link_codes (code, account_id, expires_at, generated_via)
|
||||
VALUES ($1, $2::uuid, $3, 'api')
|
||||
""",
|
||||
link_code,
|
||||
request.account_id,
|
||||
expires_at
|
||||
)
|
||||
|
||||
logger.info("link_code_generated", account_id=request.account_id, code=link_code[:4] + "***")
|
||||
|
||||
return LinkStartResponse(
|
||||
link_code=link_code,
|
||||
expires_at=expires_at
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("link_code_generation_failed", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/identity/resolve", response_model=ResolveResponse)
|
||||
async def resolve_telegram(telegram_user_id: int):
|
||||
"""
|
||||
Resolve Telegram user ID to Energy Union account ID.
|
||||
Returns null account_id if not linked.
|
||||
"""
|
||||
try:
|
||||
row = await db.pool.fetchrow(
|
||||
"""
|
||||
SELECT account_id, linked_at
|
||||
FROM account_links
|
||||
WHERE telegram_user_id = $1 AND status = 'active'
|
||||
""",
|
||||
telegram_user_id
|
||||
)
|
||||
|
||||
if row:
|
||||
return ResolveResponse(
|
||||
account_id=str(row['account_id']),
|
||||
linked=True,
|
||||
linked_at=row['linked_at']
|
||||
)
|
||||
else:
|
||||
return ResolveResponse(linked=False)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("telegram_resolve_failed", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/identity/user/{account_id}/timeline")
|
||||
async def get_user_timeline(
|
||||
account_id: str,
|
||||
limit: int = Query(default=20, le=100),
|
||||
channel: Optional[str] = None
|
||||
):
|
||||
"""
|
||||
Get user's interaction timeline across all channels.
|
||||
Only available for linked accounts.
|
||||
"""
|
||||
try:
|
||||
if channel:
|
||||
rows = await db.pool.fetch(
|
||||
"""
|
||||
SELECT id, channel, channel_id, event_type, summary,
|
||||
metadata, importance_score, event_at
|
||||
FROM user_timeline
|
||||
WHERE account_id = $1::uuid AND channel = $2
|
||||
ORDER BY event_at DESC
|
||||
LIMIT $3
|
||||
""",
|
||||
account_id, channel, limit
|
||||
)
|
||||
else:
|
||||
rows = await db.pool.fetch(
|
||||
"""
|
||||
SELECT id, channel, channel_id, event_type, summary,
|
||||
metadata, importance_score, event_at
|
||||
FROM user_timeline
|
||||
WHERE account_id = $1::uuid
|
||||
ORDER BY event_at DESC
|
||||
LIMIT $3
|
||||
""",
|
||||
account_id, limit
|
||||
)
|
||||
|
||||
events = []
|
||||
for row in rows:
|
||||
events.append({
|
||||
"id": str(row['id']),
|
||||
"channel": row['channel'],
|
||||
"channel_id": row['channel_id'],
|
||||
"event_type": row['event_type'],
|
||||
"summary": row['summary'],
|
||||
"metadata": row['metadata'] or {},
|
||||
"importance_score": row['importance_score'],
|
||||
"event_at": row['event_at'].isoformat()
|
||||
})
|
||||
|
||||
return {"events": events, "account_id": account_id, "count": len(events)}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("timeline_fetch_failed", error=str(e), account_id=account_id)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/identity/timeline/add")
|
||||
async def add_timeline_event(
|
||||
account_id: str,
|
||||
channel: str,
|
||||
channel_id: str,
|
||||
event_type: str,
|
||||
summary: str,
|
||||
metadata: Optional[dict] = None,
|
||||
importance_score: float = 0.5
|
||||
):
|
||||
"""
|
||||
Add an event to user's timeline.
|
||||
Called by Gateway when processing messages from linked accounts.
|
||||
"""
|
||||
try:
|
||||
event_id = await db.pool.fetchval(
|
||||
"""
|
||||
SELECT add_timeline_event($1::uuid, $2, $3, $4, $5, $6::jsonb, $7)
|
||||
""",
|
||||
account_id, channel, channel_id, event_type,
|
||||
summary, metadata or {}, importance_score
|
||||
)
|
||||
|
||||
return {"event_id": str(event_id), "success": True}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("timeline_add_failed", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Reference in New Issue
Block a user