diff --git a/src/hub/agent-store.ts b/src/hub/agent-store.ts new file mode 100644 index 0000000000..c5aabe1980 --- /dev/null +++ b/src/hub/agent-store.ts @@ -0,0 +1,47 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { DATA_DIR } from "../shared/index.js"; + +export interface AgentRecord { + id: string; + createdAt: number; +} + +const AGENTS_DIR = join(DATA_DIR, "agents"); +const AGENTS_FILE = join(AGENTS_DIR, "agents.json"); + +function ensureDir(): void { + if (!existsSync(AGENTS_DIR)) { + mkdirSync(AGENTS_DIR, { recursive: true }); + } +} + +export function loadAgentRecords(): AgentRecord[] { + if (!existsSync(AGENTS_FILE)) return []; + try { + const content = readFileSync(AGENTS_FILE, "utf-8"); + return JSON.parse(content) as AgentRecord[]; + } catch { + return []; + } +} + +export function saveAgentRecords(records: AgentRecord[]): void { + ensureDir(); + writeFileSync(AGENTS_FILE, JSON.stringify(records, null, 2), "utf-8"); +} + +export function addAgentRecord(record: AgentRecord): void { + const records = loadAgentRecords(); + if (records.some((r) => r.id === record.id)) return; + records.push(record); + saveAgentRecords(records); +} + +export function removeAgentRecord(id: string): void { + const records = loadAgentRecords(); + const filtered = records.filter((r) => r.id !== id); + if (filtered.length !== records.length) { + saveAgentRecords(filtered); + } +} diff --git a/src/hub/hub.ts b/src/hub/hub.ts index 2719558371..77607d0d55 100644 --- a/src/hub/hub.ts +++ b/src/hub/hub.ts @@ -3,6 +3,7 @@ import type { ConnectionState } from "../shared/gateway-sdk/types.js"; import { AsyncAgent } from "../agent/async-agent.js"; import { getDeviceId } from "./device.js"; import { GatewayClient } from "../shared/gateway-sdk/client.js"; +import { loadAgentRecords, addAgentRecord, removeAgentRecord } from "./agent-store.js"; export class Hub { private readonly agents = new Map(); @@ -23,6 +24,18 @@ export class Hub { this.deviceId = getDeviceId(); this.client = this.createClient(this.url); this.client.connect(); + this.restoreAgents(); + } + + /** Restore agents from persistent storage */ + private restoreAgents(): void { + const records = loadAgentRecords(); + for (const record of records) { + this.createAgent(record.id, { persist: false }); + } + if (records.length > 0) { + console.log(`[Hub] Restored ${records.length} agent(s)`); + } } private createClient(url: string): GatewayClient { @@ -82,7 +95,7 @@ export class Hub { } /** Create new Agent, or rebuild with existing ID */ - createAgent(id?: string): AsyncAgent { + createAgent(id?: string, options?: { persist?: boolean }): AsyncAgent { if (id) { const existing = this.agents.get(id); if (existing && !existing.closed) { @@ -93,6 +106,11 @@ export class Hub { const agent = new AsyncAgent({ sessionId: id }); this.agents.set(agent.sessionId, agent); + // Persist to agent store (skip during restore to avoid duplicates) + if (options?.persist !== false) { + addAgentRecord({ id: agent.sessionId, createdAt: Date.now() }); + } + // Internally consume messages produced by agent void this.consumeAgent(agent); @@ -130,6 +148,7 @@ export class Hub { agent.close(); this.agents.delete(id); this.agentSenders.delete(id); + removeAgentRecord(id); return true; }