From 98f505be9897efc892e9434e1820a4dacdf8464a Mon Sep 17 00:00:00 2001 From: mintyleaf Date: Thu, 25 Jun 2026 16:04:43 +0400 Subject: [PATCH 1/2] fix(coding-agent): merge cavemem hooks safely --- .../coding-agent/src/core/agent-session.ts | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index 206fa6034..309e2b2c8 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -270,6 +270,14 @@ export function promptTimingMark(label: string): void { } } +function mergeHooksConfig(base: HooksConfig | undefined, extra: HooksConfig): HooksConfig { + const merged: HooksConfig = { ...(base ?? {}) }; + for (const [event, groups] of Object.entries(extra)) { + merged[event] = [...(merged[event] ?? []), ...(groups ?? [])]; + } + return merged; +} + /** Standard thinking levels */ const THINKING_LEVELS: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high"]; @@ -3177,19 +3185,16 @@ export class AgentSession { sessionId: () => this.sessionId, registry: { disableAllHooks: sm.getDisableAllHooks?.() ?? false }, }); - const global = sm.getGlobalHooks?.() as HooksConfig | undefined; + let global = sm.getGlobalHooks?.() as HooksConfig | undefined; const project = sm.getProjectHooks?.() as HooksConfig | undefined; - if (global) manager.registry.setLayer("global", global); - if (project) manager.registry.setLayer("project", project); // Auto-record cavemem hooks when CAVE_MEMORY_AUTO_RECORD=1 (opt-in, no - // settings-manager dependency yet). When set, merge cavemem hook - // recipes on top of the global layer. + // settings-manager dependency yet). Append cavemem recipes to the global + // layer instead of replacing user hooks for the same event. if (process.env.CAVE_MEMORY_AUTO_RECORD === "1") { - manager.registry.setLayer("global", { - ...((global ?? {}) as HooksConfig), - ...buildDefaultCavememHooks(), - } as HooksConfig); + global = mergeHooksConfig(global, buildDefaultCavememHooks() as HooksConfig); } + if (global) manager.registry.setLayer("global", global); + if (project) manager.registry.setLayer("project", project); return manager; } From 3de167d3c03973fee54b142372324d38b381bde1 Mon Sep 17 00:00:00 2001 From: mintyleaf Date: Wed, 24 Jun 2026 18:59:34 +0400 Subject: [PATCH 2/2] fix(coding-agent): pass MCP hub to cavemem --- .../coding-agent/src/core/agent-session.ts | 31 +++++++++++++++++-- .../coding-agent/src/core/memory-factory.ts | 7 +++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index 309e2b2c8..ca11be1ea 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -24,7 +24,7 @@ import type { AgentTool, ThinkingLevel, } from "@juliusbrussee/caveman-agent"; -import { checkpoints, LLMLinguaMiddleware, memory as memoryNs } from "@juliusbrussee/caveman-agent"; +import { mcp as agentMcp, checkpoints, LLMLinguaMiddleware, memory as memoryNs } from "@juliusbrussee/caveman-agent"; const { CheckpointManager } = checkpoints; type CheckpointManagerInstance = InstanceType; @@ -289,6 +289,21 @@ const THINKING_LEVELS_WITH_XHIGH: ThinkingLevel[] = ["off", "minimal", "low", "m // ============================================================================ export class AgentSession { + private _mcpHub?: agentMcp.McpHub; + private async mcpHub(): Promise { + if (this._mcpHub) return this._mcpHub; + + const loaded = agentMcp.loadMcpConfig(this._cwd); + const hub = new agentMcp.McpHub({ settings: loaded.settings }); + + for (const server of loaded.servers) { + hub.addServer(server); + } + + this._mcpHub = hub; + return hub; + } + readonly agent: Agent; readonly sessionManager: SessionManager; readonly settingsManager: SettingsManager; @@ -870,7 +885,12 @@ export class AgentSession { async memoryProvider(): Promise { if (this._memoryProvider) return this._memoryProvider; if (!this._memoryProviderInit) { - this._memoryProviderInit = resolveMemoryProvider({ cwd: this._cwd }) + this._memoryProviderInit = resolveMemoryProvider({ + cwd: this._cwd, + cavememOptions: { + hub: await this.mcpHub(), + }, + }) .then((p) => { this._memoryProvider = p; return p; @@ -3286,7 +3306,12 @@ export class AgentSession { // Cached factory means cavemem (or FilesProvider fallback) is built once // and shared with the `/memory` slash command + the recall transform. try { - const memoryProvider = await resolveMemoryProvider({ cwd: this._cwd }); + const memoryProvider = await resolveMemoryProvider({ + cwd: this._cwd, + cavememOptions: { + hub: await this.mcpHub(), + }, + }); this._memoryProvider = memoryProvider; const available = await memoryProvider.isAvailable().catch(() => false); // Always register; the tools themselves short-circuit when unavailable. diff --git a/packages/coding-agent/src/core/memory-factory.ts b/packages/coding-agent/src/core/memory-factory.ts index c1be72c29..3f4bc2b01 100644 --- a/packages/coding-agent/src/core/memory-factory.ts +++ b/packages/coding-agent/src/core/memory-factory.ts @@ -29,8 +29,8 @@ interface CacheEntry { const _cache = new Map(); -function cacheKey(cwd: string, allowCavemem: boolean): string { - return `${allowCavemem ? "cm" : "fs"}::${cwd}`; +function cacheKey(cwd: string, allowCavemem: boolean, hasHub: boolean): string { + return `${allowCavemem ? "cm" : "fs"}:${hasHub ? "hub" : "nohub"}::${cwd}`; } /** @@ -40,7 +40,8 @@ function cacheKey(cwd: string, allowCavemem: boolean): string { */ export async function resolveMemoryProvider(opts: MemoryFactoryOptions): Promise { const allowCavemem = opts.allowCavemem !== false; - const key = cacheKey(opts.cwd, allowCavemem); + const hasHub = Boolean(opts.cavememOptions?.hub); + const key = cacheKey(opts.cwd, allowCavemem, hasHub); const cached = _cache.get(key); if (cached) return cached.provider;