feat: DAGI Router v2 - new endpoints, hooks, and UI card
This commit is contained in:
@@ -359,3 +359,4 @@ cat docs/tasks/PHASE3_MASTER_TASK.md | pbcopy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -126,3 +126,4 @@ if (errorMessage.includes('Provider error') ||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -612,3 +612,4 @@ await knowledgeBaseService.uploadFile("helion", file);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -185,3 +185,4 @@ Request body: {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -126,3 +126,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -422,3 +422,4 @@ http://localhost:8899/microdao/daarion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -515,3 +515,4 @@ const systemPrompt = DEFAULT_PROMPTS[agentId][language];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -643,3 +643,4 @@ GET /api/telegram/{agent_id}/status
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -162,3 +162,4 @@ INFO: Selected provider: LLMProvider(id='llm_local_qwen3_8b')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -461,3 +461,4 @@ Remaining Work:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -131,3 +131,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -171,3 +171,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -308,3 +308,4 @@ export function EnergyUnionCabinetPage() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -149,3 +149,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -382,3 +382,4 @@ Helion потребує перереєстрації webhook, інші боти
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -500,3 +500,4 @@ export function MobileResponsiveChatPage() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -173,3 +173,4 @@ LLM сервіси повністю налаштовані та працюють
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -366,3 +366,4 @@ http://localhost:8899/microdao/energy-union
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -106,3 +106,4 @@ getMicroDaoWorkspace(microDaoId: string): Promise<Workspace | null>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -412,3 +412,4 @@ curl http://localhost:9500/api/agent/monitor/file-urls?agent_id=monitor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -163,3 +163,4 @@ curl -X POST http://localhost:9500/api/agent/monitor/chat \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -317,3 +317,4 @@ curl 'http://localhost:9500/api/project/changes?since=1700000000000&limit=10'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -247,3 +247,4 @@ location.reload();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -177,3 +177,4 @@ window.dispatchEvent(new CustomEvent('project-change', {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -368,3 +368,4 @@ localStorage.setItem(storageKey, JSON.stringify(changes.slice(0, 50)));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -184,3 +184,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -537,3 +537,4 @@ curl -X POST http://localhost:8896/api/ocr/upload \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -222,3 +222,4 @@ docker exec ollama ollama ps
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -73,3 +73,4 @@ echo " 4. Протестувати Ollama з GPU: ollama run qwen3:8b 'test'"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -111,3 +111,4 @@ time ollama run qwen3:8b "Привіт, тест GPU"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -108,3 +108,4 @@ time ollama run qwen3:8b "test"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -81,3 +81,4 @@ echo " - Загальне CPU: 85.3% → 40-50%"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -628,3 +628,4 @@ const saveConversation = async () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -446,3 +446,4 @@ You now have a fully functional agent integration system. Agents can automatical
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -381,3 +381,4 @@ Test 5: Internal Endpoints
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -379,3 +379,4 @@ All specifications are complete. Pick a starting point:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -472,3 +472,4 @@ docker-compose -f docker-compose.phase3.yml down -v
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -402,3 +402,4 @@ Sofia: "В проєкті X є 3 нові задачі:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -431,3 +431,4 @@ Complete Phase 4.5 fully (2-3 години) → Then start Phase 5 with real aut
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -523,3 +523,4 @@ useAuthStore.getState().clearSession();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -323,3 +323,4 @@ PHASE4_PROGRESS_REPORT.md ✅ (this file)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -572,3 +572,4 @@ Code Quality:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -194,3 +194,4 @@ curl http://localhost:7011/auth/me \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -197,3 +197,4 @@ curl -X POST http://localhost:7011/auth/login \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -154,3 +154,4 @@ If agent replies, **Phase 2 works!** 🚀
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -248,3 +248,4 @@ After Phase 3 works:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -322,3 +322,4 @@ curl http://144.76.224.179:9102/health
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -411,3 +411,4 @@ cat docs/tasks/PHASE2_MASTER_TASK.md | pbcopy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -339,3 +339,4 @@ cd services/agent-filter
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -193,3 +193,4 @@ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:7014';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -185,3 +185,4 @@ docker-compose restart swapper-service
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -201,3 +201,4 @@ swapper:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -260,3 +260,4 @@ swapper:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -197,3 +197,4 @@ ssh root@144.76.224.179 "cd /opt/microdao-daarion && docker-compose up -d swappe
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -314,3 +314,4 @@ cryptography==41.0.7
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -368,3 +368,4 @@ done
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -602,3 +602,4 @@ async def universal_telegram_webhook(bot_id: str, update: TelegramUpdate):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -129,3 +129,4 @@ docker logs --tail 50 dagi-web-search-service
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -168,3 +168,4 @@ INFO: 145.224.94.89:27620 - "POST /route HTTP/1.1" 200 OK
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -322,3 +322,4 @@ docker ps | grep -E 'dagi-gateway|dagi-tts|dagi-stt'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -301,3 +301,4 @@ async def text_to_speech(text: str, voice_id: str):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const CITY_SERVICE_URL =
|
||||
process.env.INTERNAL_API_URL ||
|
||||
process.env.CITY_SERVICE_URL ||
|
||||
'http://daarion-city-service:7001';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
// Fallback response when data is unavailable
|
||||
const fallbackResponse = (nodeId: string) => ({
|
||||
node_id: nodeId,
|
||||
total: 0,
|
||||
active: 0,
|
||||
phantom: 0,
|
||||
stale: 0,
|
||||
agents: [],
|
||||
});
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ nodeId: string }> }
|
||||
) {
|
||||
const { nodeId } = await params;
|
||||
|
||||
if (!nodeId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'nodeId is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${CITY_SERVICE_URL}/city/internal/node/${encodeURIComponent(nodeId)}/dagi-router/agents`;
|
||||
console.log('[dagi-router/agents] Fetching:', url);
|
||||
|
||||
const upstream = await fetch(url, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[dagi-router/agents] Response status:', upstream.status);
|
||||
|
||||
if (!upstream.ok) {
|
||||
return NextResponse.json(fallbackResponse(nodeId), { status: 200 });
|
||||
}
|
||||
|
||||
const payload = await upstream.json();
|
||||
return NextResponse.json(payload, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error('[dagi-router/agents] Error:', error);
|
||||
return NextResponse.json(fallbackResponse(nodeId), { status: 200 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const CITY_SERVICE_URL =
|
||||
process.env.INTERNAL_API_URL ||
|
||||
process.env.CITY_SERVICE_URL ||
|
||||
'http://daarion-city-service:7001';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ nodeId: string }> }
|
||||
) {
|
||||
const { nodeId } = await params;
|
||||
|
||||
if (!nodeId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'nodeId is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${CITY_SERVICE_URL}/city/internal/node/${encodeURIComponent(nodeId)}/dagi-router/health`;
|
||||
console.log('[dagi-router/health] Fetching:', url);
|
||||
|
||||
const upstream = await fetch(url, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[dagi-router/health] Response status:', upstream.status);
|
||||
|
||||
if (!upstream.ok) {
|
||||
// Return fallback instead of error
|
||||
return NextResponse.json({
|
||||
node_id: nodeId,
|
||||
status: 'down',
|
||||
version: null,
|
||||
agent_count: 0,
|
||||
latency_ms: null,
|
||||
}, { status: 200 });
|
||||
}
|
||||
|
||||
const payload = await upstream.json();
|
||||
return NextResponse.json(payload, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error('[dagi-router/health] Error:', error);
|
||||
return NextResponse.json({
|
||||
node_id: nodeId,
|
||||
status: 'down',
|
||||
version: null,
|
||||
agent_count: 0,
|
||||
latency_ms: null,
|
||||
}, { status: 200 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const CITY_SERVICE_URL =
|
||||
process.env.INTERNAL_API_URL ||
|
||||
process.env.CITY_SERVICE_URL ||
|
||||
'http://daarion-city-service:7001';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
// Fallback response when data is unavailable
|
||||
const fallbackResponse = (nodeId: string) => ({
|
||||
node_id: nodeId,
|
||||
status: 'down',
|
||||
version: null,
|
||||
latency_ms: null,
|
||||
router_agent_count: 0,
|
||||
db_agent_count: 0,
|
||||
active: 0,
|
||||
phantom: 0,
|
||||
stale: 0,
|
||||
last_audit_at: null,
|
||||
});
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ nodeId: string }> }
|
||||
) {
|
||||
const { nodeId } = await params;
|
||||
|
||||
if (!nodeId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'nodeId is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${CITY_SERVICE_URL}/city/internal/node/${encodeURIComponent(nodeId)}/dagi-router/summary`;
|
||||
console.log('[dagi-router/summary] Fetching:', url);
|
||||
|
||||
const upstream = await fetch(url, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[dagi-router/summary] Response status:', upstream.status);
|
||||
|
||||
if (!upstream.ok) {
|
||||
return NextResponse.json(fallbackResponse(nodeId), { status: 200 });
|
||||
}
|
||||
|
||||
const payload = await upstream.json();
|
||||
return NextResponse.json(payload, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error('[dagi-router/summary] Error:', error);
|
||||
return NextResponse.json(fallbackResponse(nodeId), { status: 200 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,13 @@ import {
|
||||
Search,
|
||||
Filter,
|
||||
ChevronDown,
|
||||
Download,
|
||||
Upload,
|
||||
Brain
|
||||
Zap
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
useDAGIRouterAgents,
|
||||
runDAGIAudit,
|
||||
syncPhantomAgents,
|
||||
markStaleAgents,
|
||||
type DAGIRouterAgent
|
||||
} from '@/hooks/useDAGIAudit';
|
||||
useDAGIRouterSummary,
|
||||
useDAGIRouterAgents,
|
||||
type DagiRouterAgent
|
||||
} from '@/hooks/useDAGIRouter';
|
||||
|
||||
interface DAGIRouterCardProps {
|
||||
nodeId: string;
|
||||
@@ -65,18 +61,42 @@ const STATUS_CONFIG = {
|
||||
}
|
||||
};
|
||||
|
||||
const ROUTER_STATUS_CONFIG = {
|
||||
up: {
|
||||
label: 'Up',
|
||||
bgClass: 'bg-emerald-500/20',
|
||||
textClass: 'text-emerald-400',
|
||||
dotClass: 'bg-emerald-400'
|
||||
},
|
||||
degraded: {
|
||||
label: 'Degraded',
|
||||
bgClass: 'bg-amber-500/20',
|
||||
textClass: 'text-amber-400',
|
||||
dotClass: 'bg-amber-400'
|
||||
},
|
||||
down: {
|
||||
label: 'Down',
|
||||
bgClass: 'bg-red-500/20',
|
||||
textClass: 'text-red-400',
|
||||
dotClass: 'bg-red-400'
|
||||
}
|
||||
};
|
||||
|
||||
export function DAGIRouterCard({ nodeId, expanded = false }: DAGIRouterCardProps) {
|
||||
const { agents, summary, lastAuditAt, isLoading, error, refresh } = useDAGIRouterAgents(nodeId);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const [runError, setRunError] = useState<string | null>(null);
|
||||
const { data: summary, error: summaryError, mutate: refreshSummary } = useDAGIRouterSummary(nodeId);
|
||||
const { data: agentsData, error: agentsError, mutate: refreshAgents } = useDAGIRouterAgents(nodeId);
|
||||
|
||||
const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [showFilterMenu, setShowFilterMenu] = useState(false);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
const agents = agentsData?.agents || [];
|
||||
const isLoading = !summary && !summaryError;
|
||||
|
||||
// Filter agents
|
||||
const filteredAgents = useMemo(() => {
|
||||
return agents.filter(agent => {
|
||||
return agents.filter((agent: DagiRouterAgent) => {
|
||||
// Status filter
|
||||
if (statusFilter !== 'all' && agent.status !== statusFilter) {
|
||||
return false;
|
||||
@@ -85,61 +105,27 @@ export function DAGIRouterCard({ nodeId, expanded = false }: DAGIRouterCardProps
|
||||
if (searchQuery) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return (
|
||||
agent.name.toLowerCase().includes(query) ||
|
||||
agent.role?.toLowerCase().includes(query) ||
|
||||
agent.id.toLowerCase().includes(query)
|
||||
agent.id.toLowerCase().includes(query) ||
|
||||
agent.name?.toLowerCase().includes(query) ||
|
||||
agent.kind?.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, [agents, statusFilter, searchQuery]);
|
||||
|
||||
const handleRunAudit = async () => {
|
||||
setIsRunning(true);
|
||||
setRunError(null);
|
||||
const handleRefresh = async () => {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
await runDAGIAudit(nodeId);
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setRunError(e instanceof Error ? e.message : 'Failed to run audit');
|
||||
await Promise.all([refreshSummary(), refreshAgents()]);
|
||||
} finally {
|
||||
setIsRunning(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSyncPhantom = async () => {
|
||||
const phantomIds = agents.filter(a => a.status === 'phantom').map(a => a.id);
|
||||
if (phantomIds.length === 0) return;
|
||||
|
||||
setIsSyncing(true);
|
||||
try {
|
||||
await syncPhantomAgents(nodeId, phantomIds);
|
||||
await runDAGIAudit(nodeId);
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setRunError(e instanceof Error ? e.message : 'Failed to sync');
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMarkStale = async () => {
|
||||
const staleIds = agents.filter(a => a.status === 'stale').map(a => a.id);
|
||||
if (staleIds.length === 0) return;
|
||||
|
||||
setIsSyncing(true);
|
||||
try {
|
||||
await markStaleAgents(nodeId, staleIds);
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setRunError(e instanceof Error ? e.message : 'Failed to mark stale');
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Format timestamp
|
||||
const formatTime = (timestamp: string) => {
|
||||
const formatTime = (timestamp: string | null) => {
|
||||
if (!timestamp) return '—';
|
||||
try {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString('uk-UA', {
|
||||
@@ -164,6 +150,9 @@ export function DAGIRouterCard({ nodeId, expanded = false }: DAGIRouterCardProps
|
||||
);
|
||||
};
|
||||
|
||||
const routerStatus = summary?.status || 'down';
|
||||
const routerConfig = ROUTER_STATUS_CONFIG[routerStatus as keyof typeof ROUTER_STATUS_CONFIG] || ROUTER_STATUS_CONFIG.down;
|
||||
|
||||
return (
|
||||
<div className="bg-white/5 backdrop-blur-md rounded-2xl border border-white/10 p-6">
|
||||
{/* Header */}
|
||||
@@ -173,66 +162,64 @@ export function DAGIRouterCard({ nodeId, expanded = false }: DAGIRouterCardProps
|
||||
DAGI Router
|
||||
</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
{summary.phantom > 0 && (
|
||||
<button
|
||||
onClick={handleSyncPhantom}
|
||||
disabled={isSyncing || isRunning}
|
||||
className="flex items-center gap-1.5 px-2.5 py-1.5 bg-amber-500/20 hover:bg-amber-500/30
|
||||
text-amber-400 rounded-lg transition-colors text-xs disabled:opacity-50"
|
||||
title="Синхронізувати phantom агентів"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5" />
|
||||
Sync {summary.phantom}
|
||||
</button>
|
||||
)}
|
||||
{/* Router Status Badge */}
|
||||
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium ${routerConfig.bgClass} ${routerConfig.textClass}`}>
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${routerConfig.dotClass}`} />
|
||||
{routerConfig.label}
|
||||
</span>
|
||||
|
||||
<button
|
||||
onClick={handleRunAudit}
|
||||
disabled={isRunning || isLoading || isSyncing}
|
||||
onClick={handleRefresh}
|
||||
disabled={isRefreshing || isLoading}
|
||||
className="flex items-center gap-2 px-3 py-1.5 bg-purple-500/20 hover:bg-purple-500/30
|
||||
text-purple-400 rounded-lg transition-colors text-sm disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${isRunning ? 'animate-spin' : ''}`} />
|
||||
{isRunning ? 'Аудит...' : 'Запустити'}
|
||||
<RefreshCw className={`w-4 h-4 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
{(error || runError) && (
|
||||
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-400 text-sm">
|
||||
<AlertTriangle className="w-4 h-4 inline mr-2" />
|
||||
{runError || error?.message || 'Помилка завантаження'}
|
||||
{/* Router Info */}
|
||||
{summary && (
|
||||
<div className="flex items-center gap-4 mb-4 text-xs text-white/50">
|
||||
{summary.version && (
|
||||
<span className="flex items-center gap-1">
|
||||
<Zap className="w-3 h-3" />
|
||||
v{summary.version}
|
||||
</span>
|
||||
)}
|
||||
{summary.latency_ms !== null && (
|
||||
<span>{summary.latency_ms}ms</span>
|
||||
)}
|
||||
{summary.last_audit_at && (
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
Audit: {formatTime(summary.last_audit_at)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Loading */}
|
||||
{isLoading && agents.length === 0 && (
|
||||
{isLoading && (
|
||||
<div className="text-center py-8 text-white/40">
|
||||
<RefreshCw className="w-6 h-6 animate-spin mx-auto mb-2" />
|
||||
Завантаження...
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* No data */}
|
||||
{!isLoading && agents.length === 0 && !error && (
|
||||
<div className="text-center py-8 text-white/40">
|
||||
{/* Router Down */}
|
||||
{!isLoading && routerStatus === 'down' && (
|
||||
<div className="text-center py-6 text-white/40">
|
||||
<Router className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
||||
<p>Ще немає даних аудиту</p>
|
||||
<p className="text-sm mt-1">Натисніть "Запустити" для аудиту</p>
|
||||
<p className="text-red-400">Router недоступний</p>
|
||||
<p className="text-sm mt-1">Перевірте стан DAGI Router сервісу</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
{agents.length > 0 && (
|
||||
{/* Content - when router is up or degraded */}
|
||||
{!isLoading && routerStatus !== 'down' && (
|
||||
<>
|
||||
{/* Timestamp */}
|
||||
{lastAuditAt && (
|
||||
<div className="flex items-center gap-2 text-xs text-white/40 mb-4">
|
||||
<Clock className="w-3 h-3" />
|
||||
Останній аудит: {formatTime(lastAuditAt)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-3 gap-3 mb-4">
|
||||
<button
|
||||
@@ -244,7 +231,7 @@ export function DAGIRouterCard({ nodeId, expanded = false }: DAGIRouterCardProps
|
||||
}`}
|
||||
>
|
||||
<div className="text-2xl font-bold text-emerald-400">
|
||||
{summary.active}
|
||||
{summary?.active || 0}
|
||||
</div>
|
||||
<div className="text-xs text-white/50">Active</div>
|
||||
</button>
|
||||
@@ -253,13 +240,13 @@ export function DAGIRouterCard({ nodeId, expanded = false }: DAGIRouterCardProps
|
||||
className={`rounded-lg p-3 text-center transition-all ${
|
||||
statusFilter === 'phantom'
|
||||
? 'bg-amber-500/30 ring-2 ring-amber-500/50'
|
||||
: summary.phantom > 0 ? 'bg-amber-500/10 hover:bg-amber-500/20' : 'bg-slate-800/50'
|
||||
: (summary?.phantom || 0) > 0 ? 'bg-amber-500/10 hover:bg-amber-500/20' : 'bg-slate-800/50'
|
||||
}`}
|
||||
>
|
||||
<div className={`text-2xl font-bold ${
|
||||
summary.phantom > 0 ? 'text-amber-400' : 'text-white/40'
|
||||
(summary?.phantom || 0) > 0 ? 'text-amber-400' : 'text-white/40'
|
||||
}`}>
|
||||
{summary.phantom}
|
||||
{summary?.phantom || 0}
|
||||
</div>
|
||||
<div className="text-xs text-white/50">Phantom</div>
|
||||
</button>
|
||||
@@ -268,13 +255,13 @@ export function DAGIRouterCard({ nodeId, expanded = false }: DAGIRouterCardProps
|
||||
className={`rounded-lg p-3 text-center transition-all ${
|
||||
statusFilter === 'stale'
|
||||
? 'bg-orange-500/30 ring-2 ring-orange-500/50'
|
||||
: summary.stale > 0 ? 'bg-orange-500/10 hover:bg-orange-500/20' : 'bg-slate-800/50'
|
||||
: (summary?.stale || 0) > 0 ? 'bg-orange-500/10 hover:bg-orange-500/20' : 'bg-slate-800/50'
|
||||
}`}
|
||||
>
|
||||
<div className={`text-2xl font-bold ${
|
||||
summary.stale > 0 ? 'text-orange-400' : 'text-white/40'
|
||||
(summary?.stale || 0) > 0 ? 'text-orange-400' : 'text-white/40'
|
||||
}`}>
|
||||
{summary.stale}
|
||||
{summary?.stale || 0}
|
||||
</div>
|
||||
<div className="text-xs text-white/50">Stale</div>
|
||||
</button>
|
||||
@@ -282,178 +269,173 @@ export function DAGIRouterCard({ nodeId, expanded = false }: DAGIRouterCardProps
|
||||
|
||||
{/* Source counts */}
|
||||
<div className="flex justify-between text-xs text-white/40 mb-4 px-1">
|
||||
<span>Router: {summary.router_total} агентів</span>
|
||||
<span>System: {summary.system_total} агентів</span>
|
||||
<span>Router: {summary?.router_agent_count || 0} агентів</span>
|
||||
<span>DB: {summary?.db_agent_count || 0} агентів</span>
|
||||
</div>
|
||||
|
||||
{/* Search & Filter */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-white/30" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Пошук агентів..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-9 pr-3 py-2 bg-slate-800/50 border border-white/10 rounded-lg
|
||||
text-sm text-white placeholder-white/30 focus:outline-none focus:ring-2
|
||||
focus:ring-purple-500/50"
|
||||
/>
|
||||
{agents.length > 0 && (
|
||||
<div className="flex gap-2 mb-4">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-white/30" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Пошук агентів..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-9 pr-3 py-2 bg-slate-800/50 border border-white/10 rounded-lg
|
||||
text-sm text-white placeholder-white/30 focus:outline-none focus:ring-2
|
||||
focus:ring-purple-500/50"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setShowFilterMenu(!showFilterMenu)}
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors ${
|
||||
statusFilter !== 'all'
|
||||
? 'bg-purple-500/20 border-purple-500/50 text-purple-400'
|
||||
: 'bg-slate-800/50 border-white/10 text-white/60 hover:bg-slate-800'
|
||||
}`}
|
||||
>
|
||||
<Filter className="w-4 h-4" />
|
||||
<ChevronDown className="w-3 h-3" />
|
||||
</button>
|
||||
{showFilterMenu && (
|
||||
<div className="absolute right-0 top-full mt-1 bg-slate-800 border border-white/10
|
||||
rounded-lg shadow-xl z-10 min-w-[120px] py-1">
|
||||
{(['all', 'active', 'phantom', 'stale'] as const).map((status) => (
|
||||
<button
|
||||
key={status}
|
||||
onClick={() => {
|
||||
setStatusFilter(status);
|
||||
setShowFilterMenu(false);
|
||||
}}
|
||||
className={`w-full px-3 py-2 text-left text-sm hover:bg-white/5 ${
|
||||
statusFilter === status ? 'text-purple-400' : 'text-white/70'
|
||||
}`}
|
||||
>
|
||||
{status === 'all' ? 'All' : STATUS_CONFIG[status].label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setShowFilterMenu(!showFilterMenu)}
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors ${
|
||||
statusFilter !== 'all'
|
||||
? 'bg-purple-500/20 border-purple-500/50 text-purple-400'
|
||||
: 'bg-slate-800/50 border-white/10 text-white/60 hover:bg-slate-800'
|
||||
}`}
|
||||
>
|
||||
<Filter className="w-4 h-4" />
|
||||
<ChevronDown className="w-3 h-3" />
|
||||
</button>
|
||||
{showFilterMenu && (
|
||||
<div className="absolute right-0 top-full mt-1 bg-slate-800 border border-white/10
|
||||
rounded-lg shadow-xl z-10 min-w-[120px] py-1">
|
||||
{(['all', 'active', 'phantom', 'stale'] as const).map((status) => (
|
||||
<button
|
||||
key={status}
|
||||
onClick={() => {
|
||||
setStatusFilter(status);
|
||||
setShowFilterMenu(false);
|
||||
}}
|
||||
className={`w-full px-3 py-2 text-left text-sm hover:bg-white/5 ${
|
||||
statusFilter === status ? 'text-purple-400' : 'text-white/70'
|
||||
}`}
|
||||
>
|
||||
{status === 'all' ? 'All' : STATUS_CONFIG[status].label}
|
||||
</button>
|
||||
))}
|
||||
)}
|
||||
|
||||
{/* Agents Table */}
|
||||
{agents.length > 0 && (
|
||||
<div className="overflow-hidden rounded-lg border border-white/10">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-slate-800/50 text-left">
|
||||
<th className="px-4 py-3 text-xs font-medium text-white/50 uppercase tracking-wider">
|
||||
Agent
|
||||
</th>
|
||||
<th className="px-4 py-3 text-xs font-medium text-white/50 uppercase tracking-wider">
|
||||
Status
|
||||
</th>
|
||||
<th className="px-4 py-3 text-xs font-medium text-white/50 uppercase tracking-wider hidden md:table-cell">
|
||||
Runtime
|
||||
</th>
|
||||
<th className="px-4 py-3 text-xs font-medium text-white/50 uppercase tracking-wider hidden lg:table-cell">
|
||||
Last Seen
|
||||
</th>
|
||||
<th className="px-4 py-3 text-xs font-medium text-white/50 uppercase tracking-wider text-right">
|
||||
Cabinet
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-white/5">
|
||||
{filteredAgents.map((agent: DagiRouterAgent) => (
|
||||
<tr
|
||||
key={agent.id}
|
||||
className="hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${
|
||||
STATUS_CONFIG[agent.status as keyof typeof STATUS_CONFIG]?.bgClass || 'bg-slate-700'
|
||||
}`}>
|
||||
<Bot className={`w-4 h-4 ${
|
||||
STATUS_CONFIG[agent.status as keyof typeof STATUS_CONFIG]?.textClass || 'text-white/50'
|
||||
}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-white">{agent.name || agent.id}</div>
|
||||
{agent.kind && (
|
||||
<div className="text-xs text-white/40">{agent.kind}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
{getStatusBadge(agent.status)}
|
||||
</td>
|
||||
<td className="px-4 py-3 hidden md:table-cell">
|
||||
<span className="text-sm text-white/60">
|
||||
{agent.runtime || '—'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 hidden lg:table-cell">
|
||||
<span className="text-sm text-white/50">
|
||||
{formatTime(agent.last_seen_at)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
{agent.has_db_record ? (
|
||||
<Link
|
||||
href={`/agents/${agent.id}`}
|
||||
className="inline-flex items-center gap-1 px-2.5 py-1.5 bg-purple-500/10
|
||||
hover:bg-purple-500/20 text-purple-400 rounded-lg text-xs
|
||||
transition-colors"
|
||||
>
|
||||
Open
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-xs text-white/30">—</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Empty state for filtered results */}
|
||||
{filteredAgents.length === 0 && agents.length > 0 && (
|
||||
<div className="text-center py-8 text-white/40">
|
||||
<Search className="w-6 h-6 mx-auto mb-2 opacity-50" />
|
||||
<p>Немає агентів за цим фільтром</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Agents Table */}
|
||||
<div className="overflow-hidden rounded-lg border border-white/10">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-slate-800/50 text-left">
|
||||
<th className="px-4 py-3 text-xs font-medium text-white/50 uppercase tracking-wider">
|
||||
Agent
|
||||
</th>
|
||||
<th className="px-4 py-3 text-xs font-medium text-white/50 uppercase tracking-wider">
|
||||
Status
|
||||
</th>
|
||||
<th className="px-4 py-3 text-xs font-medium text-white/50 uppercase tracking-wider hidden md:table-cell">
|
||||
Runtime
|
||||
</th>
|
||||
<th className="px-4 py-3 text-xs font-medium text-white/50 uppercase tracking-wider hidden lg:table-cell">
|
||||
Last Seen
|
||||
</th>
|
||||
<th className="px-4 py-3 text-xs font-medium text-white/50 uppercase tracking-wider text-right">
|
||||
Cabinet
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-white/5">
|
||||
{filteredAgents.map((agent) => (
|
||||
<tr
|
||||
key={agent.id}
|
||||
className="hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${
|
||||
STATUS_CONFIG[agent.status as keyof typeof STATUS_CONFIG]?.bgClass || 'bg-slate-700'
|
||||
}`}>
|
||||
<Bot className={`w-4 h-4 ${
|
||||
STATUS_CONFIG[agent.status as keyof typeof STATUS_CONFIG]?.textClass || 'text-white/50'
|
||||
}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-white">{agent.name}</span>
|
||||
{agent.has_prompts && (
|
||||
<span
|
||||
className="text-purple-400"
|
||||
title="System Prompts налаштовані"
|
||||
>
|
||||
<Brain className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
)}
|
||||
{!agent.has_prompts && agent.status === 'active' && (
|
||||
<span
|
||||
className="text-amber-400/60"
|
||||
title="System Prompts відсутні"
|
||||
>
|
||||
<Brain className="w-3.5 h-3.5 opacity-50" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{agent.role && (
|
||||
<div className="text-xs text-white/40">{agent.role}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
{getStatusBadge(agent.status)}
|
||||
</td>
|
||||
<td className="px-4 py-3 hidden md:table-cell">
|
||||
<div className="text-sm text-white/60">
|
||||
{agent.gpu && <div>{agent.gpu}</div>}
|
||||
{agent.cpu && <div className="text-xs text-white/40">{agent.cpu}</div>}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 hidden lg:table-cell">
|
||||
<span className="text-sm text-white/50">
|
||||
{agent.last_seen_at ? formatTime(agent.last_seen_at) : '—'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
{agent.has_cabinet && agent.cabinet_slug ? (
|
||||
<Link
|
||||
href={`/agents/${agent.cabinet_slug}`}
|
||||
className="inline-flex items-center gap-1 px-2.5 py-1.5 bg-purple-500/10
|
||||
hover:bg-purple-500/20 text-purple-400 rounded-lg text-xs
|
||||
transition-colors"
|
||||
>
|
||||
Open
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-xs text-white/30">—</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{/* No agents */}
|
||||
{agents.length === 0 && (
|
||||
<div className="text-center py-6 text-white/40">
|
||||
<Bot className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
||||
<p>Немає агентів у Router</p>
|
||||
</div>
|
||||
|
||||
{/* Empty state for filtered results */}
|
||||
{filteredAgents.length === 0 && agents.length > 0 && (
|
||||
<div className="text-center py-8 text-white/40">
|
||||
<Search className="w-6 h-6 mx-auto mb-2 opacity-50" />
|
||||
<p>Немає агентів за цим фільтром</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer stats */}
|
||||
<div className="mt-4 flex items-center justify-between text-xs text-white/40">
|
||||
<span>
|
||||
Показано {filteredAgents.length} з {agents.length} агентів
|
||||
</span>
|
||||
{summary.phantom === 0 && summary.stale === 0 && (
|
||||
<span className="flex items-center gap-1 text-emerald-400">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
Всі агенти синхронізовані
|
||||
{agents.length > 0 && (
|
||||
<div className="mt-4 flex items-center justify-between text-xs text-white/40">
|
||||
<span>
|
||||
Показано {filteredAgents.length} з {agents.length} агентів
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{(summary?.phantom || 0) === 0 && (summary?.stale || 0) === 0 && (
|
||||
<span className="flex items-center gap-1 text-emerald-400">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
Всі агенти синхронізовані
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
78
apps/web/src/hooks/useDAGIRouter.ts
Normal file
78
apps/web/src/hooks/useDAGIRouter.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import useSWR from 'swr';
|
||||
|
||||
const fetcher = (url: string) => fetch(url).then(res => res.json());
|
||||
|
||||
export interface DagiRouterHealth {
|
||||
node_id: string;
|
||||
status: 'up' | 'down' | 'degraded';
|
||||
version: string | null;
|
||||
agent_count: number;
|
||||
latency_ms: number | null;
|
||||
}
|
||||
|
||||
export interface DagiRouterAgent {
|
||||
id: string;
|
||||
name: string | null;
|
||||
kind: string | null;
|
||||
runtime: string | null;
|
||||
node_id: string;
|
||||
last_seen_at: string | null;
|
||||
status: 'active' | 'phantom' | 'stale';
|
||||
has_db_record: boolean;
|
||||
}
|
||||
|
||||
export interface DagiRouterAgentsResponse {
|
||||
node_id: string;
|
||||
total: number;
|
||||
active: number;
|
||||
phantom: number;
|
||||
stale: number;
|
||||
agents: DagiRouterAgent[];
|
||||
}
|
||||
|
||||
export interface DagiRouterSummary {
|
||||
node_id: string;
|
||||
status: 'up' | 'down' | 'degraded';
|
||||
version: string | null;
|
||||
latency_ms: number | null;
|
||||
router_agent_count: number;
|
||||
db_agent_count: number;
|
||||
active: number;
|
||||
phantom: number;
|
||||
stale: number;
|
||||
last_audit_at: string | null;
|
||||
}
|
||||
|
||||
export function useDAGIRouterHealth(nodeId: string | null) {
|
||||
return useSWR<DagiRouterHealth>(
|
||||
nodeId ? `/api/node-internal/${nodeId}/dagi-router/health` : null,
|
||||
fetcher,
|
||||
{
|
||||
refreshInterval: 30000, // Refresh every 30 seconds
|
||||
revalidateOnFocus: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function useDAGIRouterAgents(nodeId: string | null) {
|
||||
return useSWR<DagiRouterAgentsResponse>(
|
||||
nodeId ? `/api/node-internal/${nodeId}/dagi-router/agents` : null,
|
||||
fetcher,
|
||||
{
|
||||
refreshInterval: 30000,
|
||||
revalidateOnFocus: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function useDAGIRouterSummary(nodeId: string | null) {
|
||||
return useSWR<DagiRouterSummary>(
|
||||
nodeId ? `/api/node-internal/${nodeId}/dagi-router/summary` : null,
|
||||
fetcher,
|
||||
{
|
||||
refreshInterval: 30000,
|
||||
revalidateOnFocus: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,3 +78,4 @@ networks:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -122,3 +122,4 @@ networks:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -187,3 +187,4 @@ volumes:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -996,3 +996,4 @@ rules:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -487,3 +487,4 @@ curl -X POST http://localhost:8080/api/messaging/channels \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -511,3 +511,4 @@ Instead of direct Matrix API:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -407,3 +407,4 @@ VALUES (gen_random_uuid(), '<channel-id>', 'agent:sofia', 'agent', '@sofia-agent
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -34,3 +34,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -605,3 +605,4 @@ docker exec daarion-postgres psql -U postgres -d daarion \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -190,3 +190,4 @@ Ref: messages.matrix_event_id - matrix_events.event_id [note: 'Message ↔ Matri
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -541,3 +541,4 @@ open http://localhost:8899/agents
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -863,3 +863,4 @@ networks:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -502,3 +502,4 @@ tools:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -275,3 +275,4 @@ Behavior:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -420,3 +420,4 @@ Behavior:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -238,3 +238,4 @@ COMMENT ON SCHEMA public IS 'Messenger schema v1 - Matrix-aware implementation';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -154,3 +154,4 @@ ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -145,3 +145,4 @@ EXECUTE FUNCTION update_timestamp();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -79,3 +79,4 @@ http {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -40,3 +40,4 @@ server {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
41
scripts/com.daarion.node-guardian.plist
Normal file
41
scripts/com.daarion.node-guardian.plist
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.daarion.node-guardian</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/bin/python3</string>
|
||||
<string>/Users/apple/github-projects/microdao-daarion/scripts/node-guardian-loop.py</string>
|
||||
<string>--node-id=node-2-macbook-m4max</string>
|
||||
</array>
|
||||
|
||||
<key>WorkingDirectory</key>
|
||||
<string>/Users/apple/github-projects/microdao-daarion</string>
|
||||
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>CITY_API_URL</key>
|
||||
<string>https://daarion.space/api/city</string>
|
||||
<key>SWAPPER_URL</key>
|
||||
<string>http://localhost:8890</string>
|
||||
<key>PYTHONUNBUFFERED</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>/tmp/node-guardian.log</string>
|
||||
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/tmp/node-guardian.error.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -23,3 +23,4 @@ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7005"]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -293,3 +293,4 @@ curl http://localhost:7004/internal/messaging/channels/{channel_id}/context
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -17,3 +17,4 @@ rules:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -161,3 +161,4 @@ async def shutdown_event():
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -37,3 +37,4 @@ class FilterContext(BaseModel):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,3 +9,4 @@ PyYAML==6.0.1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -116,3 +116,4 @@ class FilterRules:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,3 +23,4 @@ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7006"]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -406,3 +406,4 @@ curl -X POST http://localhost:7006/internal/agent-runtime/test-channel \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,3 +22,4 @@ memory:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -73,3 +73,4 @@ async def post_message(agent_id: str, channel_id: str, text: str) -> bool:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -36,3 +36,4 @@ class LLMResponse(BaseModel):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -74,3 +74,4 @@ pep_client = PEPClient()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user