From 1eda652a225e4ce7a5efa2728dcc58dc6e51727c Mon Sep 17 00:00:00 2001 From: YrFnS Date: Tue, 9 Jun 2026 01:40:20 +0300 Subject: [PATCH] fix(mesh-store): persist agent identity to disk so it survives restarts The readIdentity/writeIdentity functions previously stored agent identity only in an in-memory Map, which is lost on every pi restart. This caused registerAgent() to create a new agent with the new TLS fingerprint as ID, breaking message delivery from other peers that still target the old ID. Changes: - readIdentity(): added disk fallback reading from ~/.agent-comms/{harness}--{cwd}.json - writeIdentity(): added disk persistence writing to ~/.agent-comms/{harness}--{cwd}.json - registerAgent(): added re-creation path when identity exists on disk but agent not in local store (e.g., after restart) Fixes #14 Related to #13 --- src/core/mesh-store.ts | 69 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/core/mesh-store.ts b/src/core/mesh-store.ts index fa8175a..78f447d 100644 --- a/src/core/mesh-store.ts +++ b/src/core/mesh-store.ts @@ -11,7 +11,9 @@ * TCP, TlsTransport for encrypted connections, etc. */ +import * as fs from "node:fs"; import * as os from "node:os"; +import * as path from "node:path"; import { nanoid } from "./nanoid.js"; import { CommsError } from "./store.js"; import { TcpTransport } from "./tcp-transport.js"; @@ -786,12 +788,45 @@ export class MeshStore implements CommsStore { cwd: string, ): Promise<{ id: string } | undefined> { await Promise.resolve(); - return this.identityCache.get(`${harness}--${cwd}`); + const cached = this.identityCache.get(`${harness}--${cwd}`); + if (cached) return cached; + // Fallback: read from disk so identity survives restarts + try { + const dir = path.join(os.homedir(), ".agent-comms"); + const file = path.join( + dir, + `${harness}--${cwd.replace(/[\\/:*?"<>|]/g, "_")}.json`, + ); + if (fs.existsSync(file)) { + const data = JSON.parse(fs.readFileSync(file, "utf-8")) as { + id?: string; + }; + if (data?.id) { + this.identityCache.set(`${harness}--${cwd}`, { id: data.id }); + return { id: data.id }; + } + } + } catch { + // ignore read errors + } + return undefined; } async writeIdentity(harness: string, cwd: string, id: string): Promise { await Promise.resolve(); this.identityCache.set(`${harness}--${cwd}`, { id }); + // Persist to disk so identity survives restarts + try { + const dir = path.join(os.homedir(), ".agent-comms"); + fs.mkdirSync(dir, { recursive: true }); + const file = path.join( + dir, + `${harness}--${cwd.replace(/[\\/:*?"<>|]/g, "_")}.json`, + ); + fs.writeFileSync(file, JSON.stringify({ id }), "utf-8"); + } catch { + // ignore write errors + } } // ----------------------------------------------------------------------- @@ -808,13 +843,37 @@ export class MeshStore implements CommsStore { }): Promise { const existing = await this.readIdentity(opts.harness, opts.cwd); if (existing) { - return this.updateAgent(existing.id, { + // If the agent already exists in the local store, update it + if (this.agents.has(existing.id)) { + return this.updateAgent(existing.id, { + name: opts.name, + visibility: opts.visibility, + tags: opts.tags, + status: "active", + pid: opts.pid, + }); + } + // Identity exists on disk but agent not in local store (e.g., after + // restart). Re-create the agent with the persisted ID so other peers + // can find us. + const agent: AgentIdentity = { + id: existing.id, name: opts.name, + harness: opts.harness, + cwd: opts.cwd, + pid: opts.pid, + startedAt: this.startedAt, visibility: opts.visibility, - tags: opts.tags, status: "active", - pid: opts.pid, - }); + tags: opts.tags, + subscribedRooms: [], + }; + this.agents.set(existing.id, agent); + await this.broadcastPatch({ type: "agent_upsert", agent }); + if (agent.visibility === "visible") { + await this.federation.broadcastAgentVisible(agent); + } + return agent; } const id = this.peerId;