UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

317 lines (316 loc) 11.7 kB
import { Bt as discriminatedUnion, Et as array, Rn as string, Zn as unknown, dn as literal, fn as looseObject, wc as NEVER, wn as number, yt as _enum } from "./schemas-zxit8y5H.js"; import { i as resolveWorkspaceStateIdentity } from "./workspace-state-identity-UVKTnO1Z.js"; import { o as registerPluginStateSyncSequencedJournalEntry, y as pluginStateEntriesInKeyRange } from "./plugin-state-store-C6hmUuuk.js"; import { createHash } from "node:crypto"; //#region src/memory-host-sdk/event-store.ts const MEMORY_HOST_EVENTS_PLUGIN_ID = "memory-core"; const MEMORY_HOST_EVENTS_NAMESPACE = "memory-host.events"; const MEMORY_HOST_EVENT_CURSORS_NAMESPACE = "memory-host.event-cursors"; const MAX_MEMORY_HOST_EVENTS = 1e4; const MAX_MEMORY_HOST_EVENT_CURSORS = 1e3; const MAX_MEMORY_HOST_EVENT_JSON_BYTES = 8192; const MAX_MEMORY_HOST_EVENT_ITEMS = 10; const MAX_MEMORY_HOST_EVENT_TEXT_BYTES = 2048; const MAX_MEMORY_HOST_EVENT_PATH_BYTES = 256; const WORKSPACE_HASH_BYTES = 24; const memoryHostFiniteNumberSchema = number().finite(); const memoryHostRecallResultSchema = looseObject({ path: string(), startLine: memoryHostFiniteNumberSchema, endLine: memoryHostFiniteNumberSchema, score: memoryHostFiniteNumberSchema }); const memoryHostSkippedRecallResultSchema = memoryHostRecallResultSchema.extend({ reason: literal("non-short-term-memory-path") }); const boundedRecallResultsSchema = array(unknown()).transform((values, context) => { const parsed = array(memoryHostRecallResultSchema).safeParse(values.slice(0, MAX_MEMORY_HOST_EVENT_ITEMS)); if (!parsed.success) { context.addIssue({ code: "custom", message: "invalid recall result" }); return NEVER; } return { items: parsed.data, truncated: values.length > MAX_MEMORY_HOST_EVENT_ITEMS }; }); const boundedSkippedRecallResultsSchema = array(unknown()).transform((values, context) => { const parsed = array(memoryHostSkippedRecallResultSchema).safeParse(values.slice(0, MAX_MEMORY_HOST_EVENT_ITEMS)); if (!parsed.success) { context.addIssue({ code: "custom", message: "invalid skipped recall result" }); return NEVER; } return { items: parsed.data, truncated: values.length > MAX_MEMORY_HOST_EVENT_ITEMS }; }); const memoryHostPromotionCandidateSchema = looseObject({ key: string(), path: string(), startLine: memoryHostFiniteNumberSchema, endLine: memoryHostFiniteNumberSchema, score: memoryHostFiniteNumberSchema, recallCount: memoryHostFiniteNumberSchema }); const boundedPromotionCandidatesSchema = array(unknown()).transform((values, context) => { const parsed = array(memoryHostPromotionCandidateSchema).safeParse(values.slice(0, MAX_MEMORY_HOST_EVENT_ITEMS)); if (!parsed.success) { context.addIssue({ code: "custom", message: "invalid promotion candidate" }); return NEVER; } return { items: parsed.data, truncated: values.length > MAX_MEMORY_HOST_EVENT_ITEMS }; }); const memoryHostEventRecordSchema = discriminatedUnion("type", [ looseObject({ type: literal("memory.recall.recorded"), timestamp: string(), storageTruncated: unknown().optional(), query: string(), resultCount: memoryHostFiniteNumberSchema, results: boundedRecallResultsSchema }), looseObject({ type: literal("memory.recall.skipped"), timestamp: string(), storageTruncated: unknown().optional(), query: string(), reason: literal("non-short-term-memory-path"), eligibleResultCount: memoryHostFiniteNumberSchema, skippedResultCount: memoryHostFiniteNumberSchema, results: boundedSkippedRecallResultsSchema }), looseObject({ type: literal("memory.promotion.applied"), timestamp: string(), storageTruncated: unknown().optional(), memoryPath: string(), applied: memoryHostFiniteNumberSchema, candidates: boundedPromotionCandidatesSchema }), looseObject({ type: literal("memory.dream.completed"), timestamp: string(), storageTruncated: unknown().optional(), phase: _enum([ "light", "deep", "rem" ]), outcome: _enum(["completed", "failed"]).optional(), error: string().optional(), inlinePath: string().optional(), reportPath: string().optional(), lineCount: memoryHostFiniteNumberSchema, storageMode: _enum([ "inline", "separate", "both" ]) }) ]); function normalizeMemoryHostWorkspaceKey(workspaceDir) { const resolved = resolveWorkspaceStateIdentity(workspaceDir).workspacePath.replace(/\\/g, "/"); return process.platform === "win32" ? resolved.toLowerCase() : resolved; } function memoryHostWorkspacePrefix(workspaceDir) { return createHash("sha256").update(normalizeMemoryHostWorkspaceKey(workspaceDir)).digest("hex").slice(0, WORKSPACE_HASH_BYTES); } function eventKeyPrefix(workspaceDir) { return `${memoryHostWorkspacePrefix(workspaceDir)}:event:`; } function eventKeyRangeEnd(workspaceDir) { return `${memoryHostWorkspacePrefix(workspaceDir)}:event;`; } function memoryHostEventStorageKey(workspaceDir, sequence) { if (!Number.isSafeInteger(sequence)) throw new Error("Memory host event sequence must be a safe integer"); return `${eventKeyPrefix(workspaceDir)}1:${sequence.toString().padStart(16, "0")}`; } function cursorKey(workspaceDir) { return `${memoryHostWorkspacePrefix(workspaceDir)}:cursor`; } function truncateUtf8(value, maxBytes) { if (Buffer.byteLength(value, "utf8") <= maxBytes) return { value, truncated: false }; let low = 0; let high = value.length; while (low < high) { const middle = Math.ceil((low + high) / 2); if (Buffer.byteLength(value.slice(0, middle), "utf8") <= maxBytes - 3) low = middle; else high = middle - 1; } const end = low > 0 && /[\uD800-\uDBFF]/u.test(value.charAt(low - 1)) ? low - 1 : low; return { value: `${value.slice(0, end)}…`, truncated: true }; } /** Validate and bound one diagnostic event before storing it in plugin state. */ function normalizeMemoryHostEventRecordForStorage(value) { const parsed = memoryHostEventRecordSchema.safeParse(value); if (!parsed.success) return null; const event = parsed.data; const timestamp = truncateUtf8(event.timestamp, 128); let truncated = timestamp.truncated || event.storageTruncated === true; if (event.type === "memory.recall.recorded" || event.type === "memory.recall.skipped") { const query = truncateUtf8(event.query, MAX_MEMORY_HOST_EVENT_TEXT_BYTES); truncated ||= query.truncated || event.results.truncated; const results = event.results.items.map((result) => { const resultPath = truncateUtf8(result.path, MAX_MEMORY_HOST_EVENT_PATH_BYTES); truncated ||= resultPath.truncated; return { path: resultPath.value, startLine: result.startLine, endLine: result.endLine, score: result.score, ...event.type === "memory.recall.skipped" ? { reason: "non-short-term-memory-path" } : {} }; }); const normalized = event.type === "memory.recall.recorded" ? { type: "memory.recall.recorded", timestamp: timestamp.value, query: query.value, resultCount: event.resultCount, results: results.map((result) => ({ path: result.path, startLine: result.startLine, endLine: result.endLine, score: result.score })), ...truncated ? { storageTruncated: true } : {} } : { type: "memory.recall.skipped", timestamp: timestamp.value, query: query.value, reason: "non-short-term-memory-path", eligibleResultCount: event.eligibleResultCount, skippedResultCount: event.skippedResultCount, results: results.map((result) => ({ path: result.path, startLine: result.startLine, endLine: result.endLine, score: result.score, reason: "non-short-term-memory-path" })), ...truncated ? { storageTruncated: true } : {} }; return Buffer.byteLength(JSON.stringify(normalized), "utf8") <= MAX_MEMORY_HOST_EVENT_JSON_BYTES ? normalized : { ...normalized, results: [], storageTruncated: true }; } if (event.type === "memory.promotion.applied") { const memoryPath = truncateUtf8(event.memoryPath, MAX_MEMORY_HOST_EVENT_PATH_BYTES); truncated ||= memoryPath.truncated || event.candidates.truncated; const candidates = event.candidates.items.map((candidate) => { const key = truncateUtf8(candidate.key, MAX_MEMORY_HOST_EVENT_PATH_BYTES); const candidatePath = truncateUtf8(candidate.path, MAX_MEMORY_HOST_EVENT_PATH_BYTES); truncated ||= key.truncated || candidatePath.truncated; return { key: key.value, path: candidatePath.value, startLine: candidate.startLine, endLine: candidate.endLine, score: candidate.score, recallCount: candidate.recallCount }; }); const normalized = { type: "memory.promotion.applied", timestamp: timestamp.value, memoryPath: memoryPath.value, applied: event.applied, candidates, ...truncated ? { storageTruncated: true } : {} }; return Buffer.byteLength(JSON.stringify(normalized), "utf8") <= MAX_MEMORY_HOST_EVENT_JSON_BYTES ? normalized : { ...normalized, candidates: [], storageTruncated: true }; } if (event.type === "memory.dream.completed") { const error = event.error ? truncateUtf8(event.error, MAX_MEMORY_HOST_EVENT_TEXT_BYTES) : void 0; const inlinePath = event.inlinePath ? truncateUtf8(event.inlinePath, MAX_MEMORY_HOST_EVENT_PATH_BYTES) : void 0; const reportPath = event.reportPath ? truncateUtf8(event.reportPath, MAX_MEMORY_HOST_EVENT_PATH_BYTES) : void 0; truncated ||= Boolean(error?.truncated || inlinePath?.truncated || reportPath?.truncated); return { type: event.type, timestamp: timestamp.value, phase: event.phase, ...event.outcome ? { outcome: event.outcome } : {}, ...error ? { error: error.value } : {}, ...inlinePath ? { inlinePath: inlinePath.value } : {}, ...reportPath ? { reportPath: reportPath.value } : {}, lineCount: event.lineCount, storageMode: event.storageMode, ...truncated ? { storageTruncated: true } : {} }; } return null; } function registerMemoryHostEvent(params) { const event = normalizeMemoryHostEventRecordForStorage(params.event); if (!event) throw new TypeError("Memory host event is invalid"); const initialSequence = Math.max(0, listStoredMemoryHostEvents({ workspaceDir: params.workspaceDir, limit: 1, ...params.env ? { env: params.env } : {} }).at(-1)?.value.sequence ?? 0); const recordedAt = Date.now(); registerPluginStateSyncSequencedJournalEntry({ pluginId: MEMORY_HOST_EVENTS_PLUGIN_ID, cursorOptions: { namespace: MEMORY_HOST_EVENT_CURSORS_NAMESPACE, maxEntries: MAX_MEMORY_HOST_EVENT_CURSORS, ...params.env ? { env: params.env } : {} }, cursorKey: cursorKey(params.workspaceDir), journalOptions: { namespace: MEMORY_HOST_EVENTS_NAMESPACE, maxEntries: MAX_MEMORY_HOST_EVENTS, ...params.env ? { env: params.env } : {} }, initialSequence, journalKey: (sequence) => memoryHostEventStorageKey(params.workspaceDir, sequence), journalValue: (sequence) => ({ kind: "event", event, recordedAt, sequence }) }); } function listStoredMemoryHostEvents(params) { const limit = Number.isFinite(params.limit) ? Math.max(1, Math.min(MAX_MEMORY_HOST_EVENTS, Math.floor(params.limit))) : MAX_MEMORY_HOST_EVENTS; return pluginStateEntriesInKeyRange({ pluginId: MEMORY_HOST_EVENTS_PLUGIN_ID, namespace: MEMORY_HOST_EVENTS_NAMESPACE, keyStartInclusive: eventKeyPrefix(params.workspaceDir), keyEndExclusive: eventKeyRangeEnd(params.workspaceDir), limit, order: "desc", ...params.env ? { env: params.env } : {} }).flatMap((entry) => { const value = entry.value; return value.kind === "event" ? [{ ...entry, value }] : []; }).toReversed(); } //#endregion export { normalizeMemoryHostEventRecordForStorage as n, registerMemoryHostEvent as r, listStoredMemoryHostEvents as t };