UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

289 lines (288 loc) 12 kB
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { m as resolveAgentWorkspaceDir, u as resolveAgentDir } from "./agent-scope-config-DcbEhP0R.js"; import { O as parseAgentSessionKey } from "./session-key-BnWWjqNc.js"; import { n as parseSqliteSessionFileMarker, t as formatSqliteSessionFileMarker } from "./legacy-sqlite-marker-COPKCuIN.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import "./routing-adlWg0R3.js"; import { s as readSessionTranscriptEvents } from "./session-transcript-runtime-unJeSCUF.js"; import { c as patchSessionEntry, t as cleanupSessionLifecycleArtifacts } from "./session-store-runtime-Btld46Zi.js"; import "./agent-runtime-B15RhSG0.js"; import { s as ACTIVE_MEMORY_RECALL_LANE, t as ACTIVE_MEMORY_CLEANUP_RETRY_DELAYS_MS } from "./types-C1PF5JnV.js"; import { i as isMissingRegisteredMemoryToolsError, l as resolvePersistentTranscriptBaseDir, u as resolveSafeTranscriptDir } from "./config-DAnNH7TW.js"; import { o as getModelRef } from "./query-DZyTmNC1.js"; import { i as buildRecallPrompt } from "./prompt-Ca-uneA0.js"; import { m as toSingleLineErrorMessage } from "./recall-state-BqGfVoAV.js"; import { a as resolveRecallRunChannelContext } from "./session-C3P3SoFy.js"; import { t as readMergedActiveMemoryTranscriptState } from "./transcript-watch-DVCpP3wT.js"; import { i as readMemoryToolResultEvidence, o as readPartialAssistantTextFromSources, t as attachPartialTimeoutData } from "./transcript-result-BMKJKzR6.js"; import path from "node:path"; import fs from "node:fs/promises"; import crypto from "node:crypto"; import { setTimeout } from "node:timers/promises"; //#region extensions/active-memory/recall-run.ts async function persistActiveMemoryTranscriptArtifact(params) { const events = []; const seen = /* @__PURE__ */ new Set(); for (const source of params.sources) { let sourceEvents; try { sourceEvents = await readSessionTranscriptEvents(source); } catch { continue; } for (const event of sourceEvents) { const serialized = JSON.stringify(event); if (seen.has(serialized)) continue; seen.add(serialized); events.push(event); } } if (events.length === 0) return; await fs.mkdir(path.dirname(params.sessionFile), { recursive: true, mode: 448 }); await fs.writeFile(params.sessionFile, `${events.map((event) => JSON.stringify(event)).join("\n")}\n`, { encoding: "utf8", mode: 384 }); } async function cleanupActiveMemoryRecallSession(params) { const sessionKeySegmentPrefix = parseAgentSessionKey(params.sessionKey)?.rest ?? params.sessionKey; let lastError; for (const delayMs of ACTIVE_MEMORY_CLEANUP_RETRY_DELAYS_MS) { if (delayMs > 0) await setTimeout(delayMs); try { const result = await cleanupSessionLifecycleArtifacts({ agentId: params.agentId, archiveRemovedEntryTranscripts: false, orphanTranscriptMinAgeMs: 0, sessionKeySegmentPrefix, storePath: params.storePath, transcriptContentMarker: `"runId":"${params.sessionId}"` }); if (result.removedEntries !== 1) throw new Error(`active-memory recall cleanup removed ${String(result.removedEntries)} sessions`); return; } catch (error) { lastError = error; } } throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`active-memory recall cleanup failed: ${String(lastError)}`); } async function runRecallSubagent(params) { const workspaceDir = resolveAgentWorkspaceDir(params.runtimeConfig, params.agentId); const agentDir = resolveAgentDir(params.runtimeConfig, params.agentId); const modelRef = params.modelRef ?? getModelRef(params.runtimeConfig, params.agentId, params.config, { modelProviderId: params.currentModelProviderId, modelId: params.currentModelId }); if (!modelRef) return { rawReply: "NONE" }; const subagentSessionId = `active-memory-${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`; const parentSessionKey = params.parentSessionKey; const subagentScope = parentSessionKey ?? params.sessionId ?? crypto.randomUUID(); const subagentSuffix = `active-memory:${crypto.createHash("sha1").update(`${subagentScope}:${params.query}:${subagentSessionId}`).digest("hex").slice(0, 12)}`; const subagentSessionKey = parentSessionKey ? `${parentSessionKey}:${subagentSuffix}` : `agent:${params.agentId}:${subagentSuffix}`; const persistedDir = params.config.persistTranscripts ? resolveSafeTranscriptDir(resolvePersistentTranscriptBaseDir(params.api, params.agentId), params.config.transcriptDir) : void 0; const artifactSessionFile = persistedDir !== void 0 ? path.join(persistedDir, `${subagentSessionId}.jsonl`) : void 0; const storePath = params.storePath; const runtimeSessionFile = formatSqliteSessionFileMarker({ agentId: params.agentId, sessionId: subagentSessionId, storePath }); const runtimeSource = { agentId: params.agentId, sessionId: subagentSessionId, sessionKey: subagentSessionKey, storePath }; const transcriptSources = [runtimeSource]; let harnessHasUsableMemoryResult = false; let harnessHasUnavailableMemorySearchResult = false; let transcriptArtifactPersisted = false; let runtimeSessionCreated = false; let resultStatus; const cleanupRecallResources = async () => { try { if (runtimeSessionCreated) { if (artifactSessionFile && !transcriptArtifactPersisted) await persistActiveMemoryTranscriptArtifact({ sources: transcriptSources, sessionFile: artifactSessionFile }).catch((error) => { const message = toSingleLineErrorMessage(error); params.api.logger.debug?.(`active-memory: failed to persist recall transcript ${artifactSessionFile}: ${message}`); }); await cleanupActiveMemoryRecallSession({ agentId: params.agentId, sessionId: subagentSessionId, sessionKey: subagentSessionKey, storePath }).catch((error) => { const message = toSingleLineErrorMessage(error); params.api.logger.warn?.(`active-memory: failed to clean up recall session ${subagentSessionKey}: ${message}`); throw error; }); } } catch (error) { attachPartialTimeoutData(error, { cleanupFailed: true }); throw error; } }; try { const runtimeEntry = { pluginOwnerId: params.api.id, sessionId: subagentSessionId, sessionFile: runtimeSessionFile, updatedAt: Date.now() }; if ((await patchSessionEntry({ agentId: params.agentId, fallbackEntry: runtimeEntry, replaceEntry: true, sessionKey: subagentSessionKey, skipMaintenance: true, storePath, update: (_entry, context) => context.existingEntry ? null : runtimeEntry }))?.sessionId !== subagentSessionId) throw new Error(`active-memory recall session already exists: ${subagentSessionKey}`); runtimeSessionCreated = true; params.onTranscriptSources?.(transcriptSources); if (persistedDir) { await fs.mkdir(persistedDir, { recursive: true, mode: 448 }); await fs.chmod(persistedDir, 448).catch(() => void 0); } const prompt = buildRecallPrompt({ config: params.config, query: params.query, searchQuery: params.searchQuery }); const { messageChannel, messageProvider } = resolveRecallRunChannelContext({ api: params.api, agentId: params.agentId, sessionKey: parentSessionKey, sessionId: params.sessionId, messageProvider: params.messageProvider, channelId: params.channelId }); const embeddedTimeoutMs = params.config.timeoutMs + params.config.setupGraceTimeoutMs; const result = await params.api.runtime.agent.runEmbeddedAgent({ sessionId: subagentSessionId, sessionKey: subagentSessionKey, agentId: params.agentId, sessionTarget: { agentId: params.agentId, sessionId: subagentSessionId, sessionKey: subagentSessionKey, storePath }, messageChannel, messageProvider, sessionFile: runtimeSessionFile, workspaceDir, agentDir, config: params.runtimeConfig, prompt, provider: modelRef.provider, model: modelRef.model, lane: ACTIVE_MEMORY_RECALL_LANE, timeoutMs: embeddedTimeoutMs, runId: subagentSessionId, trigger: "manual", conversationRecall: params.conversationRecall, toolsAllow: [...params.config.toolsAllow], disableMessageTool: true, allowGatewaySubagentBinding: true, bootstrapContextMode: "lightweight", verboseLevel: "off", thinkLevel: params.config.thinking, fastMode: params.fastMode, reasoningLevel: "off", silentExpected: true, authProfileFailurePolicy: "local", cliBackendDispatch: "subscription-auth", cleanupBundleMcpOnRunEnd: true, abortSignal: params.abortSignal, onAgentToolResult: (event) => { const evidence = readMemoryToolResultEvidence({ ...event, toolsAllow: params.config.toolsAllow }); harnessHasUsableMemoryResult ||= evidence.hasUsableMemoryResult; harnessHasUnavailableMemorySearchResult ||= evidence.hasUnavailableMemorySearchResult; } }).finally(params.onEmbeddedRunSettled); resultStatus = result.meta.error ? "failed" : void 0; const agentMeta = result.meta.agentMeta; const activeSessionFile = normalizeOptionalString(agentMeta?.sessionFile); const activeSessionId = parseSqliteSessionFileMarker(activeSessionFile)?.sessionId ?? (activeSessionFile === subagentSessionKey ? normalizeOptionalString(agentMeta?.sessionId) : void 0); if (activeSessionId && activeSessionId !== subagentSessionId) transcriptSources.push({ ...runtimeSource, sessionId: activeSessionId }); params.onTranscriptSources?.(transcriptSources); if (params.abortSignal?.aborted) { const reason = params.abortSignal.reason; if (reason instanceof Error) throw reason; const abortErr = reason !== void 0 ? new Error("Operation aborted", { cause: reason }) : /* @__PURE__ */ new Error("Operation aborted"); abortErr.name = "AbortError"; throw abortErr; } const rawReply = (result.payloads ?? []).filter((payload) => payload.isError !== true).map((payload) => payload.text?.trim() ?? "").filter(Boolean).join("\n").trim(); if (artifactSessionFile) { await persistActiveMemoryTranscriptArtifact({ sources: transcriptSources, sessionFile: artifactSessionFile }); transcriptArtifactPersisted = true; } const transcriptState = await readMergedActiveMemoryTranscriptState({ sources: transcriptSources, toolsAllow: params.config.toolsAllow }); return { rawReply: rawReply || "NONE", resultStatus, transcriptPath: artifactSessionFile, searchDebug: transcriptState.searchDebug, hasUsableMemoryResult: transcriptState.hasUsableMemoryResult || harnessHasUsableMemoryResult, hasUnavailableMemorySearchResult: transcriptState.hasUnavailableMemorySearchResult || harnessHasUnavailableMemorySearchResult }; } catch (error) { if (params.abortSignal?.aborted) { const partialReply = await readPartialAssistantTextFromSources(transcriptSources); const transcriptState = await readMergedActiveMemoryTranscriptState({ sources: transcriptSources, toolsAllow: params.config.toolsAllow }); attachPartialTimeoutData(error, { rawReply: partialReply ?? void 0, resultStatus, searchDebug: transcriptState.searchDebug, hasUnavailableMemorySearchResult: transcriptState.hasUnavailableMemorySearchResult || harnessHasUnavailableMemorySearchResult, hasUsableMemoryResult: transcriptState.hasUsableMemoryResult || harnessHasUsableMemoryResult }); } if (!params.abortSignal?.aborted && isMissingRegisteredMemoryToolsError(error, params.config.toolsAllow)) { params.api.logger.debug?.(`active-memory: no configured memory tools available; skipping sub-agent`); return { rawReply: "NONE", resultStatus: "unavailable" }; } if (!params.abortSignal?.aborted) { const message = toSingleLineErrorMessage(error); params.api.logger.warn?.(`active-memory: memory sub-agent failed, skipping recall: ${message}`); return { rawReply: "NONE", resultStatus: "failed" }; } throw error; } finally { await cleanupRecallResources(); } } //#endregion export { runRecallSubagent as t };