From 7dc44b04c1ffcc6dfb30783839312f0ca94bd882 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 12 Jan 2026 18:49:15 +0000 Subject: [PATCH] fix: close memory index and refresh protocol outputs --- .../ClawdbotProtocol/GatewayModels.swift | 8 +++++++ src/auto-reply/reply/directive-handling.ts | 16 ++++++------- src/memory/index.test.ts | 9 ++++++- src/memory/index.ts | 24 +++++++++++++++++++ 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift index 94485a5e7..08026c1fd 100644 --- a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift +++ b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift @@ -1352,6 +1352,7 @@ public struct SkillsUpdateParams: Codable, Sendable { public struct CronJob: Codable, Sendable { public let id: String + public let agentid: String? public let name: String public let description: String? public let enabled: Bool @@ -1366,6 +1367,7 @@ public struct CronJob: Codable, Sendable { public init( id: String, + agentid: String?, name: String, description: String?, enabled: Bool, @@ -1379,6 +1381,7 @@ public struct CronJob: Codable, Sendable { state: [String: AnyCodable] ) { self.id = id + self.agentid = agentid self.name = name self.description = description self.enabled = enabled @@ -1393,6 +1396,7 @@ public struct CronJob: Codable, Sendable { } private enum CodingKeys: String, CodingKey { case id + case agentid = "agentId" case name case description case enabled @@ -1425,6 +1429,7 @@ public struct CronStatusParams: Codable, Sendable { public struct CronAddParams: Codable, Sendable { public let name: String + public let agentid: AnyCodable? public let description: String? public let enabled: Bool? public let schedule: AnyCodable @@ -1435,6 +1440,7 @@ public struct CronAddParams: Codable, Sendable { public init( name: String, + agentid: AnyCodable?, description: String?, enabled: Bool?, schedule: AnyCodable, @@ -1444,6 +1450,7 @@ public struct CronAddParams: Codable, Sendable { isolation: [String: AnyCodable]? ) { self.name = name + self.agentid = agentid self.description = description self.enabled = enabled self.schedule = schedule @@ -1454,6 +1461,7 @@ public struct CronAddParams: Codable, Sendable { } private enum CodingKeys: String, CodingKey { case name + case agentid = "agentId" case description case enabled case schedule diff --git a/src/auto-reply/reply/directive-handling.ts b/src/auto-reply/reply/directive-handling.ts index f769e10c5..7395bf6c2 100644 --- a/src/auto-reply/reply/directive-handling.ts +++ b/src/auto-reply/reply/directive-handling.ts @@ -1158,16 +1158,16 @@ export async function handleDirectiveOnly(params: { await saveSessionStore(storePath, sessionStore); } if (elevatedChanged) { - const nextElevated = - (sessionEntry.elevatedLevel ?? "off") as ElevatedLevel; + const nextElevated = (sessionEntry.elevatedLevel ?? + "off") as ElevatedLevel; enqueueSystemEvent(formatElevatedEvent(nextElevated), { sessionKey, contextKey: "mode:elevated", }); } if (reasoningChanged) { - const nextReasoning = - (sessionEntry.reasoningLevel ?? "off") as ReasoningLevel; + const nextReasoning = (sessionEntry.reasoningLevel ?? + "off") as ReasoningLevel; enqueueSystemEvent(formatReasoningEvent(nextReasoning), { sessionKey, contextKey: "mode:reasoning", @@ -1414,16 +1414,16 @@ export async function persistInlineDirectives(params: { await saveSessionStore(storePath, sessionStore); } if (elevatedChanged) { - const nextElevated = - (sessionEntry.elevatedLevel ?? "off") as ElevatedLevel; + const nextElevated = (sessionEntry.elevatedLevel ?? + "off") as ElevatedLevel; enqueueSystemEvent(formatElevatedEvent(nextElevated), { sessionKey, contextKey: "mode:elevated", }); } if (reasoningChanged) { - const nextReasoning = - (sessionEntry.reasoningLevel ?? "off") as ReasoningLevel; + const nextReasoning = (sessionEntry.reasoningLevel ?? + "off") as ReasoningLevel; enqueueSystemEvent(formatReasoningEvent(nextReasoning), { sessionKey, contextKey: "mode:reasoning", diff --git a/src/memory/index.test.ts b/src/memory/index.test.ts index f088eb074..930247314 100644 --- a/src/memory/index.test.ts +++ b/src/memory/index.test.ts @@ -4,7 +4,7 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { getMemorySearchManager } from "./index.js"; +import { getMemorySearchManager, type MemoryIndexManager } from "./index.js"; vi.mock("./embeddings.js", () => { const embedText = (text: string) => { @@ -29,6 +29,7 @@ vi.mock("./embeddings.js", () => { describe("memory index", () => { let workspaceDir: string; let indexPath: string; + let manager: MemoryIndexManager | null = null; beforeEach(async () => { workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-mem-")); @@ -45,6 +46,10 @@ describe("memory index", () => { }); afterEach(async () => { + if (manager) { + await manager.close(); + manager = null; + } await fs.rm(workspaceDir, { recursive: true, force: true }); }); @@ -67,6 +72,7 @@ describe("memory index", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); if (!result.manager) throw new Error("manager missing"); + manager = result.manager; await result.manager.sync({ force: true }); const results = await result.manager.search("alpha"); expect(results.length).toBeGreaterThan(0); @@ -91,6 +97,7 @@ describe("memory index", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); if (!result.manager) throw new Error("manager missing"); + manager = result.manager; await expect( result.manager.readFile({ relPath: "NOTES.md" }), ).rejects.toThrow("path required"); diff --git a/src/memory/index.ts b/src/memory/index.ts index 1b3d4fc43..b81b10597 100644 --- a/src/memory/index.ts +++ b/src/memory/index.ts @@ -56,6 +56,7 @@ const SNIPPET_MAX_CHARS = 700; const INDEX_CACHE = new Map(); export class MemoryIndexManager { + private readonly cacheKey: string; private readonly cfg: ClawdbotConfig; private readonly agentId: string; private readonly workspaceDir: string; @@ -67,6 +68,7 @@ export class MemoryIndexManager { private watcher: FSWatcher | null = null; private watchTimer: NodeJS.Timeout | null = null; private intervalTimer: NodeJS.Timeout | null = null; + private closed = false; private dirty = false; private sessionWarm = new Set(); private syncing: Promise | null = null; @@ -91,6 +93,7 @@ export class MemoryIndexManager { local: settings.local, }); const manager = new MemoryIndexManager({ + cacheKey: key, cfg, agentId, workspaceDir, @@ -102,12 +105,14 @@ export class MemoryIndexManager { } private constructor(params: { + cacheKey: string; cfg: ClawdbotConfig; agentId: string; workspaceDir: string; settings: ResolvedMemorySearchConfig; providerResult: EmbeddingProviderResult; }) { + this.cacheKey = params.cacheKey; this.cfg = params.cfg; this.agentId = params.agentId; this.workspaceDir = params.workspaceDir; @@ -234,6 +239,25 @@ export class MemoryIndexManager { }; } + async close(): Promise { + if (this.closed) return; + this.closed = true; + if (this.watchTimer) { + clearTimeout(this.watchTimer); + this.watchTimer = null; + } + if (this.intervalTimer) { + clearInterval(this.intervalTimer); + this.intervalTimer = null; + } + if (this.watcher) { + await this.watcher.close(); + this.watcher = null; + } + this.db.close(); + INDEX_CACHE.delete(this.cacheKey); + } + private openDatabase(): DatabaseSync { const dbPath = resolveUserPath(this.settings.store.path); const dir = path.dirname(dbPath);