diff --git a/.env.example b/.env.example index a2ed330..b03d02f 100644 --- a/.env.example +++ b/.env.example @@ -138,3 +138,11 @@ WEB_INTEL_SELF_URL=http://localhost:4002 WEB_INTEL_V2_SELF_URL=http://localhost:4003 ANALYSIS_AGENT_SELF_URL=http://localhost:4004 REPORT_AGENT_SELF_URL=http://localhost:4005 + +# Cluster Runtime Environments +PORT=3000 +REGISTRY_URL=http://localhost:3000 + +# Agent Cluster Core Policies +# Time-To-Live (TTL) ceiling boundary value in seconds to mark uncommunicative workers inactive. +AGENT_TTL_SECONDS=120 \ No newline at end of file diff --git a/packages/agent_shared_directory/src/server.ts b/packages/agent_shared_directory/src/server.ts new file mode 100644 index 0000000..bd09a0c --- /dev/null +++ b/packages/agent_shared_directory/src/server.ts @@ -0,0 +1,28 @@ +import axios from 'axios'; + +const REGISTRY_URL = process.env.REGISTRY_URL || 'http://localhost:3000'; +const AGENT_ID = process.env.AGENT_ID || 'agent-core-01'; + +/** + * Registers startup configuration and provisions automatic lifecycle loops + */ +export function initializeAgentHeartbeat() { + console.log(`[Lifecycle] Initializing background pulse telemetry for Agent: ${AGENT_ID}`); + + // Establish heartbeat cycle matching the 60-second execution window specifications + const heartbeatInterval = setInterval(async () => { + try { + await axios.post(`${REGISTRY_URL}/agents/${AGENT_ID}/heartbeat`); + } catch (error: any) { + console.warn(`[Lifecycle Warning] Pulse telemetry transmission dropped: ${error.message}`); + } + }, 60000); + + // Structural Cleanup Management: Clear event pools if process triggers termination signals + process.on('SIGTERM', () => { + clearInterval(heartbeatInterval); + }); + process.on('SIGINT', () => { + clearInterval(heartbeatInterval); + }); +} \ No newline at end of file diff --git a/packages/registry/src/store.ts b/packages/registry/src/store.ts index 90c855c..3e2b126 100644 --- a/packages/registry/src/store.ts +++ b/packages/registry/src/store.ts @@ -23,15 +23,45 @@ function ensureDataDir(): void { /** * Load all registered agents from `data/registry.json`. * Returns an empty array if the file doesn't exist or contains invalid JSON. + * * Process state conversions automatically to evaluate agent freshness. */ -export function loadAgents(): AgentRecord[] { +export function loadAgents(includeInactive: boolean = false): AgentRecord[] { ensureDataDir(); if (!fs.existsSync(REGISTRY_FILE)) return []; + + let agents: AgentRecord[] = []; try { - return JSON.parse(fs.readFileSync(REGISTRY_FILE, 'utf-8')); + agents = JSON.parse(fs.readFileSync(REGISTRY_FILE, 'utf-8')); } catch { return []; } + + const nowMs = Date.now(); + const ttlSeconds = Number(process.env.AGENT_TTL_SECONDS) || 120; + const ttlThresholdMs = ttlSeconds * 1000; + let mutated = false; + + // Process live lifecycle updates based on absolute timestamp drift + const updatedAgents = agents.map((agent) => { + const lastSeenMs = new Date(agent.last_seen).getTime(); + const isStale = nowMs - lastSeenMs > ttlThresholdMs; + + if (isStale && agent.status === 'active') { + mutated = true; + return { ...agent, status: 'inactive' as const }; + } + return agent; + }); + + // Automatically sync back to JSON storage file if any statuses collapsed to inactive + if (mutated) { + saveAgents(updatedAgents); + } + + if (includeInactive) { + return updatedAgents; + } + return updatedAgents.filter((a) => a.status === 'active'); } /** Overwrite `data/registry.json` with the given list of agents. */ @@ -42,7 +72,8 @@ export function saveAgents(agents: AgentRecord[]): void { /** Find a single agent by its `agent_id`, or `undefined` if not registered. */ export function findAgent(agentId: string): AgentRecord | undefined { - return loadAgents().find((a) => a.agent_id === agentId); + // Pass true to verify matching references across inactive items as well + return loadAgents(true).find((a) => a.agent_id === agentId); } /** @@ -50,7 +81,7 @@ export function findAgent(agentId: string): AgentRecord | undefined { * Returns the agent that was stored. */ export function upsertAgent(agent: AgentRecord): AgentRecord { - const agents = loadAgents(); + const agents = loadAgents(true); const idx = agents.findIndex((a) => a.agent_id === agent.agent_id); if (idx >= 0) { agents[idx] = agent; @@ -63,9 +94,9 @@ export function upsertAgent(agent: AgentRecord): AgentRecord { /** Remove an agent by `agent_id`. Returns `true` if an agent was removed. */ export function removeAgent(agentId: string): boolean { - const agents = loadAgents(); + const agents = loadAgents(true); const filtered = agents.filter((a) => a.agent_id !== agentId); if (filtered.length === agents.length) return false; saveAgents(filtered); return true; -} +} \ No newline at end of file