@alfe.ai/openclaw-memory-cloud 0.0.37 → 0.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -60
- package/dist/index.js +2 -396
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.d.cts +59 -0
- package/dist/plugin.d.cts.map +1 -0
- package/dist/plugin.d.ts +59 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +2 -0
- package/dist/plugin2.cjs +1107 -0
- package/dist/plugin2.d.cts +2 -0
- package/dist/plugin2.d.ts +2 -0
- package/dist/plugin2.js +1104 -0
- package/dist/plugin2.js.map +1 -0
- package/openclaw.plugin.json +6 -5
- package/package.json +27 -7
- package/.turbo/turbo-build.log +0 -4
- package/CHANGELOG.md +0 -309
- package/dist/auto-capture.d.ts +0 -45
- package/dist/auto-capture.d.ts.map +0 -1
- package/dist/auto-capture.js +0 -101
- package/dist/auto-capture.js.map +0 -1
- package/dist/auto-recall.d.ts +0 -22
- package/dist/auto-recall.d.ts.map +0 -1
- package/dist/auto-recall.js +0 -63
- package/dist/auto-recall.js.map +0 -1
- package/dist/formatter.d.ts +0 -7
- package/dist/formatter.d.ts.map +0 -1
- package/dist/formatter.js +0 -27
- package/dist/formatter.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/ingest-epoch.d.ts +0 -38
- package/dist/ingest-epoch.d.ts.map +0 -1
- package/dist/ingest-epoch.js +0 -66
- package/dist/ingest-epoch.js.map +0 -1
- package/dist/session-backfill.d.ts +0 -54
- package/dist/session-backfill.d.ts.map +0 -1
- package/dist/session-backfill.js +0 -192
- package/dist/session-backfill.js.map +0 -1
- package/dist/types.d.ts +0 -113
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/src/__tests__/auto-capture.test.ts +0 -115
- package/src/__tests__/ingest-epoch.test.ts +0 -67
- package/src/__tests__/session-backfill.test.ts +0 -289
- package/src/auto-capture.ts +0 -108
- package/src/auto-recall.ts +0 -66
- package/src/formatter.ts +0 -30
- package/src/index.ts +0 -464
- package/src/ingest-epoch.ts +0 -78
- package/src/session-backfill.ts +0 -220
- package/src/types.ts +0 -93
- package/sst-env.d.ts +0 -10
- package/tsconfig.json +0 -20
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin2.js","names":["noFollowFlag","hasControlCharacters","isRecord","noFollowFlag"],"sources":["../src/auto-capture.ts","../src/file-boundary.ts","../src/boundary.ts","../src/formatter.ts","../src/auto-recall.ts","../src/ingest-epoch.ts","../src/session-backfill.ts","../src/runtime.ts","../src/plugin.ts"],"sourcesContent":["import type { MemoryApi, MemoryCloudConfig, MemoryMessage, SessionMetadata } from \"./types.js\";\n\ninterface CaptureLogger {\n debug(message: string, context?: Record<string, unknown>): void;\n warn(message: string, context?: Record<string, unknown>): void;\n}\n\ninterface SessionCaptureState {\n messages: MemoryMessage[];\n metadata: SessionMetadata;\n timer: ReturnType<typeof setTimeout> | null;\n flushPromise: Promise<void> | null;\n}\n\n/**\n * Debounces and flushes live conversation capture independently per session.\n *\n * OpenClaw can interleave hooks from multiple sessions in one daemon process,\n * so a process-wide current session or queue is not safe. Every public method\n * therefore requires the canonical hook context session key.\n */\nexport class AutoCapture {\n private static readonly MAX_QUEUE_SIZE = 500;\n private readonly sessions = new Map<string, SessionCaptureState>();\n /** Global per boot so evicting one session cannot reset its index sequence. */\n private messageIndex = 0;\n\n constructor(\n private readonly client: MemoryApi,\n private readonly config: MemoryCloudConfig,\n private readonly logger: CaptureLogger,\n private readonly ingestEpoch: number,\n ) {}\n\n trackMessage(\n sessionKey: string,\n role: \"user\" | \"assistant\",\n content: string,\n metadata?: SessionMetadata,\n ): void {\n if (!this.config.autoCapture || content.length === 0) return;\n\n const state = this.getSession(sessionKey);\n if (metadata) state.metadata = mergeMetadata(state.metadata, metadata);\n if (state.messages.length >= AutoCapture.MAX_QUEUE_SIZE) {\n state.messages.shift();\n this.logger.warn(\"Memory capture queue full; dropped oldest message\", {\n queueSize: AutoCapture.MAX_QUEUE_SIZE,\n });\n }\n\n state.messages.push({\n role,\n content: content.slice(0, this.config.captureMaxChars),\n index: this.messageIndex++,\n timestamp: new Date().toISOString(),\n });\n this.resetTimer(sessionKey, state);\n }\n\n async flush(sessionKey: string): Promise<void> {\n const state = this.sessions.get(sessionKey);\n if (!state || state.messages.length === 0) return;\n\n if (state.flushPromise) {\n await state.flushPromise;\n return;\n }\n\n state.flushPromise = this.flushSerially(sessionKey, state);\n try {\n await state.flushPromise;\n } finally {\n state.flushPromise = null;\n if (state.messages.length === 0) this.deleteSession(sessionKey, state);\n }\n }\n\n async onAgentEnd(sessionKey: string): Promise<void> {\n const state = this.sessions.get(sessionKey);\n if (!state) return;\n this.clearTimer(state);\n await this.flush(sessionKey);\n }\n\n destroy(): void {\n for (const state of this.sessions.values()) this.clearTimer(state);\n this.sessions.clear();\n }\n\n private getSession(sessionKey: string): SessionCaptureState {\n const existing = this.sessions.get(sessionKey);\n if (existing) return existing;\n const state: SessionCaptureState = {\n messages: [],\n metadata: {},\n timer: null,\n flushPromise: null,\n };\n this.sessions.set(sessionKey, state);\n return state;\n }\n\n private async flushSerially(sessionKey: string, state: SessionCaptureState): Promise<void> {\n while (state.messages.length > 0) {\n const batch = state.messages.splice(0, state.messages.length);\n if (batch.length === 0) return;\n\n this.logger.debug(\"Flushing memory capture batch\", { messageCount: batch.length });\n try {\n await this.client.memoryIngest(sessionKey, batch, state.metadata, this.ingestEpoch);\n } catch {\n state.messages.unshift(...batch);\n this.logger.warn(\"Memory capture flush failed; retained batch for retry\", {\n messageCount: batch.length,\n });\n return;\n }\n }\n }\n\n private resetTimer(sessionKey: string, state: SessionCaptureState): void {\n this.clearTimer(state);\n state.timer = setTimeout(() => {\n state.timer = null;\n void this.flush(sessionKey);\n }, this.config.idleFlushSeconds * 1_000);\n state.timer.unref();\n }\n\n private clearTimer(state: SessionCaptureState): void {\n if (state.timer) clearTimeout(state.timer);\n state.timer = null;\n }\n\n private deleteSession(sessionKey: string, state: SessionCaptureState): void {\n this.clearTimer(state);\n if (this.sessions.get(sessionKey) === state) this.sessions.delete(sessionKey);\n }\n}\n\nfunction mergeMetadata(current: SessionMetadata, next: SessionMetadata): SessionMetadata {\n return {\n ...(current.channelId ? { channelId: current.channelId } : {}),\n ...(current.userId ? { userId: current.userId } : {}),\n ...(current.userName ? { userName: current.userName } : {}),\n ...(next.channelId ? { channelId: next.channelId } : {}),\n ...(next.userId ? { userId: next.userId } : {}),\n ...(next.userName ? { userName: next.userName } : {}),\n };\n}\n","import { constants } from \"node:fs\";\nimport { lstat, open, realpath } from \"node:fs/promises\";\nimport { isAbsolute, join, relative, resolve, sep } from \"node:path\";\n\nexport const MAX_LEARN_TEXT_CHARS = 100_000;\nexport const MAX_LEARN_FILE_BYTES = 400 * 1024;\nexport const MAX_WORKSPACE_PATH_CHARS = 1_024;\n\nexport class WorkspaceFileError extends Error {\n override readonly name = \"WorkspaceFileError\";\n}\n\nexport async function readWorkspaceText(\n workspacePath: string,\n relativePath: string,\n): Promise<string> {\n validateRelativePath(relativePath);\n const workspaceReal = await realpath(workspacePath);\n const requestedPath = resolve(workspaceReal, relativePath);\n let componentPath = workspaceReal;\n for (const component of relativePath.split(\"/\")) {\n componentPath = join(componentPath, component);\n const componentStat = await lstat(componentPath);\n if (componentStat.isSymbolicLink()) {\n throw new WorkspaceFileError(\"Workspace path must not contain symlinks\");\n }\n }\n const requestedStat = await lstat(requestedPath);\n if (requestedStat.isSymbolicLink() || !requestedStat.isFile()) {\n throw new WorkspaceFileError(\"Workspace path must identify a regular file\");\n }\n\n const fileReal = await realpath(requestedPath);\n if (!isInside(workspaceReal, fileReal)) {\n throw new WorkspaceFileError(\"Workspace path escapes the configured workspace\");\n }\n\n let handle: Awaited<ReturnType<typeof open>> | undefined;\n try {\n handle = await open(fileReal, constants.O_RDONLY | noFollowFlag());\n const opened = await handle.stat();\n if (!opened.isFile() || opened.size > MAX_LEARN_FILE_BYTES) {\n throw new WorkspaceFileError(\"Workspace file exceeds the 400 KiB read limit\");\n }\n const text = await handle.readFile(\"utf8\");\n if (text.length > MAX_LEARN_TEXT_CHARS) {\n throw new WorkspaceFileError(\"Workspace file exceeds the 100,000 character learn limit\");\n }\n return text;\n } finally {\n await handle?.close();\n }\n}\n\nexport function validateRelativePath(value: unknown): asserts value is string {\n if (typeof value !== \"string\" || value.length === 0 || value.length > MAX_WORKSPACE_PATH_CHARS) {\n throw new WorkspaceFileError(\"path must be a non-empty workspace-relative path\");\n }\n if (\n isAbsolute(value) ||\n value.includes(\"\\\\\") ||\n value.includes(\"//\") ||\n hasControlCharacters(value) ||\n value.split(\"/\").some((part) => part === \"\" || part === \".\" || part === \"..\")\n ) {\n throw new WorkspaceFileError(\"path must remain inside the configured workspace\");\n }\n}\n\nfunction hasControlCharacters(value: string): boolean {\n for (const character of value) {\n const code = character.charCodeAt(0);\n if (code < 32 || code === 127) return true;\n }\n return false;\n}\n\nfunction isInside(root: string, candidate: string): boolean {\n const rel = relative(root, candidate);\n return rel !== \"..\" && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);\n}\n\nfunction noFollowFlag(): number {\n return \"O_NOFOLLOW\" in constants ? constants.O_NOFOLLOW : 0;\n}\n\nexport function workspaceFilePath(workspacePath: string, relativePath: string): string {\n validateRelativePath(relativePath);\n return join(workspacePath, relativePath);\n}\n","import { MAX_LEARN_TEXT_CHARS, validateRelativePath } from \"./file-boundary.js\";\nimport type { MemoryCloudConfig, MemoryTag } from \"./types.js\";\n\nexport const MAX_QUERY_CHARS = 4_000;\nexport const MAX_SEARCH_RESULTS = 50;\nexport const MAX_TOPIC_CHARS = 128;\nexport const MAX_FILTER_CHARS = 256;\nexport const MAX_STORE_TEXT_CHARS = 5_000;\nexport const MAX_SOURCE_CHARS = 512;\nexport const MAX_ENTITY_CHARS = 512;\nexport const MAX_MEMORY_ID_CHARS = 512;\nexport const MAX_CONTEXT_XML_CHARS = 16_384;\n\nexport const DEFAULT_CONFIG: MemoryCloudConfig = {\n autoCapture: true,\n autoRecall: true,\n captureMaxChars: 500,\n idleFlushSeconds: 60,\n backfillSessions: true,\n backfillMaxSessions: 50,\n};\n\nconst TAGS = new Set<MemoryTag>([\"fact\", \"decision\", \"preference\", \"event\", \"discovery\"]);\n\nexport class MemoryInputError extends Error {\n override readonly name = \"MemoryInputError\";\n}\n\nexport function parseMemoryConfig(value: unknown): MemoryCloudConfig {\n if (value === undefined) return { ...DEFAULT_CONFIG };\n const input = record(value, \"Memory plugin config\");\n rejectUnknown(input, [\"autoCapture\", \"autoRecall\", \"captureMaxChars\", \"idleFlushSeconds\", \"backfillSessions\", \"backfillMaxSessions\"]);\n return {\n autoCapture: optionalBoolean(input.autoCapture, DEFAULT_CONFIG.autoCapture, \"autoCapture\"),\n autoRecall: optionalBoolean(input.autoRecall, DEFAULT_CONFIG.autoRecall, \"autoRecall\"),\n captureMaxChars: optionalInteger(input.captureMaxChars, DEFAULT_CONFIG.captureMaxChars, \"captureMaxChars\", 100, 10_000),\n idleFlushSeconds: optionalInteger(input.idleFlushSeconds, DEFAULT_CONFIG.idleFlushSeconds, \"idleFlushSeconds\", 10, 600),\n backfillSessions: optionalBoolean(input.backfillSessions, DEFAULT_CONFIG.backfillSessions, \"backfillSessions\"),\n backfillMaxSessions: optionalInteger(input.backfillMaxSessions, DEFAULT_CONFIG.backfillMaxSessions, \"backfillMaxSessions\", 1, 1_000),\n };\n}\n\nexport function parseSearchInput(value: unknown): {\n query: string;\n limit: number;\n topic?: string;\n subtopic?: string;\n tag?: MemoryTag;\n} {\n const input = record(value, \"Memory search parameters\");\n rejectUnknown(input, [\"query\", \"limit\", \"topic\", \"subtopic\", \"tag\"]);\n return compact({\n query: string(input.query, \"query\", 1, MAX_QUERY_CHARS, true),\n limit: input.limit === undefined ? 10 : integer(input.limit, \"limit\", 1, MAX_SEARCH_RESULTS),\n topic: optionalString(input.topic, \"topic\", MAX_FILTER_CHARS),\n subtopic: optionalString(input.subtopic, \"subtopic\", MAX_FILTER_CHARS),\n tag: optionalTag(input.tag),\n });\n}\n\nexport function parseStoreInput(value: unknown): {\n text: string;\n topic?: string;\n subtopic?: string;\n tag?: MemoryTag;\n importance?: number;\n} {\n const input = record(value, \"Memory store parameters\");\n rejectUnknown(input, [\"text\", \"topic\", \"subtopic\", \"tag\", \"importance\"]);\n return compact({\n text: string(input.text, \"text\", 1, MAX_STORE_TEXT_CHARS, true),\n topic: optionalString(input.topic, \"topic\", MAX_TOPIC_CHARS),\n subtopic: optionalString(input.subtopic, \"subtopic\", MAX_TOPIC_CHARS),\n tag: optionalTag(input.tag),\n importance: input.importance === undefined ? undefined : finite(input.importance, \"importance\", 0, 1),\n });\n}\n\nexport function parseLearnInput(value: unknown): { content?: string; path?: string; source?: string } {\n const input = record(value, \"Memory learn parameters\");\n rejectUnknown(input, [\"content\", \"path\", \"source\"]);\n const content = optionalString(input.content, \"content\", MAX_LEARN_TEXT_CHARS, true);\n const path = optionalString(input.path, \"path\", 1_024);\n if ((content === undefined) === (path === undefined)) {\n throw new MemoryInputError(\"Provide exactly one of content or path\");\n }\n if (path !== undefined) {\n try { validateRelativePath(path); } catch { throw new MemoryInputError(\"path must remain inside the configured workspace\"); }\n }\n return compact({ content, path, source: optionalString(input.source, \"source\", MAX_SOURCE_CHARS) });\n}\n\nexport function parseForgetInput(value: unknown): { query: string; confirmMemoryIds?: string[] } {\n const input = record(value, \"Memory forget parameters\");\n rejectUnknown(input, [\"query\", \"confirmMemoryIds\"]);\n let confirmMemoryIds: string[] | undefined;\n if (input.confirmMemoryIds !== undefined) {\n if (!Array.isArray(input.confirmMemoryIds) || input.confirmMemoryIds.length < 1 || input.confirmMemoryIds.length > 5) {\n throw new MemoryInputError(\"confirmMemoryIds must contain between 1 and 5 memory IDs\");\n }\n confirmMemoryIds = input.confirmMemoryIds.map((id) => string(id, \"memory ID\", 1, MAX_MEMORY_ID_CHARS));\n if (new Set(confirmMemoryIds).size !== confirmMemoryIds.length) {\n throw new MemoryInputError(\"confirmMemoryIds must not contain duplicates\");\n }\n }\n return compact({ query: string(input.query, \"query\", 1, MAX_QUERY_CHARS, true), confirmMemoryIds });\n}\n\nexport function parseEntityInput(value: unknown): { entity: string } {\n const input = record(value, \"Memory graph parameters\");\n rejectUnknown(input, [\"entity\"]);\n return { entity: string(input.entity, \"entity\", 1, MAX_ENTITY_CHARS, true) };\n}\n\nexport function parseEmptyInput(value: unknown): Record<string, never> {\n const input = record(value, \"Tool parameters\");\n rejectUnknown(input, []);\n return {};\n}\n\nexport function safeCount(value: unknown): number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0 ? value : 0;\n}\n\nexport function safeText(value: unknown, maxChars: number): string {\n return typeof value === \"string\" ? value.slice(0, maxChars) : \"\";\n}\n\nexport function classifyToolFailure(error: unknown, action: string): string {\n if (error instanceof MemoryInputError) return error.message;\n if (error instanceof Error && error.name === \"WorkspaceFileError\") return error.message;\n return `${action} failed. Retry, and inspect agent diagnostics if the failure persists.`;\n}\n\nfunction record(value: unknown, label: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) throw new MemoryInputError(`${label} must be an object`);\n return value as Record<string, unknown>;\n}\n\nfunction rejectUnknown(input: Record<string, unknown>, allowed: string[]): void {\n const unknown = Object.keys(input).find((key) => !allowed.includes(key));\n if (unknown) throw new MemoryInputError(`Unknown parameter: ${unknown}`);\n}\n\nfunction string(value: unknown, label: string, min: number, max: number, trim = false): string {\n if (typeof value !== \"string\") throw new MemoryInputError(`${label} must be a string`);\n const result = trim ? value.trim() : value;\n if (result.length < min || result.length > max || hasControlCharacters(result, true)) {\n throw new MemoryInputError(`${label} must contain ${String(min)}-${String(max)} safe characters`);\n }\n return result;\n}\n\nexport function hasControlCharacters(value: string, allowTextWhitespace = false): boolean {\n for (const character of value) {\n const code = character.charCodeAt(0);\n if (code === 127) return true;\n if (code < 32 && !(allowTextWhitespace && (code === 9 || code === 10 || code === 13))) return true;\n }\n return false;\n}\n\nfunction optionalString(value: unknown, label: string, max: number, trim = false): string | undefined {\n return value === undefined ? undefined : string(value, label, 1, max, trim);\n}\n\nfunction integer(value: unknown, label: string, min: number, max: number): number {\n if (!Number.isInteger(value) || (value as number) < min || (value as number) > max) {\n throw new MemoryInputError(`${label} must be an integer from ${String(min)} to ${String(max)}`);\n }\n return value as number;\n}\n\nfunction optionalInteger(value: unknown, fallback: number, label: string, min: number, max: number): number {\n return value === undefined ? fallback : integer(value, label, min, max);\n}\n\nfunction finite(value: unknown, label: string, min: number, max: number): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < min || value > max) {\n throw new MemoryInputError(`${label} must be a finite number from ${String(min)} to ${String(max)}`);\n }\n return value;\n}\n\nfunction optionalBoolean(value: unknown, fallback: boolean, label: string): boolean {\n if (value === undefined) return fallback;\n if (typeof value !== \"boolean\") throw new MemoryInputError(`${label} must be a boolean`);\n return value;\n}\n\nfunction optionalTag(value: unknown): MemoryTag | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\" || !TAGS.has(value as MemoryTag)) {\n throw new MemoryInputError(\"tag must be fact, decision, preference, event, or discovery\");\n }\n return value as MemoryTag;\n}\n\nfunction compact<T extends Record<string, unknown>>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as T;\n}\n","import { MAX_CONTEXT_XML_CHARS, safeText } from \"./boundary.js\";\n\nconst MAX_FACTS = 50;\nconst MAX_MEMORIES = 50;\nconst MAX_FIELD_CHARS = 512;\nconst MAX_MEMORY_TEXT_CHARS = 2_000;\n\nexport interface SafeSearchResults {\n facts: { subject: string; predicate: string; object: string; since: string; confidence: number }[];\n memories: { id: string; text: string; topic: string; subtopic: string; tag: string; importance: number; timestamp: number; score: number }[];\n truncated: boolean;\n}\n\nexport function normalizeSearchResults(value: unknown): SafeSearchResults {\n const input = isRecord(value) ? value : {};\n const rawFacts = Array.isArray(input.facts) ? input.facts : [];\n const rawMemories = Array.isArray(input.memories) ? input.memories : [];\n return {\n facts: rawFacts.slice(0, MAX_FACTS).filter(isRecord).map((fact) => ({\n subject: safeText(fact.subject, MAX_FIELD_CHARS),\n predicate: safeText(fact.predicate, MAX_FIELD_CHARS),\n object: safeText(fact.object, MAX_MEMORY_TEXT_CHARS),\n since: safeText(fact.since, 64),\n confidence: safeFinite(fact.confidence),\n })),\n memories: rawMemories.slice(0, MAX_MEMORIES).filter(isRecord).map((memory) => ({\n id: safeText(memory.id, MAX_FIELD_CHARS),\n text: safeText(memory.text, MAX_MEMORY_TEXT_CHARS),\n topic: safeText(memory.topic, MAX_FIELD_CHARS),\n subtopic: safeText(memory.subtopic, MAX_FIELD_CHARS),\n tag: safeText(memory.tag, 64),\n importance: safeFinite(memory.importance),\n timestamp: safeFinite(memory.timestamp),\n score: safeFinite(memory.score),\n })),\n truncated: rawFacts.length > MAX_FACTS || rawMemories.length > MAX_MEMORIES,\n };\n}\n\n/** Format trusted, locally-normalized search results for tool display. */\nexport function formatSearchResults(results: unknown): SafeSearchResults {\n return normalizeSearchResults(results);\n}\n\n/** Build prompt XML locally; the service-provided formatted field is ignored. */\nexport function formatMemoryContext(value: unknown): string | undefined {\n const input = isRecord(value) ? value : {};\n const facts = Array.isArray(input.facts) ? input.facts.slice(0, 20).filter(isRecord) : [];\n const memories = Array.isArray(input.memories) ? input.memories.slice(0, 5).filter(isRecord) : [];\n if (facts.length === 0 && memories.length === 0) return undefined;\n\n const lines: string[] = [\"<relevant-memories>\"];\n if (facts.length > 0) {\n lines.push(\"Known facts:\");\n for (const fact of facts) {\n lines.push(`- ${escapeXml(safeText(fact.subject, 256))} ${escapeXml(safeText(fact.predicate, 256))} ${escapeXml(safeText(fact.object, 1_000))} (since ${escapeXml(safeText(fact.since, 64))})`);\n }\n }\n if (memories.length > 0) {\n lines.push(\"Related conversations:\");\n for (const memory of memories) {\n lines.push(`- [${escapeXml(safeText(memory.topic, 256))}/${escapeXml(safeText(memory.subtopic, 256))}] ${escapeXml(safeText(memory.text, 2_000))}`);\n }\n }\n lines.push(\"</relevant-memories>\");\n const xml = lines.join(\"\\n\");\n return xml.length <= MAX_CONTEXT_XML_CHARS ? xml : undefined;\n}\n\nexport function escapeXml(value: string): string {\n return value\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\")\n .replaceAll(\"'\", \"'\");\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction safeFinite(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n","import type { MemoryApi, MemoryCloudConfig } from \"./types.js\";\nimport { formatMemoryContext } from \"./formatter.js\";\n\nconst RECALL_TIMEOUT_MS = 5_000;\n\n/**\n * Auto-recall loads tiered memory context at session start\n * and formats it for injection into the agent's prompt.\n *\n * Called from the before_agent_start hook.\n */\nexport class AutoRecall {\n constructor(\n private readonly client: MemoryApi,\n private readonly config: MemoryCloudConfig,\n private readonly logger: { debug: (msg: string, ctx?: Record<string, unknown>) => void; warn: (msg: string, ctx?: Record<string, unknown>) => void },\n ) {}\n\n /**\n * Load memory context and return formatted XML for prompt injection.\n * Returns prependContext string for the before_agent_start hook result.\n */\n async loadForPrompt(userMessage: string): Promise<string | undefined> {\n if (!this.config.autoRecall) return undefined;\n\n try {\n // Detect topic from user message (first significant word)\n const topicHint = extractTopicHint(userMessage);\n\n // Load L1 (facts) + L2 (topic context) in one call\n const tier = topicHint ? 2 : 1;\n const context = await withTimeout(\n this.client.memoryLoadContext(tier, topicHint),\n RECALL_TIMEOUT_MS,\n );\n const formatted = formatMemoryContext(context);\n\n if (!formatted) {\n this.logger.debug(\"No relevant memories found for prompt\");\n return undefined;\n }\n\n this.logger.debug(\"Loaded memory context for prompt\", {\n tier,\n topicHint,\n factCount: Array.isArray(context.facts) ? context.facts.length : 0,\n memoryCount: Array.isArray(context.memories) ? context.memories.length : 0,\n tokenEstimate: typeof context.tokenEstimate === \"number\" ? context.tokenEstimate : 0,\n });\n\n return formatted;\n } catch {\n this.logger.warn(\"Failed to load bounded memory context\");\n return undefined;\n }\n }\n}\n\n/**\n * Simple topic hint extraction from user message.\n * Returns the first capitalized word or proper noun as a potential topic.\n */\nexport function extractTopicHint(message: string): string | undefined {\n // Look for proper nouns (capitalized words not at sentence start)\n const words = message.split(/\\s+/);\n for (let i = 1; i < words.length; i++) {\n const word = words[i];\n if (word && word.length > 2 && /^[A-Z]/.test(word) && !/^(The|And|But|For|With|This|That|What|How|Why|When|Where)$/.test(word)) {\n return word.toLowerCase();\n }\n }\n return undefined;\n}\n\nasync function withTimeout<T>(operation: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => { reject(new Error(\"Memory recall timed out\")); }, timeoutMs);\n timer.unref();\n });\n try {\n return await Promise.race([operation, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n","/** Strictly monotonic, crash-safe per-boot ingest epoch persistence. */\n\nimport { randomUUID } from \"node:crypto\";\nimport {\n chmodSync,\n closeSync,\n constants,\n fstatSync,\n fsyncSync,\n lstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\n\nconst DEFAULT_EPOCH_FILE = join(homedir(), \".alfe\", \"memory\", \"ingest-epoch.json\");\nconst MAX_STATE_BYTES = 128;\n\nexport interface ResolveIngestEpochOptions {\n epochFile?: string;\n now?: number;\n}\n\nexport function resolveIngestEpoch(opts: ResolveIngestEpochOptions = {}): number {\n const epochFile = opts.epochFile ?? DEFAULT_EPOCH_FILE;\n const now = opts.now ?? Date.now();\n if (!Number.isSafeInteger(now) || now < 0) {\n throw new Error(\"Memory ingest epoch clock value is invalid\");\n }\n\n const stateDir = dirname(epochFile);\n mkdirSync(stateDir, { recursive: true, mode: 0o700 });\n const directoryStat = lstatSync(stateDir);\n if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) {\n throw new Error(\"Memory ingest epoch directory is not a secure directory\");\n }\n\n const last = readLastEpoch(epochFile);\n if (last === Number.MAX_SAFE_INTEGER) {\n throw new Error(\"Memory ingest epoch has reached its safe integer limit\");\n }\n const epoch = Math.max(now, last + 1);\n persistEpoch(epochFile, epoch);\n return epoch;\n}\n\nfunction readLastEpoch(epochFile: string): number {\n try {\n const target = lstatSync(epochFile);\n if (target.isSymbolicLink() || !target.isFile() || target.size > MAX_STATE_BYTES) {\n throw new Error(\"Memory ingest epoch state file is unsafe\");\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return 0;\n throw error;\n }\n\n const fd = openSync(epochFile, constants.O_RDONLY | noFollowFlag());\n try {\n const opened = fstatSync(fd);\n if (!opened.isFile() || opened.size > MAX_STATE_BYTES) {\n throw new Error(\"Memory ingest epoch state file is invalid\");\n }\n const parsed: unknown = JSON.parse(readFileSync(fd, \"utf8\"));\n const epoch = typeof parsed === \"object\" && parsed !== null\n ? (parsed as Record<string, unknown>).epoch\n : undefined;\n if (!Number.isSafeInteger(epoch) || (epoch as number) < 0) {\n throw new Error(\"Memory ingest epoch state is corrupt\");\n }\n return epoch as number;\n } catch (error) {\n if (error instanceof SyntaxError) throw new Error(\"Memory ingest epoch state is corrupt\");\n throw error;\n } finally {\n closeSync(fd);\n }\n}\n\nfunction persistEpoch(epochFile: string, epoch: number): void {\n const temporary = join(\n dirname(epochFile),\n `.${basename(epochFile)}.${String(process.pid)}.${randomUUID()}.tmp`,\n );\n let fd: number | undefined;\n try {\n fd = openSync(\n temporary,\n constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | noFollowFlag(),\n 0o600,\n );\n writeFileSync(fd, `${JSON.stringify({ epoch })}\\n`, \"utf8\");\n fsyncSync(fd);\n closeSync(fd);\n fd = undefined;\n renameSync(temporary, epochFile);\n chmodSync(epochFile, 0o600);\n } catch (error) {\n if (fd !== undefined) closeSync(fd);\n try { unlinkSync(temporary); } catch { /* no temporary file to remove */ }\n throw error;\n }\n}\n\nfunction noFollowFlag(): number {\n return \"O_NOFOLLOW\" in constants ? constants.O_NOFOLLOW : 0;\n}\n","/**\n * Session backfill — one-time ingestion of pre-existing chat history into\n * cloud memory, run fire-and-forget at plugin startup (see index.ts).\n *\n * Reads the openclaw-chat plugin's on-disk session files directly:\n * ~/.alfe/sessions/chat/{sessionId}.json\n * The write side is packages/openclaw-chat/src/session-store.ts — that path\n * and file shape are a read contract; keep the two in sync. The chat plugin\n * is not a package dependency because its session-store functions aren't\n * part of its public entry and the plugin isn't guaranteed to be installed\n * alongside memory. Like the session store itself, this assumes one agent\n * per machine (`alfe remove` deletes ~/.alfe, so files never outlive the\n * agent they belong to).\n *\n * No-re-sync guarantees (in concert with services/memory):\n * 1. Runs only while the server-side `sessionsBackfillSyncedAt` flag is\n * unset; the flag survives integration uninstall/reinstall.\n * 2. Only messages with timestamp < cutoffMs are ingested — for agents that\n * already had memory installed, everything after `bootstrapSyncedAt` was\n * already captured live by AutoCapture.\n * 3. sessionKey = file sessionId and index = file array position, so the\n * ingest consumer's per-session `lastProcessedIndex` high-water mark makes\n * a crash-and-retry replay free (filtered server-side before any cost).\n *\n * Each session is sent as exactly ONE memoryIngest call (one SQS message,\n * windowed to the most recent messages that fit the caps below). Splitting a\n * session across multiple SQS messages would race the shared per-session\n * high-water mark on the standard (non-FIFO) ingest queue: a transiently\n * failed early chunk redelivered after a later chunk succeeds gets filtered\n * out and silently lost.\n */\n\nimport { constants } from \"node:fs\";\nimport { open, readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { MemoryCloudConfig, MemoryMessage, SessionMetadata } from \"./types.js\";\nimport { hasControlCharacters } from \"./boundary.js\";\n\nconst DEFAULT_SESSIONS_DIR = join(homedir(), \".alfe\", \"sessions\", \"chat\");\n\n// Server-side zod caps on /agent/memory/ingest (services/memory\n// api-agents/ingest/post.ts) — exceeding any of these is a deterministic 400,\n// which would put the startup retry loop into a permanent failure.\nconst MAX_MESSAGES_PER_SESSION = 500;\nconst MAX_SESSION_KEY_CHARS = 256;\nconst MAX_MESSAGE_INDEX = 1_000_000;\nconst MAX_CONTENT_CHARS = 100_000;\n/** One ingest call becomes one SQS SendMessage (256KB hard limit). */\nconst MAX_BATCH_BYTES = 180_000;\nconst MAX_SESSION_FILES = 10_000;\nconst MAX_SESSION_FILE_BYTES = 8 * 1024 * 1024;\nconst MAX_METADATA_CHARS = 256;\nconst MAX_DATE_MS = 8_640_000_000_000_000;\n\ninterface BackfillApi {\n memoryIngest(sessionKey: string, messages: MemoryMessage[], metadata?: SessionMetadata): Promise<{ queued: boolean; messageCount: number }>;\n memoryBootstrapStatusMark(scope?: \"files\" | \"sessions\"): Promise<{ synced: true; syncedAt: string }>;\n}\n\ninterface BackfillLogger {\n info: (msg: string, ctx?: Record<string, unknown>) => void;\n debug: (msg: string, ctx?: Record<string, unknown>) => void;\n warn: (msg: string, ctx?: Record<string, unknown>) => void;\n}\n\ninterface ParsedSession {\n sessionId: string;\n channel?: string;\n userId?: string;\n /** Recent-window messages carrying their original file-array position as `index`. */\n messages: MemoryMessage[];\n lastMessageMs: number;\n}\n\nexport interface SessionsBackfillOptions {\n cutoffMs: number;\n sessionsDir?: string;\n}\n\nexport async function runSessionsBackfill(\n client: BackfillApi,\n config: MemoryCloudConfig,\n logger: BackfillLogger,\n opts: SessionsBackfillOptions,\n): Promise<void> {\n const sessionsDir = opts.sessionsDir ?? DEFAULT_SESSIONS_DIR;\n\n let fileNames: string[];\n try {\n fileNames = (await readdir(sessionsDir, { withFileTypes: true }))\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".json\"))\n .map((entry) => entry.name)\n .sort()\n .slice(0, MAX_SESSION_FILES);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n // No sessions directory — nothing to backfill, ever.\n logger.debug(\"sessions backfill: no sessions directory, marking complete\");\n try { await client.memoryBootstrapStatusMark(\"sessions\"); } catch { /* retry next start */ }\n } else {\n // Transient (EACCES/EMFILE/...) — don't forfeit the backfill.\n logger.warn(\"sessions backfill: sessions directory unreadable; will retry on next startup\");\n }\n return;\n }\n\n const sessions: ParsedSession[] = [];\n for (const name of fileNames) {\n const parsed = await parseSessionFile(join(sessionsDir, name), config.captureMaxChars, opts.cutoffMs);\n if (parsed) sessions.push(parsed);\n }\n\n // Most recent non-empty sessions first, capped. Filter BEFORE capping:\n // on a retrofit agent the newest sessions are often entirely post-install\n // (emptied by the cutoff) and must not occupy the cap slots.\n const eligible = sessions\n .filter((s) => s.messages.length > 0)\n .sort((a, b) => b.lastMessageMs - a.lastMessageMs)\n .slice(0, config.backfillMaxSessions);\n\n if (eligible.length === 0) {\n logger.debug(\"sessions backfill: no eligible sessions, marking complete\");\n try { await client.memoryBootstrapStatusMark(\"sessions\"); } catch { /* retry next start */ }\n return;\n }\n\n logger.info(\"sessions backfill: ingesting sessions\", {\n sessionCount: eligible.length,\n messageCount: eligible.reduce((n, s) => n + s.messages.length, 0),\n });\n\n for (const session of eligible) {\n const metadata: SessionMetadata = {\n ...(session.channel ? { channelId: session.channel } : {}),\n ...(session.userId ? { userId: session.userId } : {}),\n };\n try {\n await client.memoryIngest(session.sessionId, session.messages, metadata);\n } catch {\n // Abort without marking — next startup replays, and the server-side\n // lastProcessedIndex filter discards already-processed sessions free.\n logger.warn(\"sessions backfill: ingest failed; will retry on next startup\");\n return;\n }\n }\n\n try {\n await client.memoryBootstrapStatusMark(\"sessions\");\n logger.info(\"sessions backfill: complete\", { sessionCount: eligible.length });\n } catch {\n logger.warn(\"sessions backfill: mark failed; will retry on next startup\");\n }\n}\n\n/**\n * Tolerant parse of a single session file. Returns null for corrupt or\n * shape-mismatched files (skipped, never fatal). Message `index` is the\n * original array position in the file — stable because the store is\n * append-only — so retries resume idempotently server-side.\n */\nasync function parseSessionFile(\n filePath: string,\n captureMaxChars: number,\n cutoffMs: number,\n): Promise<ParsedSession | null> {\n let raw: unknown;\n let handle: Awaited<ReturnType<typeof open>> | undefined;\n try {\n handle = await open(filePath, constants.O_RDONLY | noFollowFlag());\n const stat = await handle.stat();\n if (!stat.isFile() || stat.size > MAX_SESSION_FILE_BYTES) return null;\n raw = JSON.parse(await handle.readFile(\"utf8\"));\n } catch {\n return null;\n } finally {\n await handle?.close();\n }\n if (typeof raw !== \"object\" || raw === null) return null;\n\n const data = raw as Record<string, unknown>;\n if (!isBoundedScalar(data.sessionId, MAX_SESSION_KEY_CHARS)) return null;\n if (!Array.isArray(data.messages)) return null;\n\n const maxContentChars = Math.min(captureMaxChars, MAX_CONTENT_CHARS);\n let lastMessageMs = 0;\n const filtered: MemoryMessage[] = [];\n for (const [index, entry] of data.messages.entries()) {\n if (index > MAX_MESSAGE_INDEX) break;\n if (typeof entry !== \"object\" || entry === null) continue;\n const msg = entry as Record<string, unknown>;\n if (msg.role !== \"user\" && msg.role !== \"assistant\") continue;\n if (typeof msg.content !== \"string\" || msg.content.trim().length === 0) continue;\n if (\n typeof msg.timestamp !== \"number\" ||\n !Number.isFinite(msg.timestamp) ||\n msg.timestamp < 0 ||\n msg.timestamp > MAX_DATE_MS\n ) continue;\n\n lastMessageMs = Math.max(lastMessageMs, msg.timestamp);\n if (msg.timestamp >= cutoffMs) continue;\n\n filtered.push({\n role: msg.role,\n content: msg.content.length > maxContentChars ? msg.content.slice(0, maxContentChars) : msg.content,\n index,\n timestamp: new Date(msg.timestamp).toISOString(),\n });\n }\n\n return {\n sessionId: data.sessionId,\n channel: isBoundedScalar(data.channel, MAX_METADATA_CHARS) ? data.channel : undefined,\n userId: isBoundedScalar(data.userId, MAX_METADATA_CHARS) ? data.userId : undefined,\n messages: recentWindow(filtered),\n lastMessageMs,\n };\n}\n\nfunction isBoundedScalar(value: unknown, maxChars: number): value is string {\n return typeof value === \"string\" && value.length > 0 && value.length <= maxChars &&\n !hasControlCharacters(value);\n}\n\nfunction noFollowFlag(): number {\n return \"O_NOFOLLOW\" in constants ? constants.O_NOFOLLOW : 0;\n}\n\n/**\n * The most recent contiguous run of messages fitting both ingest caps\n * (message count and serialized bytes). Walks backwards from the newest\n * message and stops at the first one that would overflow the byte budget.\n */\nfunction recentWindow(messages: MemoryMessage[]): MemoryMessage[] {\n const window: MemoryMessage[] = [];\n let bytes = 0;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (window.length >= MAX_MESSAGES_PER_SESSION) break;\n const msgBytes = Buffer.byteLength(JSON.stringify(messages[i])) + 1;\n if (bytes + msgBytes > MAX_BATCH_BYTES) break;\n window.unshift(messages[i]);\n bytes += msgBytes;\n }\n return window;\n}\n","import { createRequire } from \"node:module\";\nimport { lstat, readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { Type, type TSchema } from \"@sinclair/typebox\";\nimport { resolveConfig, type ResolvedConfig } from \"@alfe.ai/config\";\nimport { AgentApiClient, installToolErrorCapture } from \"@alfe.ai/agent-api-client\";\nimport { defineTool, type ToolDef } from \"@alfe.ai/openclaw-plugin-kit\";\nimport { AutoCapture } from \"./auto-capture.js\";\nimport { AutoRecall } from \"./auto-recall.js\";\nimport {\n classifyToolFailure,\n hasControlCharacters,\n MAX_ENTITY_CHARS,\n MAX_FILTER_CHARS,\n MAX_MEMORY_ID_CHARS,\n MAX_QUERY_CHARS,\n MAX_SEARCH_RESULTS,\n MAX_SOURCE_CHARS,\n MAX_STORE_TEXT_CHARS,\n MAX_TOPIC_CHARS,\n parseEmptyInput,\n parseEntityInput,\n parseForgetInput,\n parseLearnInput,\n parseMemoryConfig,\n parseSearchInput,\n parseStoreInput,\n safeCount,\n safeText,\n} from \"./boundary.js\";\nimport { readWorkspaceText } from \"./file-boundary.js\";\nimport { formatSearchResults, normalizeSearchResults } from \"./formatter.js\";\nimport { resolveIngestEpoch } from \"./ingest-epoch.js\";\nimport { runSessionsBackfill } from \"./session-backfill.js\";\nimport type { MemoryApi, MemoryCloudConfig, SessionMetadata } from \"./types.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { version?: unknown };\nconst PLUGIN_VERSION = typeof pkg.version === \"string\" ? pkg.version : \"0.0.0\";\nconst MAX_BOOTSTRAP_FILES = 200;\nconst TAG_SCHEMA = Type.Union([\n Type.Literal(\"fact\"),\n Type.Literal(\"decision\"),\n Type.Literal(\"preference\"),\n Type.Literal(\"event\"),\n Type.Literal(\"discovery\"),\n]);\n\nexport interface PluginLogger {\n info(message: string, context?: Record<string, unknown>): void;\n debug(message: string, context?: Record<string, unknown>): void;\n warn(message: string, context?: Record<string, unknown>): void;\n error(message: string, context?: Record<string, unknown>): void;\n}\n\nexport interface PluginApi {\n pluginConfig?: unknown;\n logger: PluginLogger;\n registerTool(tool: ToolDef<TSchema>, options?: { names?: string[] }): void;\n on(hookName: string, handler: (...args: unknown[]) => unknown, options?: { priority?: number }): void;\n registerMemoryPromptSection(builder: (params: { availableTools: Set<string> }) => string[]): void;\n}\n\nexport interface MemoryCloudPluginDependencies {\n resolveConfig?: () => Pick<ResolvedConfig, \"apiKey\" | \"apiUrl\" | \"workspacePath\">;\n createClient?: (config: { apiKey: string; apiUrl: string }) => MemoryApi;\n resolveEpoch?: typeof resolveIngestEpoch;\n installErrorCapture?: typeof installToolErrorCapture;\n}\n\nexport interface MemoryCloudPlugin {\n id: string;\n name: string;\n description: string;\n version: string;\n kind: \"memory\";\n register(api: PluginApi): void;\n}\n\ninterface RuntimeState {\n client: MemoryApi | null;\n workspacePath: string | null;\n capture: AutoCapture | null | false;\n}\n\nexport function createMemoryCloudPlugin(\n dependencies: MemoryCloudPluginDependencies = {},\n): MemoryCloudPlugin {\n const resolveRuntimeConfig = dependencies.resolveConfig ?? resolveConfig;\n const createClient = dependencies.createClient ?? ((config) => new AgentApiClient(config));\n const resolveEpoch = dependencies.resolveEpoch ?? resolveIngestEpoch;\n const installErrorCapture = dependencies.installErrorCapture ?? installToolErrorCapture;\n\n return {\n id: \"@alfe.ai/openclaw-memory-cloud\",\n name: \"Cloud Memory\",\n description: \"Persistent agent memory backed by Turbopuffer and DynamoDB\",\n version: PLUGIN_VERSION,\n kind: \"memory\",\n\n register(api): void {\n installErrorCapture(api, { plugin: \"openclaw-memory-cloud\" });\n const config = parseMemoryConfig(api.pluginConfig);\n const state: RuntimeState = { client: null, workspacePath: null, capture: null };\n\n const ensureRuntime = (): { client: MemoryApi; workspacePath: string } => {\n if (state.client === null || state.workspacePath === null) {\n const runtimeConfig = resolveRuntimeConfig();\n state.client = createClient({ apiKey: runtimeConfig.apiKey, apiUrl: runtimeConfig.apiUrl });\n state.workspacePath = runtimeConfig.workspacePath;\n }\n return { client: state.client, workspacePath: state.workspacePath };\n };\n\n const captureApi = {\n memoryIngest: (...args: Parameters<MemoryApi[\"memoryIngest\"]>) => ensureRuntime().client.memoryIngest(...args),\n } as MemoryApi;\n const recallApi = {\n memoryLoadContext: (...args: Parameters<MemoryApi[\"memoryLoadContext\"]>) => ensureRuntime().client.memoryLoadContext(...args),\n } as MemoryApi;\n const autoRecall = new AutoRecall(recallApi, config, api.logger);\n\n const ensureCapture = (): AutoCapture | null => {\n if (!config.autoCapture || state.capture === false) return null;\n if (state.capture === null) {\n try {\n state.capture = new AutoCapture(captureApi, config, api.logger, resolveEpoch());\n } catch {\n state.capture = false;\n api.logger.warn(\"Memory auto-capture disabled because its monotonic epoch could not be secured\");\n return null;\n }\n }\n return state.capture;\n };\n\n const runTool = async <T>(action: string, operation: () => Promise<T>): Promise<T | { status: \"error\"; error: string }> => {\n try {\n return await operation();\n } catch (error) {\n return { status: \"error\", error: classifyToolFailure(error, action) };\n }\n };\n\n api.registerMemoryPromptSection(({ availableTools }) => buildMemoryPrompt(availableTools));\n\n api.registerTool(defineTool({\n name: \"memory_recall\",\n description: \"Search private conversation memory and the per-agent knowledge graph.\",\n parameters: Type.Object({\n query: Type.String({ minLength: 1, maxLength: MAX_QUERY_CHARS }),\n limit: Type.Optional(Type.Integer({ minimum: 1, maximum: MAX_SEARCH_RESULTS, default: 10 })),\n topic: Type.Optional(Type.String({ maxLength: MAX_FILTER_CHARS })),\n subtopic: Type.Optional(Type.String({ maxLength: MAX_FILTER_CHARS })),\n tag: Type.Optional(TAG_SCHEMA),\n }, { additionalProperties: false }),\n handler: async (params) => runTool(\"Memory recall\", async () => {\n const input = parseSearchInput(params);\n return formatSearchResults(await ensureRuntime().client.memorySearch(input.query, input));\n }),\n }), { names: [\"memory_recall\", \"memory_search\"] });\n\n api.registerTool(defineTool({\n name: \"memory_store\",\n description: \"Explicitly save one bounded item to private long-term memory.\",\n parameters: Type.Object({\n text: Type.String({ minLength: 1, maxLength: MAX_STORE_TEXT_CHARS }),\n topic: Type.Optional(Type.String({ maxLength: MAX_TOPIC_CHARS })),\n subtopic: Type.Optional(Type.String({ maxLength: MAX_TOPIC_CHARS })),\n tag: Type.Optional(TAG_SCHEMA),\n importance: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),\n }, { additionalProperties: false }),\n handler: async (params) => runTool(\"Memory store\", async () => {\n const input = parseStoreInput(params);\n const result = await ensureRuntime().client.memoryStore(input.text, input);\n const memoryId = safeText((result as { memoryId?: unknown }).memoryId, MAX_MEMORY_ID_CHARS);\n if (!memoryId) throw new Error(\"Memory store response omitted the memory ID\");\n return { status: \"stored\" as const, memoryId };\n }),\n }), { names: [\"memory_store\"] });\n\n api.registerTool(defineTool({\n name: \"memory_learn\",\n description: \"Ingest bounded inline text or one regular file inside the configured agent workspace.\",\n parameters: Type.Object({\n content: Type.Optional(Type.String({ minLength: 1, maxLength: 100_000 })),\n path: Type.Optional(Type.String({ minLength: 1, maxLength: 1_024 })),\n source: Type.Optional(Type.String({ minLength: 1, maxLength: MAX_SOURCE_CHARS })),\n }, { additionalProperties: false }),\n handler: async (params) => runTool(\"Memory learn\", async () => {\n const input = parseLearnInput(params);\n const runtime = ensureRuntime();\n let text: string;\n if (input.path === undefined) {\n if (input.content === undefined) throw new Error(\"Memory learn content is unavailable\");\n text = input.content;\n } else {\n text = await readWorkspaceText(runtime.workspacePath, input.path);\n }\n const result = await runtime.client.memoryLearn({\n text,\n source: input.source ?? input.path,\n sourceType: input.path === undefined ? \"inline\" : \"file\",\n });\n return {\n status: \"stored\" as const,\n memoriesStored: safeCount(result.memoriesStored),\n triplesStored: safeCount(result.triplesStored),\n chunks: safeCount(result.chunks),\n };\n }),\n }), { names: [\"memory_learn\"] });\n\n api.registerTool(defineTool({\n name: \"memory_forget\",\n description: \"Preview up to five matching private memories, then delete only after confirming the exact returned IDs.\",\n parameters: Type.Object({\n query: Type.String({ minLength: 1, maxLength: MAX_QUERY_CHARS }),\n confirmMemoryIds: Type.Optional(Type.Array(Type.String({ minLength: 1, maxLength: MAX_MEMORY_ID_CHARS }), { minItems: 1, maxItems: 5, uniqueItems: true })),\n }, { additionalProperties: false }),\n handler: async (params) => runTool(\"Memory deletion\", async () => {\n const input = parseForgetInput(params);\n const client = ensureRuntime().client;\n const results = normalizeSearchResults(await client.memorySearch(input.query, { limit: 5 }));\n const matches = results.memories.slice(0, 5).filter((memory) => memory.id.length > 0);\n const currentIds = matches.map((memory) => memory.id);\n if (input.confirmMemoryIds === undefined) {\n return {\n status: \"confirmation_required\" as const,\n matches: matches.map((memory) => ({ id: memory.id, text: memory.text.slice(0, 240) })),\n instruction: \"Call memory_forget again with confirmMemoryIds exactly as returned, in the same order.\",\n };\n }\n if (JSON.stringify(input.confirmMemoryIds) !== JSON.stringify(currentIds)) {\n return { status: \"error\" as const, error: \"Matching memories changed; preview again before deleting.\" };\n }\n const deletedIds: string[] = [];\n const failedIds: string[] = [];\n for (const memoryId of currentIds) {\n try {\n const deleted = await client.memoryDelete(memoryId);\n if (deleted.deleted) deletedIds.push(memoryId);\n else failedIds.push(memoryId);\n } catch {\n failedIds.push(memoryId);\n }\n }\n return failedIds.length === 0\n ? { status: \"deleted\" as const, deletedIds }\n : { status: \"error\" as const, error: \"Some memories could not be deleted.\", deletedIds, failedIds };\n }),\n }), { names: [\"memory_forget\"] });\n\n api.registerTool(defineTool({\n name: \"memory_graph\",\n description: \"Look up bounded facts about one entity in the private knowledge graph.\",\n parameters: Type.Object({ entity: Type.String({ minLength: 1, maxLength: MAX_ENTITY_CHARS }) }, { additionalProperties: false }),\n handler: async (params) => runTool(\"Memory graph lookup\", async () => {\n const { entity } = parseEntityInput(params);\n const result = await ensureRuntime().client.memoryLookupEntity(entity);\n const triples = Array.isArray(result.triples) ? result.triples.slice(0, 100) : [];\n return {\n subject: safeText(result.subject, MAX_ENTITY_CHARS),\n triples: triples.map((triple) => ({\n predicate: safeText(triple.predicate, 512),\n object: safeText(triple.object, 2_000),\n validFrom: safeText(triple.validFrom, 64),\n confidence: typeof triple.confidence === \"number\" && Number.isFinite(triple.confidence) ? triple.confidence : 0,\n })),\n truncated: Array.isArray(result.triples) && result.triples.length > 100,\n };\n }),\n }), { names: [\"memory_graph\"] });\n\n api.registerTool(defineTool({\n name: \"memory_stats\",\n description: \"Get bounded private-memory storage statistics.\",\n parameters: Type.Object({}, { additionalProperties: false }),\n handler: async (params) => runTool(\"Memory stats\", async () => {\n parseEmptyInput(params);\n const stats = await ensureRuntime().client.memoryStats();\n return {\n vectorCount: safeCount(stats.vectorCount),\n tripleCount: safeCount(stats.tripleCount),\n storageEstimateBytes: safeCount(stats.storageEstimateBytes),\n };\n }),\n }), { names: [\"memory_stats\"] });\n\n api.registerTool(defineTool({\n name: \"memory_navigate\",\n description: \"Browse bounded topic and subtopic counts in private memory.\",\n parameters: Type.Object({}, { additionalProperties: false }),\n handler: async (params) => runTool(\"Memory navigation\", async () => {\n parseEmptyInput(params);\n const result = await ensureRuntime().client.memoryNavigate();\n const topics = Array.isArray(result.topics) ? result.topics.slice(0, 200) : [];\n return {\n topics: topics.map((topic) => ({\n name: safeText(topic.name, 256),\n tripleCount: safeCount(topic.tripleCount),\n subtopics: Array.isArray(topic.subtopics) ? topic.subtopics.slice(0, 100).map((entry) => safeText(entry, 256)) : [],\n })),\n truncated: Array.isArray(result.topics) && result.topics.length > 200,\n };\n }),\n }), { names: [\"memory_navigate\"] });\n\n api.on(\"before_agent_start\", async (event: unknown) => {\n const prompt = isRecord(event) && typeof event.prompt === \"string\" ? event.prompt : \"\";\n const contextXml = await autoRecall.loadForPrompt(prompt.slice(0, MAX_QUERY_CHARS));\n return contextXml ? { prependContext: contextXml } : undefined;\n }, { priority: 10 });\n\n api.on(\"message_received\", (event: unknown, context: unknown) => {\n trackHookMessage(ensureCapture(), \"user\", event, context);\n });\n api.on(\"message_sending\", (event: unknown, context: unknown) => {\n trackHookMessage(ensureCapture(), \"assistant\", event, context);\n });\n api.on(\"agent_end\", async (event: unknown, context: unknown) => {\n if (isRecord(event) && event.success === false) return;\n const sessionKey = hookSessionKey(context);\n if (sessionKey) await ensureCapture()?.onAgentEnd(sessionKey);\n });\n\n void Promise.resolve().then(async () => {\n const runtime = ensureRuntime();\n await runStartupSync(runtime.client, runtime.workspacePath, config, api.logger);\n }).catch(() => {\n api.logger.warn(\"Memory startup sync failed; it will retry on next startup\");\n });\n\n api.logger.info(\"memory-cloud extension registered\", {\n autoCapture: config.autoCapture,\n autoRecall: config.autoRecall,\n });\n },\n };\n}\n\nfunction trackHookMessage(\n capture: AutoCapture | null,\n role: \"user\" | \"assistant\",\n event: unknown,\n context: unknown,\n): void {\n if (!capture || !isRecord(event)) return;\n const sessionKey = hookSessionKey(context);\n const content = typeof event.content === \"string\" ? event.content : \"\";\n if (!sessionKey || content.length === 0) return;\n capture.trackMessage(sessionKey, role, content, hookMetadata(context));\n}\n\nfunction hookSessionKey(context: unknown): string | undefined {\n if (!isRecord(context) || typeof context.sessionKey !== \"string\") return undefined;\n const value = context.sessionKey;\n return value.length > 0 && value.length <= 256 && !hasControlCharacters(value) ? value : undefined;\n}\n\nfunction hookMetadata(context: unknown): SessionMetadata | undefined {\n if (!isRecord(context)) return undefined;\n const channelId = safeOptionalScalar(context.channelId);\n const userId = safeOptionalScalar(context.userId);\n return channelId || userId ? { ...(channelId ? { channelId } : {}), ...(userId ? { userId } : {}) } : undefined;\n}\n\nfunction safeOptionalScalar(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 && value.length <= 256 && !hasControlCharacters(value)\n ? value\n : undefined;\n}\n\nfunction buildMemoryPrompt(availableTools: Set<string>): string[] {\n const lines = [\"## Memory\"];\n if (availableTools.has(\"memory_recall\")) lines.push(\"Use memory_recall to search private conversation memory and per-agent facts.\");\n if (availableTools.has(\"memory_store\")) lines.push(\"Use memory_store for an explicit bounded item worth retaining.\");\n if (availableTools.has(\"memory_learn\")) lines.push(\"Use memory_learn for bounded source text or a regular file inside the configured workspace.\");\n if (availableTools.has(\"memory_graph\")) lines.push(\"Use memory_graph to look up facts about one entity.\");\n if (availableTools.has(\"memory_forget\")) lines.push(\"memory_forget always requires a preview followed by exact ID confirmation.\");\n return lines;\n}\n\nexport async function runStartupSync(\n client: MemoryApi,\n workspacePath: string,\n config: MemoryCloudConfig,\n logger: PluginLogger,\n): Promise<void> {\n let status: Awaited<ReturnType<MemoryApi[\"memoryBootstrapStatus\"]>>;\n try {\n status = await client.memoryBootstrapStatus();\n } catch {\n logger.debug(\"Memory bootstrap status check failed; startup sync skipped\");\n return;\n }\n const startupMs = Date.now();\n if (!status.synced && !(await runBootstrap(client, workspacePath, logger))) return;\n\n if (config.backfillSessions && status.sessionsBackfillSynced === false) {\n const installedAtMs = status.syncedAt ? Date.parse(status.syncedAt) : Number.POSITIVE_INFINITY;\n const cutoffMs = Math.min(startupMs, Number.isFinite(installedAtMs) ? installedAtMs : Number.POSITIVE_INFINITY);\n await runSessionsBackfill(client, config, logger, { cutoffMs });\n }\n}\n\nasync function runBootstrap(client: MemoryApi, workspacePath: string, logger: PluginLogger): Promise<boolean> {\n const relativePaths: string[] = [];\n try {\n const stat = await lstat(join(workspacePath, \"MEMORY.md\"));\n if (stat.isFile() && !stat.isSymbolicLink()) relativePaths.push(\"MEMORY.md\");\n } catch { /* optional file */ }\n\n try {\n const memoryDirectory = join(workspacePath, \"memory\");\n const stat = await lstat(memoryDirectory);\n if (stat.isDirectory() && !stat.isSymbolicLink()) {\n const entries = await readdir(memoryDirectory, { withFileTypes: true });\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n if (relativePaths.length >= MAX_BOOTSTRAP_FILES) break;\n if (entry.isFile() && entry.name.endsWith(\".md\")) relativePaths.push(`memory/${entry.name}`);\n }\n }\n } catch { /* optional directory */ }\n\n for (const relativePath of relativePaths) {\n try {\n const text = await readWorkspaceText(workspacePath, relativePath);\n if (text.trim().length > 0) {\n await client.memoryLearn({ text, source: relativePath, sourceType: \"file\" });\n }\n } catch {\n logger.warn(\"Memory bootstrap file ingestion failed; it will retry on next startup\");\n return false;\n }\n }\n\n try {\n await client.memoryBootstrapStatusMark();\n logger.info(\"Memory workspace bootstrap complete\", { fileCount: relativePaths.length });\n return true;\n } catch {\n logger.warn(\"Memory bootstrap completion mark failed; it will retry on next startup\");\n return false;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { createMemoryCloudPlugin, type MemoryCloudPlugin } from \"./runtime.js\";\n\nconst plugin: MemoryCloudPlugin = createMemoryCloudPlugin();\n\nexport default plugin;\n"],"mappings":"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"}
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"kind": "memory",
|
|
4
4
|
"name": "Cloud Memory",
|
|
5
5
|
"description": "Persistent agent memory backed by Turbopuffer vectors and DynamoDB knowledge graph. Replaces the builtin LanceDB memory extension.",
|
|
6
|
-
"entry": "./dist/
|
|
6
|
+
"entry": "./dist/plugin.js",
|
|
7
7
|
"activation": { "onStartup": true },
|
|
8
8
|
"contracts": {
|
|
9
9
|
"tools": [
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"description": "Automatically inject relevant memories into context at session start"
|
|
32
32
|
},
|
|
33
33
|
"captureMaxChars": {
|
|
34
|
-
"type": "
|
|
34
|
+
"type": "integer",
|
|
35
35
|
"default": 500,
|
|
36
36
|
"minimum": 100,
|
|
37
37
|
"maximum": 10000,
|
|
38
38
|
"description": "Maximum message length for auto-capture"
|
|
39
39
|
},
|
|
40
40
|
"idleFlushSeconds": {
|
|
41
|
-
"type": "
|
|
41
|
+
"type": "integer",
|
|
42
42
|
"default": 60,
|
|
43
43
|
"minimum": 10,
|
|
44
44
|
"maximum": 600,
|
|
@@ -50,13 +50,14 @@
|
|
|
50
50
|
"description": "On first install, ingest the agent's existing chat sessions into memory (one-time, duplicate-safe)"
|
|
51
51
|
},
|
|
52
52
|
"backfillMaxSessions": {
|
|
53
|
-
"type": "
|
|
53
|
+
"type": "integer",
|
|
54
54
|
"default": 50,
|
|
55
55
|
"minimum": 1,
|
|
56
56
|
"maximum": 1000,
|
|
57
57
|
"description": "Maximum number of most-recent chat sessions to backfill"
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
},
|
|
60
|
+
"additionalProperties": false
|
|
60
61
|
},
|
|
61
62
|
"uiHints": {
|
|
62
63
|
"autoCapture": {
|
package/package.json
CHANGED
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-memory-cloud",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.39",
|
|
4
4
|
"description": "Cloud memory extension for OpenClaw — Turbopuffer vectors + DynamoDB knowledge graph",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./plugin": {
|
|
15
|
+
"types": "./dist/plugin.d.ts",
|
|
16
|
+
"require": "./dist/plugin.cjs",
|
|
17
|
+
"import": "./dist/plugin.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
8
20
|
"openclaw": {
|
|
9
21
|
"extensions": [
|
|
10
|
-
"./dist/
|
|
22
|
+
"./dist/plugin.js"
|
|
11
23
|
]
|
|
12
24
|
},
|
|
13
25
|
"dependencies": {
|
|
14
|
-
"@
|
|
15
|
-
"@alfe.ai/
|
|
26
|
+
"@sinclair/typebox": "^0.34.48",
|
|
27
|
+
"@alfe.ai/agent-api-client": "0.15.0",
|
|
28
|
+
"@alfe.ai/config": "0.4.1",
|
|
29
|
+
"@alfe.ai/openclaw-plugin-kit": "0.2.0"
|
|
16
30
|
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"openclaw.plugin.json",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
17
36
|
"license": "UNLICENSED",
|
|
18
37
|
"homepage": "https://alfe.ai",
|
|
19
38
|
"author": "Alfe (https://alfe.ai)",
|
|
@@ -26,7 +45,8 @@
|
|
|
26
45
|
"plugin"
|
|
27
46
|
],
|
|
28
47
|
"scripts": {
|
|
29
|
-
"build": "
|
|
48
|
+
"build": "tsdown",
|
|
49
|
+
"dev": "tsdown --watch",
|
|
30
50
|
"test": "vitest run --passWithNoTests",
|
|
31
51
|
"test:watch": "vitest",
|
|
32
52
|
"typecheck": "tsc --noEmit",
|
package/.turbo/turbo-build.log
DELETED
package/CHANGELOG.md
DELETED
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
# @alfe.ai/openclaw-memory-cloud
|
|
2
|
-
|
|
3
|
-
## 0.0.37
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- 84bd15e: Fix: live auto-capture no longer silently drops messages after a daemon restart. `AutoCapture` now stamps each flush with a per-boot `ingestEpoch` (wall-clock ms captured at process start) and forwards it through `memoryIngest`. The memory service uses it to detect that a restarted daemon's message-index counter has reset to 0 and resets the per-session high-water mark accordingly — previously every post-restart capture was filtered out by the surviving watermark (`storedCount: 0`) until the local counter climbed back past the pre-restart max. Backfill is unchanged (it keeps stable file-position indices and sends no epoch).
|
|
8
|
-
- Updated dependencies [84bd15e]
|
|
9
|
-
- @alfe.ai/agent-api-client@0.13.0
|
|
10
|
-
|
|
11
|
-
## 0.0.36
|
|
12
|
-
|
|
13
|
-
### Patch Changes
|
|
14
|
-
|
|
15
|
-
- Updated dependencies [c8c8166]
|
|
16
|
-
- @alfe.ai/agent-api-client@0.12.0
|
|
17
|
-
|
|
18
|
-
## 0.0.35
|
|
19
|
-
|
|
20
|
-
### Patch Changes
|
|
21
|
-
|
|
22
|
-
- Updated dependencies [fb19a43]
|
|
23
|
-
- Updated dependencies [c38f020]
|
|
24
|
-
- @alfe.ai/agent-api-client@0.11.2
|
|
25
|
-
|
|
26
|
-
## 0.0.34
|
|
27
|
-
|
|
28
|
-
### Patch Changes
|
|
29
|
-
|
|
30
|
-
- Updated dependencies [3dd0528]
|
|
31
|
-
- @alfe.ai/agent-api-client@0.11.1
|
|
32
|
-
|
|
33
|
-
## 0.0.33
|
|
34
|
-
|
|
35
|
-
### Patch Changes
|
|
36
|
-
|
|
37
|
-
- Updated dependencies [0cb83b6]
|
|
38
|
-
- Updated dependencies [194e0b1]
|
|
39
|
-
- Updated dependencies [09cb229]
|
|
40
|
-
- Updated dependencies [d392820]
|
|
41
|
-
- @alfe.ai/agent-api-client@0.11.0
|
|
42
|
-
|
|
43
|
-
## 0.0.32
|
|
44
|
-
|
|
45
|
-
### Patch Changes
|
|
46
|
-
|
|
47
|
-
- Updated dependencies [4f11322]
|
|
48
|
-
- @alfe.ai/agent-api-client@0.10.0
|
|
49
|
-
|
|
50
|
-
## 0.0.31
|
|
51
|
-
|
|
52
|
-
### Patch Changes
|
|
53
|
-
|
|
54
|
-
- Updated dependencies [41faec4]
|
|
55
|
-
- @alfe.ai/agent-api-client@0.9.0
|
|
56
|
-
|
|
57
|
-
## 0.0.30
|
|
58
|
-
|
|
59
|
-
### Patch Changes
|
|
60
|
-
|
|
61
|
-
- 30c06d6: Capture tool failures for Sentry: wrap tool registration with installToolErrorCapture so thrown and silently-returned tool errors emit a detector-matched [ERROR] line the Alfe daemon reports to Sentry.
|
|
62
|
-
- Updated dependencies [30c06d6]
|
|
63
|
-
- @alfe.ai/agent-api-client@0.8.0
|
|
64
|
-
|
|
65
|
-
## 0.0.29
|
|
66
|
-
|
|
67
|
-
### Patch Changes
|
|
68
|
-
|
|
69
|
-
- Updated dependencies [019c5e5]
|
|
70
|
-
- @alfe.ai/agent-api-client@0.7.1
|
|
71
|
-
|
|
72
|
-
## 0.0.28
|
|
73
|
-
|
|
74
|
-
### Patch Changes
|
|
75
|
-
|
|
76
|
-
- Updated dependencies [930e47b]
|
|
77
|
-
- @alfe.ai/agent-api-client@0.7.0
|
|
78
|
-
|
|
79
|
-
## 0.0.27
|
|
80
|
-
|
|
81
|
-
### Patch Changes
|
|
82
|
-
|
|
83
|
-
- Updated dependencies [3cb626d]
|
|
84
|
-
- @alfe.ai/agent-api-client@0.6.1
|
|
85
|
-
|
|
86
|
-
## 0.0.26
|
|
87
|
-
|
|
88
|
-
### Patch Changes
|
|
89
|
-
|
|
90
|
-
- 0f90085: One-time backfill of existing chat history when memory is first installed. On
|
|
91
|
-
startup, if the server-side `sessionsBackfillSyncedAt` flag is unset, the plugin
|
|
92
|
-
reads the openclaw-chat session files (`~/.alfe/sessions/chat/*.json`), takes the
|
|
93
|
-
most recent `backfillMaxSessions` (default 50) sessions, and ingests messages
|
|
94
|
-
through the existing `/agent/memory/ingest` pipeline. Duplicate-safe by
|
|
95
|
-
construction: a timestamp cutoff excludes anything already captured live (agents
|
|
96
|
-
that had memory before this release only backfill pre-install messages), and the
|
|
97
|
-
ingest consumer's per-session `lastProcessedIndex` high-water mark makes retries
|
|
98
|
-
free. New plugin config: `backfillSessions` (default true), `backfillMaxSessions`
|
|
99
|
-
(default 50). agent-api-client: `memoryBootstrapStatus()` now also returns
|
|
100
|
-
`sessionsBackfillSynced`/`sessionsBackfillSyncedAt`, and
|
|
101
|
-
`memoryBootstrapStatusMark()` accepts an optional `scope` ("files" | "sessions").
|
|
102
|
-
- Updated dependencies [0f90085]
|
|
103
|
-
- @alfe.ai/agent-api-client@0.6.0
|
|
104
|
-
|
|
105
|
-
## 0.0.25
|
|
106
|
-
|
|
107
|
-
### Patch Changes
|
|
108
|
-
|
|
109
|
-
- Updated dependencies [4f07d4a]
|
|
110
|
-
- @alfe.ai/agent-api-client@0.5.0
|
|
111
|
-
|
|
112
|
-
## 0.0.24
|
|
113
|
-
|
|
114
|
-
### Patch Changes
|
|
115
|
-
|
|
116
|
-
- Updated dependencies [cf857c6]
|
|
117
|
-
- @alfe.ai/config@0.3.0
|
|
118
|
-
|
|
119
|
-
## 0.0.23
|
|
120
|
-
|
|
121
|
-
### Patch Changes
|
|
122
|
-
|
|
123
|
-
- a7fcb13: Add npm package metadata (homepage, keywords, author) and README backlinks to alfe.ai and docs.alfe.ai for discoverability + SEO. Metadata/docs only — no code or API changes.
|
|
124
|
-
- Updated dependencies [73aaddb]
|
|
125
|
-
- Updated dependencies [156618e]
|
|
126
|
-
- Updated dependencies [61ade51]
|
|
127
|
-
- Updated dependencies [a7fcb13]
|
|
128
|
-
- Updated dependencies [156618e]
|
|
129
|
-
- @alfe.ai/agent-api-client@0.4.0
|
|
130
|
-
- @alfe.ai/config@0.2.0
|
|
131
|
-
|
|
132
|
-
## 0.0.22
|
|
133
|
-
|
|
134
|
-
### Patch Changes
|
|
135
|
-
|
|
136
|
-
- Updated dependencies [c6e316f]
|
|
137
|
-
- Updated dependencies [4da52e5]
|
|
138
|
-
- @alfe.ai/config@0.1.0
|
|
139
|
-
- @alfe.ai/agent-api-client@0.3.0
|
|
140
|
-
|
|
141
|
-
## 0.0.21
|
|
142
|
-
|
|
143
|
-
### Patch Changes
|
|
144
|
-
|
|
145
|
-
- Updated dependencies [2c81c60]
|
|
146
|
-
- @alfe.ai/agent-api-client@0.2.3
|
|
147
|
-
|
|
148
|
-
## 0.0.20
|
|
149
|
-
|
|
150
|
-
### Patch Changes
|
|
151
|
-
|
|
152
|
-
- b829ff4: Declare the `memory_search` tool in the plugin manifest's `contracts.tools`. The recall tool registers under two names (`memory_recall` and the `memory_search` alias), but only `memory_recall` was declared — OpenClaw rejects any runtime-registered tool not pre-declared in `contracts.tools`, so the plugin logged `plugin must declare contracts.tools for: memory_search` on every activation and never settled, leaving the integration stuck at `actualStatus=unknown`.
|
|
153
|
-
|
|
154
|
-
## 0.0.19
|
|
155
|
-
|
|
156
|
-
### Patch Changes
|
|
157
|
-
|
|
158
|
-
- Updated dependencies [62c3f05]
|
|
159
|
-
- @alfe.ai/agent-api-client@0.2.2
|
|
160
|
-
|
|
161
|
-
## 0.0.18
|
|
162
|
-
|
|
163
|
-
### Patch Changes
|
|
164
|
-
|
|
165
|
-
- Updated dependencies [6927804]
|
|
166
|
-
- @alfe.ai/agent-api-client@0.2.1
|
|
167
|
-
|
|
168
|
-
## 0.0.17
|
|
169
|
-
|
|
170
|
-
### Patch Changes
|
|
171
|
-
|
|
172
|
-
- Updated dependencies [9beffcc]
|
|
173
|
-
- Updated dependencies [4a4cd3e]
|
|
174
|
-
- Updated dependencies [4a4cd3e]
|
|
175
|
-
- Updated dependencies [48e58c5]
|
|
176
|
-
- Updated dependencies [6cbd063]
|
|
177
|
-
- @alfe.ai/agent-api-client@0.2.0
|
|
178
|
-
|
|
179
|
-
## 0.0.16
|
|
180
|
-
|
|
181
|
-
### Patch Changes
|
|
182
|
-
|
|
183
|
-
- Updated dependencies [aa889fe]
|
|
184
|
-
- @alfe.ai/config@0.0.9
|
|
185
|
-
|
|
186
|
-
## 0.0.15
|
|
187
|
-
|
|
188
|
-
### Patch Changes
|
|
189
|
-
|
|
190
|
-
- Updated dependencies [89723a9]
|
|
191
|
-
- @alfe.ai/agent-api-client@0.1.4
|
|
192
|
-
|
|
193
|
-
## 0.0.14
|
|
194
|
-
|
|
195
|
-
### Patch Changes
|
|
196
|
-
|
|
197
|
-
- 6c10518: Defensive `.unref()` on long-lived timers so abnormal exits (e.g. crash before `registerService.stop` runs, CLI exit during transient connection) don't pin the Node event loop. Covers: `openclaw/src/ws-client.ts` tick watch, `openclaw/src/ipc-client.ts` reconnect, `openclaw-sync/src/plugin.ts` scheduled-sync interval + sync-relay debounce + sync-relay reconnect, and `openclaw-memory-cloud/src/auto-capture.ts` idle flush timer. No behavioral change in the happy path — `clearInterval`/`clearTimeout` still fire on graceful teardown.
|
|
198
|
-
|
|
199
|
-
## 0.0.13
|
|
200
|
-
|
|
201
|
-
### Patch Changes
|
|
202
|
-
|
|
203
|
-
- Updated dependencies [893a67a]
|
|
204
|
-
- @alfe.ai/agent-api-client@0.1.3
|
|
205
|
-
|
|
206
|
-
## 0.0.12
|
|
207
|
-
|
|
208
|
-
### Patch Changes
|
|
209
|
-
|
|
210
|
-
- 47b38af: Add `memory_learn` tool and first-run bootstrap sync for `MEMORY.md` + `memory/*.md`. The new tool lets the agent's LLM dump arbitrary content (paragraph, doc, fact list) into long-term memory and have the system auto-classify and extract entities — no manual topic/subtopic/tag required. On first activation the plugin scans the workspace for `MEMORY.md` and one level of `memory/*.md`, ingests them through the same path, and marks the palace synced so subsequent startups skip. `agent-api-client` exposes the matching `memoryLearn`, `memoryBootstrapStatus`, and `memoryBootstrapStatusMark` methods.
|
|
211
|
-
- Updated dependencies [47b38af]
|
|
212
|
-
- @alfe.ai/agent-api-client@0.1.2
|
|
213
|
-
|
|
214
|
-
## 0.0.11
|
|
215
|
-
|
|
216
|
-
### Patch Changes
|
|
217
|
-
|
|
218
|
-
- 9147ccc: Declare manifest contracts (`activation.onStartup`, `contracts.tools`) so OpenClaw 2026.5+'s effective-plugin-set loader imports these plugins. Without these declarations, the new runtime silently skips the plugin entirely — never imports it, never calls `activate(api)`, never establishes its connections. Backwards-compatible with OpenClaw 2026.4.x (already supports the same fields). No source-code or public-API changes — manifest-only patches.
|
|
219
|
-
|
|
220
|
-
- `memory-cloud`, `metrics`, `mcp-bundler`, `openclaw` (alfe-core), `google-chat`, `voice`, `webhooks` set `activation.onStartup: true` because they need to load at boot to maintain persistent connections, attach hooks, or run service polls.
|
|
221
|
-
- `secrets`, `search`, `database`, `google`, `mobile`, `teams`, `whatsapp` set `activation.onStartup: false` — they're tool-only and load on demand once a tool is invoked.
|
|
222
|
-
- `mcp-bundler` omits `contracts.tools` because it registers tools dynamically via the factory form of `api.registerTool(...)`; `onStartup: true` forces import without static tool names.
|
|
223
|
-
|
|
224
|
-
## 0.0.10
|
|
225
|
-
|
|
226
|
-
### Patch Changes
|
|
227
|
-
|
|
228
|
-
- 229045a: Fix plugin id mismatch — set the runtime `plugin.id` (and the manifest `id` for memory-cloud) to the fully qualified package name. OpenClaw 2026.5.x validates that the plugin module's exported id matches the package name; the previous short ids (`"secrets"`, `"memory-cloud"`) caused the plugins to fail validation and not load on agents running newer OpenClaw runtimes.
|
|
229
|
-
|
|
230
|
-
## 0.0.9
|
|
231
|
-
|
|
232
|
-
### Patch Changes
|
|
233
|
-
|
|
234
|
-
- Updated dependencies [229b827]
|
|
235
|
-
- Updated dependencies [4de07e1]
|
|
236
|
-
- @alfe.ai/agent-api-client@0.1.1
|
|
237
|
-
|
|
238
|
-
## 0.0.8
|
|
239
|
-
|
|
240
|
-
### Patch Changes
|
|
241
|
-
|
|
242
|
-
- Updated dependencies [a86024c]
|
|
243
|
-
- @alfe.ai/agent-api-client@0.1.0
|
|
244
|
-
|
|
245
|
-
## 0.0.7
|
|
246
|
-
|
|
247
|
-
### Patch Changes
|
|
248
|
-
|
|
249
|
-
- Updated dependencies [f0a9368]
|
|
250
|
-
- @alfe.ai/agent-api-client@0.0.13
|
|
251
|
-
|
|
252
|
-
## 0.0.6
|
|
253
|
-
|
|
254
|
-
### Patch Changes
|
|
255
|
-
|
|
256
|
-
- Updated dependencies [4f3b54a]
|
|
257
|
-
- @alfe.ai/agent-api-client@0.0.12
|
|
258
|
-
|
|
259
|
-
## 0.0.5
|
|
260
|
-
|
|
261
|
-
### Patch Changes
|
|
262
|
-
|
|
263
|
-
- 6921672: Fix plugin install and activation issues
|
|
264
|
-
|
|
265
|
-
- openclaw-memory-cloud: Add `openclaw.extensions`, refactor to use AgentApiClient instead of raw MemoryClient
|
|
266
|
-
- openclaw-secrets: Add missing `openclaw.extensions` field
|
|
267
|
-
- openclaw-google-chat: Fix poller not starting — registrationMode guard was blocking registerService
|
|
268
|
-
- agent-api-client: Add memory API methods, include Atlassian OAuth clientId/clientSecret in credentials
|
|
269
|
-
- All plugins: Remove registrationMode guard from activate() — OpenClaw handles mode filtering via registerService lifecycle
|
|
270
|
-
|
|
271
|
-
- Updated dependencies [6921672]
|
|
272
|
-
- @alfe.ai/agent-api-client@0.0.11
|
|
273
|
-
|
|
274
|
-
## 0.0.4
|
|
275
|
-
|
|
276
|
-
### Patch Changes
|
|
277
|
-
|
|
278
|
-
- 847f753: Add missing `openclaw.extensions` field to package.json so OpenClaw can install the plugin
|
|
279
|
-
|
|
280
|
-
## 0.0.3
|
|
281
|
-
|
|
282
|
-
### Patch Changes
|
|
283
|
-
|
|
284
|
-
- c6b4fb3: Read plugin version from package.json instead of hardcoding, remove stale version field from openclaw.plugin.json
|
|
285
|
-
|
|
286
|
-
## 0.0.2
|
|
287
|
-
|
|
288
|
-
### Patch Changes
|
|
289
|
-
|
|
290
|
-
- 08bf818: Fix zombie plugin registrations and incomplete plugin manifests
|
|
291
|
-
|
|
292
|
-
Plugin lifecycle fixes:
|
|
293
|
-
|
|
294
|
-
- Skip IPC/client init in management command context (no `api.runtime`) — openclaw + metrics
|
|
295
|
-
- Guard against duplicate activations via globalThis flag — openclaw + metrics
|
|
296
|
-
- Clean up stale IPC client on re-activation without prior deactivate
|
|
297
|
-
- Clear event listeners in IPCClient.stop() to prevent leaks
|
|
298
|
-
- Pass actual plugin ID to `registerWithDaemon` instead of hardcoding
|
|
299
|
-
|
|
300
|
-
Gateway deduplication:
|
|
301
|
-
|
|
302
|
-
- Deduplicate plugin registrations daemon-side (new connection evicts stale one with same name, two-pass loop)
|
|
303
|
-
|
|
304
|
-
Manifest fixes (missing fields that broke OpenClaw deduplication and entry resolution):
|
|
305
|
-
|
|
306
|
-
- openclaw-chat: add name, version, description, entry
|
|
307
|
-
- openclaw-metrics: add name, version, description, entry
|
|
308
|
-
- openclaw-sync: add id (was using name key), entry, description; fix version
|
|
309
|
-
- openclaw-memory-cloud: rename displayName → name, add entry
|