UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

208 lines (207 loc) • 8.9 kB
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs"; import { t as createSubsystemLogger } from "./subsystem-I-uqdx4f.mjs"; import { g as resolveDefaultAgentId } from "./agent-scope-config-CDYpzA0r.mjs"; import { d as prepareSystemAgentRunAdmission } from "./admitted-run-context-QJnFIxUZ.mjs"; import { o as getMemoryProviderRuntime } from "./memory-state-a-rASoOR.mjs"; import { o as waitForSessionTranscriptProjection } from "./session-transcript-reconcile-BKkILU5o.mjs"; import { _ as resolveSessionStorePathForScope } from "./session-transcript-worker-runtime-Dty5DpKL.mjs"; import { n as resolveInternalSessionEffectsIdentity } from "./internal-session-key-CPayZwJR.mjs"; import { o as withSessionContextAdmission } from "./session-transcript-read-fence-DfxRHzep.mjs"; import { i as MAX_VISIBLE_MESSAGE_MAX_MESSAGES, r as MAX_VISIBLE_MESSAGE_MAX_BYTES } from "./session-accessor.sqlite-visible-cursor-CJh4GhW9.mjs"; import { i as delegateMemoryAudience, o as resolveMemoryAudienceFromEntry } from "./memory-audience-C76wXpDX.mjs"; import { t as SessionManager } from "./session-manager-CJB28fah.mjs"; import { d as resolveModelFallbackOptions } from "./agent-runner-utils-CZpNUzGS.mjs"; import { r as resolveMemoryFlushPlanForRun, t as isToolsMemoryFlushPlan } from "./memory-flush-plan-CKNTMCtK.mjs"; import fs from "node:fs"; import path from "node:path"; import crypto from "node:crypto"; //#region src/auto-reply/reply/memory-flush-session.ts /** * Derive one UUID-shaped mutation identity from the source session incarnation * (session ID and lifecycle revision) and its pre-compaction count. The count * advances after each completed compaction, so retries within one cycle reuse * the ID and later cycles do not. A reset keeps the session ID and restarts the * count but rotates the lifecycle revision, so a new incarnation never reuses an ID. */ function deriveMemoryFlushId(params) { const bytes = crypto.createHash("sha256").update(`openclaw-memory-flush\0${params.sourceSessionId}\0${params.sourceLifecycleRevision ?? ""}\0${params.compactionCount}`).digest().subarray(0, 16); bytes[6] = (bytes[6] ?? 0) & 15 | 128; bytes[8] = (bytes[8] ?? 0) & 63 | 128; const hex = bytes.toString("hex"); return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; } /** Memory inference owns a detached view; an admission excludes its waiting user. */ async function prepareMemoryFlushSession(params) { params.signal?.throwIfAborted(); await waitForSessionTranscriptProjection(params.source, params.signal); params.signal?.throwIfAborted(); const sessionManager = await withSessionContextAdmission(params.source, params.admission, () => SessionManager.openDetachedBoundedAsync(params.source, { signal: params.signal, cwd: params.workspaceDir, maxBytes: MAX_VISIBLE_MESSAGE_MAX_BYTES, maxEvents: MAX_VISIBLE_MESSAGE_MAX_MESSAGES, onTruncated: () => { throw new Error("Memory flush exceeds the bounded conversation view."); } })); const identity = resolveInternalSessionEffectsIdentity({ agentId: params.source.agentId, runId: params.runId }); return { ...identity, sessionFile: identity.sessionKey, sessionTarget: { agentId: params.source.agentId, storePath: params.source.storePath, ...identity }, sessionManager, sessionPersistence: "detached", cleanupBundleMcpOnRunEnd: true }; } async function ensureMemoryFlushTargetFile(params) { const workspaceDir = normalizeOptionalString(params.workspaceDir); const relativePath = normalizeOptionalString(params.relativePath); if (!workspaceDir || !relativePath || path.isAbsolute(relativePath)) throw new Error("Invalid memory flush target path"); const workspaceRoot = path.resolve(workspaceDir); const targetPath = path.resolve(workspaceRoot, relativePath); const targetRelativePath = path.relative(workspaceRoot, targetPath); if (!targetRelativePath || targetRelativePath.startsWith("..") || path.isAbsolute(targetRelativePath)) throw new Error("Memory flush target path must stay inside the workspace"); params.assertCurrent(); await fs.promises.mkdir(path.dirname(targetPath), { recursive: true }); params.assertCurrent(); const handle = await fs.promises.open(targetPath, "a"); try { params.assertCurrent(); } finally { await handle.close(); } } //#endregion //#region src/auto-reply/reply/memory-flush-prepare.ts const log = createSubsystemLogger("auto-reply/memory-flush"); function resolveMemoryFlushModelFallbackOptions(run, model, configOverride = run.config) { const options = resolveModelFallbackOptions(run, configOverride); const override = normalizeOptionalString(model); if (!override) return options; const slashIdx = override.indexOf("/"); if (slashIdx > 0) { const overrideProvider = override.slice(0, slashIdx).trim(); const overrideModel = override.slice(slashIdx + 1).trim(); if (overrideProvider && overrideModel) return { ...options, provider: overrideProvider, model: overrideModel, requestedRouteResolution: "raw", fallbacksOverride: [] }; } return { ...options, model: override, requestedRouteResolution: "raw", fallbacksOverride: [] }; } /** Prepare one detached flush, source delegation, and persistence target before model fallback. */ async function prepareMemoryFlushAttempt(params) { params.assertCurrent(); const resolution = resolveMemoryFlushPlanForRun({ cfg: params.cfg, nowMs: Date.now(), contextWindowTokens: params.contextWindowTokens }); if (!resolution) return null; const plan = resolution.plan; if (!isToolsMemoryFlushPlan(plan) && !params.memoryFlushWritable) return null; const { sessionKey, sessionEntry, followupRun } = params; if (!sessionKey || !sessionEntry) throw new Error("Memory flush has no current transcript target."); const agentId = followupRun.run.agentId ?? resolveDefaultAgentId(params.cfg); const sourceStorePath = resolveSessionStorePathForScope({ agentId, sessionKey, storePath: params.storePath }, params.cfg); const sourceAudience = isToolsMemoryFlushPlan(plan) && getMemoryProviderRuntime() ? await resolveMemoryAudienceFromEntry({ agentId, sessionKey, sessionId: sessionEntry.sessionId, senderIsOwner: followupRun.memoryAudienceSenderIsOwner ?? followupRun.run.senderIsOwner, storePath: sourceStorePath, assertCallerCurrent: params.assertCurrent }, sessionEntry) : void 0; if (sourceAudience?.status === "denied") { log.debug("memory flush skipped: source turn has no memory audience", { event: "memory_flush_no_audience", sourceSessionKey: sessionKey, sourceSessionId: sessionEntry.sessionId, pluginId: resolution.pluginId, reason: sourceAudience.reason }); return null; } let delegated; const release = () => { delegated?.release(); sourceAudience?.release(); }; try { params.assertCurrent(); const memorySession = await prepareMemoryFlushSession({ admission: params.preflightAdmission, source: { agentId, sessionId: sessionEntry.sessionId, sessionKey, storePath: sourceStorePath }, runId: params.flushRunId, workspaceDir: followupRun.run.workspaceDir, signal: params.abortSignal }); if (!isToolsMemoryFlushPlan(plan)) await ensureMemoryFlushTargetFile({ workspaceDir: followupRun.run.workspaceDir, relativePath: plan.relativePath, assertCurrent: params.assertCurrent }); const systemPrompt = [followupRun.run.extraSystemPrompt, plan.systemPrompt].filter(Boolean).join("\n\n"); const selection = resolveMemoryFlushModelFallbackOptions(followupRun.run, plan.model, params.cfg); params.assertCurrent(); delegated = sourceAudience ? await delegateMemoryAudience(sourceAudience.audience, { sessionKey: memorySession.sessionKey, storePath: memorySession.sessionTarget.storePath, detached: true, assertCallerCurrent: params.assertCurrent }) : void 0; const memoryFlushTools = isToolsMemoryFlushPlan(plan) ? { flushId: deriveMemoryFlushId({ sourceSessionId: sessionEntry.sessionId, sourceLifecycleRevision: sessionEntry.lifecycleRevision, compactionCount: sessionEntry.compactionCount ?? 0 }), ownerPluginId: resolution.pluginId, persistenceToolNames: plan.persistenceToolNames, lookupToolNames: plan.lookupToolNames, recordPersistenceToolSuccess: params.recordPersistenceToolSuccess } : void 0; const preparedRunAdmission = prepareSystemAgentRunAdmission(params.cfg, params.flushRunId, followupRun.run.agentId, "auto-reply.memory-flush", void 0, followupRun.operatorAuthority); return { plan, writePath: isToolsMemoryFlushPlan(plan) ? void 0 : plan.relativePath, systemPrompt, selection, preparedRunAdmission, memorySession, memoryAudience: delegated?.audience, memoryFlushTools, release }; } catch (error) { release(); throw error; } } //#endregion export { prepareMemoryFlushAttempt };