UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

364 lines (363 loc) 18.4 kB
import { m as resolveAgentWorkspaceDir, u as resolveAgentDir } from "../../agent-scope-config-DcbEhP0R.js"; import { l as normalizePluginsConfig } from "../../config-state-BkU1frVq.js"; import { a as getMemoryCapabilityRegistration } from "../../memory-state-BanUXlB7.js"; import { t as definePluginEntry } from "../../plugin-entry-zfBGJaNO.js"; import { n as resolveLivePluginConfigObject } from "../../plugin-config-runtime-BaaMs-tR.js"; import "../../agent-runtime-B15RhSG0.js"; import "../../memory-host-core-BuVChYR2.js"; import { L as MAX_SETUP_GRACE_TIMEOUT_MS, M as HOOK_TIMEOUT_RECOVERY_GRACE_MS, R as MAX_TIMEOUT_MS, l as ACTIVE_MEMORY_STATUS_PREFIX } from "../../types-C1PF5JnV.js"; import { c as resetActiveMemoryConfigForTests, d as setMinimumTimeoutMsForTests, f as setSetupGraceTimeoutMsForTests, i as isMissingRegisteredMemoryToolsError, o as normalizePluginConfig, r as hasDeprecatedModelFallbackPolicy, s as readActiveMemoryConfig, t as applyCliRuntimeRecallTimeoutDefault } from "../../config-DAnNH7TW.js"; import { n as resolveRecallEscalationDecision } from "../../escalation-B0r0wGN-.js"; import { n as buildSearchQuery, o as getModelRef, r as extractRecentTurns, t as buildQuery } from "../../query-DZyTmNC1.js"; import { n as buildPromptPrefix, r as buildRecallOutcomePrefix, t as buildMetadata } from "../../prompt-Ca-uneA0.js"; import { a as getCircuitBreakerEntry, c as resetActiveRecallStateForTests, f as setCachedResult, i as getCachedResult, m as toSingleLineErrorMessage, n as buildCircuitBreakerKey, o as isCircuitBreakerOpen, p as shouldCacheResult, r as forgetActiveRecallRun, t as buildCacheKey } from "../../recall-state-BqGfVoAV.js"; import { i as resolveCanonicalSessionKeyFromSessionId, n as buildPluginStatusLine, o as resolveStatusUpdateAgentId, r as persistPluginStatusLines } from "../../session-C3P3SoFy.js"; import { o as hasUsableMemoryResultInSessionRecord, t as createActiveMemoryHookDeadline } from "../../transcript-TDSwXotJ.js"; import { a as readPartialAssistantText, c as resetActiveMemoryTranscriptForTests, l as setTimeoutPartialDataGraceMsForTests } from "../../transcript-result-BMKJKzR6.js"; import { t as maybeResolveActiveRecall } from "../../recall-Bz4T2Sci.js"; import { a as isAllowedChatId, c as isEnabledForAgent, d as lacksAdminToMutateActiveMemoryGlobal, f as resolveCommandSessionKey, g as updateActiveMemoryGlobalEnabledInConfig, h as shouldSkipActiveMemoryForHarnessSession, i as isActiveMemoryPluginEnabled, l as isPrivateRecallDestination, m as shouldRememberAcrossConversations, n as formatActiveMemoryCommandHelp, o as isAllowedChatType, p as setSessionActiveMemoryDisabled, r as isActiveMemoryGloballyEnabled, s as isEligibleInteractiveSession, t as ACTIVE_MEMORY_GLOBAL_MUTATION_ADMIN_REQUIRED_TEXT, u as isSessionActiveMemoryDisabled } from "../../session-policy-C-GeBLlU.js"; import { a as resetTriggerRecallRunsForTests, o as resolveTriggerRecall, r as forgetTriggerRecallRun } from "../../trigger-recall-C8jxPiCM.js"; //#region extensions/active-memory/index.ts var active_memory_default = definePluginEntry({ id: "active-memory", name: "Active Memory", description: "Proactively surfaces relevant memory before eligible conversational replies.", register(api) { const readCurrentConfig = () => readActiveMemoryConfig(api); let config = normalizePluginConfig(api.pluginConfig, readCurrentConfig()); const warnDeprecatedModelFallbackPolicy = (pluginConfig) => { if (hasDeprecatedModelFallbackPolicy(pluginConfig)) api.logger.warn?.("active-memory: config.modelFallbackPolicy is deprecated and no longer changes runtime behavior. config.modelFallback is a chain-resolution last-resort (consulted only when config.model, the current run's model, and the agent's configured default all resolve to nothing) — it is NOT a runtime failover that substitutes a different model when the resolved model errors out."); }; warnDeprecatedModelFallbackPolicy(api.pluginConfig); const refreshLiveConfigFromRuntime = () => { const livePluginConfig = resolveLivePluginConfigObject(api.runtime.config?.current ? () => api.runtime.config.current() : void 0, "active-memory", api.pluginConfig); const liveConfig = readCurrentConfig(); const effectivePluginConfig = !isActiveMemoryPluginEnabled(liveConfig) ? { enabled: false } : livePluginConfig ?? {}; config = normalizePluginConfig(effectivePluginConfig, liveConfig); if (livePluginConfig) warnDeprecatedModelFallbackPolicy(livePluginConfig); }; api.registerCommand({ name: "active-memory", description: "Enable, disable, or inspect Active Memory for this session.", acceptsArgs: true, exposeSenderIsOwner: true, handler: async (ctx) => { const tokens = ctx.args?.trim().split(/\s+/).filter(Boolean) ?? []; const isGlobal = tokens.includes("--global"); const action = (tokens.find((token) => token !== "--global") ?? "status").toLowerCase(); if (action === "help") return { text: formatActiveMemoryCommandHelp() }; const enabled = [ "on", "enable", "enabled" ].includes(action) ? true : [ "off", "disable", "disabled" ].includes(action) ? false : void 0; refreshLiveConfigFromRuntime(); if (isGlobal) { const currentConfig = api.runtime.config.current(); if (action === "status") return { text: `Active Memory: ${isActiveMemoryGloballyEnabled(currentConfig) ? "on" : "off"} globally.` }; if (lacksAdminToMutateActiveMemoryGlobal({ senderIsOwner: ctx.senderIsOwner, gatewayClientScopes: ctx.gatewayClientScopes })) return { text: ACTIVE_MEMORY_GLOBAL_MUTATION_ADMIN_REQUIRED_TEXT }; if (enabled !== void 0) { await api.runtime.config.mutateConfigFile({ afterWrite: { mode: "auto" }, mutate: (draft) => { const nextConfig = updateActiveMemoryGlobalEnabledInConfig(draft, enabled); Object.assign(draft, nextConfig); } }); refreshLiveConfigFromRuntime(); return { text: `Active Memory: ${enabled ? "on" : "off"} globally.` }; } } const sessionKey = resolveCommandSessionKey({ api, config, sessionKey: ctx.sessionKey, sessionId: ctx.sessionId }); if (!sessionKey) return { text: "Active Memory: session toggle unavailable because this command has no session context." }; const commandAgentId = resolveStatusUpdateAgentId({ sessionKey }); const liveConfig = readCurrentConfig(); if (!(isEnabledForAgent(config, commandAgentId) || config.enabled && shouldRememberAcrossConversations(liveConfig, commandAgentId))) return { text: "Active Memory: off for this session." }; if (action === "status") return { text: `Active Memory: ${await isSessionActiveMemoryDisabled({ api, sessionKey }) ? "off" : "on"} for this session.` }; if (enabled !== void 0) { await setSessionActiveMemoryDisabled({ api, sessionKey, disabled: !enabled }); if (!enabled) await persistPluginStatusLines({ api, agentId: commandAgentId, sessionKey }); return { text: `Active Memory: ${enabled ? "on" : "off"} for this session.` }; } return { text: `Unknown Active Memory action: ${action}\n\n${formatActiveMemoryCommandHelp()}` }; } }); const beforePromptBuildTimeoutMs = MAX_TIMEOUT_MS + MAX_SETUP_GRACE_TIMEOUT_MS + HOOK_TIMEOUT_RECOVERY_GRACE_MS * 2; api.on("before_prompt_build", async (event, ctx) => { const toolAuthority = ctx.toolAuthority; if (!toolAuthority) { api.logger.debug?.("active-memory: recall skipped because this prompt has no turn tool authority"); return; } toolAuthority.assertActive(); refreshLiveConfigFromRuntime(); const liveConfig = readCurrentConfig(); const timeoutAgentId = resolveStatusUpdateAgentId(ctx); const timeoutModelRef = (timeoutAgentId ? getModelRef(liveConfig, timeoutAgentId, config, { modelProviderId: ctx.modelProviderId, modelId: ctx.modelId }) : { provider: ctx.modelProviderId, model: ctx.modelId }) ?? {}; const cliDispatchEligibility = api.runtime.agent.resolveCliBackendDispatchEligibility({ provider: timeoutModelRef.provider, model: timeoutModelRef.model, config: liveConfig, ...timeoutAgentId ? { agentId: timeoutAgentId, agentDir: resolveAgentDir(liveConfig, timeoutAgentId), workspaceDir: resolveAgentWorkspaceDir(liveConfig, timeoutAgentId) } : {} }); const invocationConfig = applyCliRuntimeRecallTimeoutDefault(config, cliDispatchEligibility !== void 0); const authorityAllowedRecallTools = invocationConfig.toolsAllow.filter((toolName) => toolAuthority.allows(toolName)); const liveRecallTimeoutMs = invocationConfig.timeoutMs + invocationConfig.setupGraceTimeoutMs + HOOK_TIMEOUT_RECOVERY_GRACE_MS; const deadlineController = new AbortController(); const hookDeadline = createActiveMemoryHookDeadline(); const armHookDeadline = (timeoutMs, phase) => { hookDeadline.arm(timeoutMs, () => { deadlineController.abort(/* @__PURE__ */ new Error(`active-memory ${phase} timeout after ${timeoutMs}ms`)); api.logger.warn?.(`active-memory: before_prompt_build ${phase} timed out after ${String(timeoutMs)}ms; skipping memory lookup`); }); }; armHookDeadline(HOOK_TIMEOUT_RECOVERY_GRACE_MS, "preflight"); const handlerPromise = (async () => { try { const resolvedAgentId = resolveStatusUpdateAgentId(ctx); const resolvedSessionKey = ctx.sessionKey?.trim() || (resolvedAgentId ? resolveCanonicalSessionKeyFromSessionId({ api, agentId: resolvedAgentId, sessionId: ctx.sessionId }) : void 0); const effectiveAgentId = resolvedAgentId || resolveStatusUpdateAgentId({ sessionKey: resolvedSessionKey }); if (authorityAllowedRecallTools.length === 0) { await persistPluginStatusLines({ api, agentId: effectiveAgentId, sessionKey: resolvedSessionKey, statusLine: `${ACTIVE_MEMORY_STATUS_PREFIX} status=policy-disabled` }); toolAuthority.assertActive(); return; } if (shouldSkipActiveMemoryForHarnessSession({ api, agentId: effectiveAgentId, sessionKey: resolvedSessionKey })) return; const sessionDisabled = await isSessionActiveMemoryDisabled({ api, sessionKey: resolvedSessionKey }); deadlineController.signal.throwIfAborted(); toolAuthority.assertActive(); if (sessionDisabled) { await persistPluginStatusLines({ api, agentId: effectiveAgentId, sessionKey: resolvedSessionKey }); return; } const sessionContext = { ...ctx, sessionKey: resolvedSessionKey ?? ctx.sessionKey }; if (!isEligibleInteractiveSession(sessionContext)) { await persistPluginStatusLines({ api, agentId: effectiveAgentId, sessionKey: resolvedSessionKey }); return; } const destinationContext = { ...sessionContext, mainKey: liveConfig.session?.mainKey ?? api.config.session?.mainKey }; const recentTurns = extractRecentTurns(event.messages); const searchQuery = buildSearchQuery({ latestUserMessage: event.prompt, recentTurns }); const memorySlot = normalizePluginsConfig(liveConfig.plugins).slots.memory; const memoryCapabilityRegistration = getMemoryCapabilityRegistration(); const memoryCapability = memoryCapabilityRegistration && memoryCapabilityRegistration.pluginId === memorySlot ? memoryCapabilityRegistration.capability : void 0; const allowedRecallTools = authorityAllowedRecallTools; const deterministicRecallToolName = memoryCapability?.deterministicRecallToolName; const chatIdAllowed = isAllowedChatId(invocationConfig, { sessionKey: destinationContext.sessionKey, messageProvider: destinationContext.messageProvider, channelId: destinationContext.channelId }); const activeMemoryConfigured = isEnabledForAgent(invocationConfig, effectiveAgentId); let laneOne = { hasStrongHit: false, injectedCount: 0 }; if (activeMemoryConfigured && effectiveAgentId && deterministicRecallToolName && allowedRecallTools.includes(deterministicRecallToolName) && isPrivateRecallDestination(destinationContext) && chatIdAllowed) { toolAuthority.assertActive(); laneOne = await resolveTriggerRecall({ cfg: liveConfig, agentId: effectiveAgentId, query: searchQuery, message: event.prompt, activeProjectKeys: ctx.activeProjectKeys, signal: AbortSignal.timeout(HOOK_TIMEOUT_RECOVERY_GRACE_MS), runId: ctx.runId, authorityFingerprint: toolAuthority.fingerprint }).catch((error) => { api.logger.debug?.(`active-memory: lane-1 trigger recall failed: ${toSingleLineErrorMessage(error)}`); return { hasStrongHit: false, injectedCount: 0 }; }); toolAuthority.assertActive(); if (laneOne.context && laneOne.injectedCount > 0 && invocationConfig.logging) api.logger.info?.(`active-memory: lane-1 injected ${laneOne.injectedCount} trigger-matched entries`); } const laneOneContext = laneOne.context; const activeMemoryAllowed = activeMemoryConfigured && isAllowedChatType(invocationConfig, destinationContext) && chatIdAllowed; const productRecallRequested = Boolean(invocationConfig.enabled && resolvedSessionKey && shouldRememberAcrossConversations(liveConfig, effectiveAgentId) && isPrivateRecallDestination(destinationContext) && chatIdAllowed); const productRecallEligible = productRecallRequested && memoryCapability?.supportsPrivateTranscriptRecall === true; if (productRecallRequested && !productRecallEligible) api.logger.warn?.("active-memory: the current memory provider does not support protected private transcript recall; skipping Remember across conversations"); const productRecallToolName = productRecallEligible && deterministicRecallToolName && allowedRecallTools.includes(deterministicRecallToolName) ? deterministicRecallToolName : void 0; const productRecallAllowed = Boolean(productRecallToolName); if (productRecallEligible && !productRecallAllowed) api.logger.warn?.(`active-memory: ${deterministicRecallToolName ?? "the provider's deterministic recall tool"} is unavailable; skipping Remember across conversations private transcript recall`); if (!activeMemoryAllowed && !productRecallAllowed) { await persistPluginStatusLines({ api, agentId: effectiveAgentId, sessionKey: resolvedSessionKey }); return laneOneContext ? { prependContext: laneOneContext } : void 0; } const escalationDecision = resolveRecallEscalationDecision({ mode: invocationConfig.mode, message: event.prompt, hasStrongLaneOneHit: laneOne.hasStrongHit }); if (escalationDecision !== "recall") { api.logger.debug?.(`active-memory: recall skipped reason=${escalationDecision}`); const prependContext = [laneOneContext, escalationDecision === "no-recall-intent" ? buildRecallOutcomePrefix("skipped-no-recall-intent") : void 0].filter(Boolean).join("\n"); return prependContext ? { prependContext } : void 0; } const conversationRecall = productRecallAllowed && resolvedSessionKey ? { anchorSessionKey: resolvedSessionKey, scope: "same-agent-private", corpus: activeMemoryAllowed ? "configured" : "sessions" } : void 0; const recallConfig = productRecallToolName && !activeMemoryAllowed ? { ...invocationConfig, toolsAllow: [productRecallToolName] } : { ...invocationConfig, toolsAllow: allowedRecallTools }; const query = buildQuery({ latestUserMessage: event.prompt, recentTurns, config: recallConfig }); armHookDeadline(liveRecallTimeoutMs, "recall"); toolAuthority.assertActive(); const result = await maybeResolveActiveRecall({ api, runtimeConfig: liveConfig, config: recallConfig, agentId: effectiveAgentId, sessionKey: resolvedSessionKey, sessionId: ctx.sessionId, messageProvider: ctx.messageProvider, channelId: ctx.channelId, query, searchQuery, currentModelProviderId: ctx.modelProviderId, currentModelId: ctx.modelId, conversationRecall, abortSignal: deadlineController.signal, runId: ctx.runId, authorityFingerprint: toolAuthority.fingerprint, memorySlot: memorySlot ?? void 0, activeProjectKeys: ctx.activeProjectKeys }); deadlineController.signal.throwIfAborted(); toolAuthority.assertActive(); const prependContext = [laneOneContext, result.summary ? buildPromptPrefix(result.summary) : result.status === "unavailable" ? buildRecallOutcomePrefix(result.status) : void 0].filter(Boolean).join("\n"); return prependContext ? { prependContext } : void 0; } catch (error) { if (deadlineController.signal.aborted) return; const message = toSingleLineErrorMessage(error); api.logger.warn?.(`active-memory: before_prompt_build failed, skipping memory lookup: ${message}`); return; } })(); try { const result = await Promise.race([handlerPromise, hookDeadline.promise]); return typeof result === "symbol" ? void 0 : result; } finally { hookDeadline.stop(); } }, { timeoutMs: beforePromptBuildTimeoutMs, requiresToolAuthority: true }); api.on("agent_end", (event, ctx) => { const runId = event.runId ?? ctx.runId; forgetActiveRecallRun(runId); forgetTriggerRecallRun(runId); }); } }); const testing = { buildSearchQuery, buildCacheKey, buildCircuitBreakerKey, buildMetadata, buildPluginStatusLine, buildPromptPrefix, getCachedResult, hasUsableMemoryResultInSessionRecord, isCircuitBreakerOpen, isMissingRegisteredMemoryToolsError, normalizePluginConfig, readPartialAssistantText, shouldCacheResult, resetActiveRecallCacheForTests() { resetActiveRecallStateForTests(); resetActiveMemoryConfigForTests(); resetActiveMemoryTranscriptForTests(); resetTriggerRecallRunsForTests(); }, setMinimumTimeoutMsForTests, setSetupGraceTimeoutMsForTests, setTimeoutPartialDataGraceMsForTests, setCachedResult, getCircuitBreakerEntry }; //#endregion export { active_memory_default as default, testing };