Files
microdao-daarion/services/memory-service/identity_endpoints.py
Apple 0c8bef82f4 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)
2026-01-28 06:40:34 -08:00

178 lines
5.5 KiB
Python

# 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))