fix: close memory index and refresh protocol outputs

This commit is contained in:
Peter Steinberger
2026-01-12 18:49:15 +00:00
parent 45232137a2
commit 7dc44b04c1
4 changed files with 48 additions and 9 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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");

View File

@@ -56,6 +56,7 @@ const SNIPPET_MAX_CHARS = 700;
const INDEX_CACHE = new Map<string, MemoryIndexManager>();
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<string>();
private syncing: Promise<void> | 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<void> {
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);