@memorilabs/openclaw-memori 0.0.5 → 0.0.6

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.
@@ -7,5 +7,16 @@ export declare const RECALL_CONFIG: {
7
7
  readonly MIN_PROMPT_LENGTH: 2;
8
8
  };
9
9
  export declare const AUGMENTATION_CONFIG: {
10
- readonly MAX_CONTEXT_MESSAGES: 5;
10
+ readonly MAX_CONTEXT_MESSAGES: 20;
11
+ };
12
+ export declare const MESSAGE_CONSTANTS: {
13
+ readonly SILENT_REPLY: "SILENT_REPLY";
14
+ readonly NO_REPLY: "NO_REPLY";
15
+ readonly SYNTHETIC_RESPONSE: "Okay, I'll remember that for you.";
16
+ };
17
+ export declare const ROLE: {
18
+ readonly USER: "user";
19
+ readonly ASSISTANT: "assistant";
20
+ readonly TOOL_RESULT: "toolResult";
21
+ readonly SYSTEM: "system";
11
22
  };
package/dist/constants.js CHANGED
@@ -7,5 +7,16 @@ export const RECALL_CONFIG = {
7
7
  MIN_PROMPT_LENGTH: 2,
8
8
  };
9
9
  export const AUGMENTATION_CONFIG = {
10
- MAX_CONTEXT_MESSAGES: 5,
10
+ MAX_CONTEXT_MESSAGES: 20,
11
+ };
12
+ export const MESSAGE_CONSTANTS = {
13
+ SILENT_REPLY: 'SILENT_REPLY',
14
+ NO_REPLY: 'NO_REPLY',
15
+ SYNTHETIC_RESPONSE: "Okay, I'll remember that for you.",
16
+ };
17
+ export const ROLE = {
18
+ USER: 'user',
19
+ ASSISTANT: 'assistant',
20
+ TOOL_RESULT: 'toolResult',
21
+ SYSTEM: 'system',
11
22
  };
@@ -1,3 +1,7 @@
1
1
  import { OpenClawEvent, OpenClawContext, MemoriPluginConfig } from '../types.js';
2
2
  import { MemoriLogger } from '../utils/index.js';
3
+ /**
4
+ * Main handler for augmentation hook.
5
+ * Extracts the latest conversation turn and sends it to Memori backend.
6
+ */
3
7
  export declare function handleAugmentation(event: OpenClawEvent, ctx: OpenClawContext, config: MemoriPluginConfig, logger: MemoriLogger): Promise<void>;
@@ -1,10 +1,13 @@
1
1
  import { extractContext, initializeMemoriClient } from '../utils/index.js';
2
2
  import { cleanText, isSystemMessage } from '../sanitizer.js';
3
- import { AUGMENTATION_CONFIG } from '../constants.js';
3
+ import { AUGMENTATION_CONFIG, MESSAGE_CONSTANTS, ROLE } from '../constants.js';
4
4
  import { SDK_VERSION } from '../version.js';
5
+ /**
6
+ * Extracts metadata about the LLM provider and model used during the turn.
7
+ */
5
8
  function extractLLMMetadata(event) {
6
9
  const messages = event.messages || [];
7
- const lastAssistant = messages.findLast((m) => m.role === 'assistant');
10
+ const lastAssistant = messages.findLast((m) => m.role === ROLE.ASSISTANT);
8
11
  return {
9
12
  provider: lastAssistant?.provider || null,
10
13
  model: lastAssistant?.model || null,
@@ -13,62 +16,157 @@ function extractLLMMetadata(event) {
13
16
  platform: 'openclaw',
14
17
  };
15
18
  }
19
+ /**
20
+ * Extracts all tool results from messages and maps them by tool call ID.
21
+ */
22
+ function extractToolResults(messages) {
23
+ const resultsMap = new Map();
24
+ for (const msg of messages) {
25
+ if (msg.role === ROLE.TOOL_RESULT && msg.toolCallId) {
26
+ resultsMap.set(msg.toolCallId, cleanText(msg.content));
27
+ }
28
+ }
29
+ return resultsMap;
30
+ }
31
+ /**
32
+ * Parses tool arguments from various formats into a structured object.
33
+ */
34
+ function parseToolArguments(rawArgs) {
35
+ if (typeof rawArgs === 'string') {
36
+ try {
37
+ return JSON.parse(rawArgs);
38
+ }
39
+ catch {
40
+ return {};
41
+ }
42
+ }
43
+ if (typeof rawArgs === 'object' && rawArgs !== null) {
44
+ return rawArgs;
45
+ }
46
+ return {};
47
+ }
48
+ /**
49
+ * Extracts tool calls from an assistant message.
50
+ * Iterates backward through content blocks to match original extraction order.
51
+ */
52
+ function extractToolCalls(msg, toolResults) {
53
+ if (!Array.isArray(msg.content)) {
54
+ return [];
55
+ }
56
+ const tools = [];
57
+ for (let i = msg.content.length - 1; i >= 0; i--) {
58
+ const block = msg.content[i];
59
+ if (block.type === 'toolCall' || block.type === 'tool_use') {
60
+ const rawArgs = block.arguments ?? block.input;
61
+ // Unshift to maintain chronological order within the message
62
+ tools.unshift({
63
+ name: block.name,
64
+ args: parseToolArguments(rawArgs),
65
+ result: toolResults.get(block.id) ?? null,
66
+ });
67
+ }
68
+ }
69
+ return tools;
70
+ }
71
+ /**
72
+ * Parses a user message, extracting the cleaned content.
73
+ */
74
+ function parseUserMessage(msg) {
75
+ const cleanedContent = cleanText(msg.content);
76
+ return cleanedContent ? { role: msg.role, content: cleanedContent } : null;
77
+ }
78
+ /**
79
+ * Parses the most recent conversation turn from messages.
80
+ * Walks backward to find the last user message and assistant response.
81
+ * Collects all tool calls from assistant messages in the turn.
82
+ */
83
+ function parseTurnFromMessages(messages) {
84
+ const tools = [];
85
+ const toolResults = extractToolResults(messages);
86
+ let userMessage = null;
87
+ let assistantMessage = null;
88
+ // Walk backwards to find the last complete turn
89
+ for (let i = messages.length - 1; i >= 0; i--) {
90
+ const msg = messages[i];
91
+ if (msg.role === ROLE.ASSISTANT) {
92
+ // Extract tool calls from ALL assistant messages in the turn
93
+ const extractedTools = extractToolCalls(msg, toolResults);
94
+ if (extractedTools.length > 0) {
95
+ // Prepend to maintain chronological order
96
+ tools.unshift(...extractedTools);
97
+ }
98
+ // Capture the text response from the FIRST (most recent) assistant message
99
+ if (!assistantMessage) {
100
+ const cleanedContent = cleanText(msg.content);
101
+ if (cleanedContent) {
102
+ assistantMessage = {
103
+ role: msg.role,
104
+ content: cleanedContent.replace(/^\[\[.*?\]\]\s*/, ''),
105
+ };
106
+ }
107
+ else if (extractedTools.length > 0) {
108
+ assistantMessage = { role: msg.role, content: MESSAGE_CONSTANTS.SILENT_REPLY };
109
+ }
110
+ }
111
+ }
112
+ else if (msg.role === ROLE.USER) {
113
+ userMessage = parseUserMessage(msg);
114
+ break; // Found the user message that started this turn
115
+ }
116
+ }
117
+ return { userMessage, assistantMessage, tools };
118
+ }
119
+ /**
120
+ * Builds the augmentation payload to send to Memori backend.
121
+ */
122
+ function buildAugmentationPayload(userMessage, agentResponse, tools, event) {
123
+ const payload = {
124
+ userMessage,
125
+ agentResponse,
126
+ metadata: extractLLMMetadata(event),
127
+ };
128
+ if (tools.length > 0) {
129
+ payload.trace = { tools };
130
+ }
131
+ return payload;
132
+ }
133
+ /**
134
+ * Helper to skip augmentation and log the reason.
135
+ */
136
+ function skipAugmentation(logger, reason) {
137
+ logger.info(reason);
138
+ logger.endSection('AUGMENTATION HOOK END');
139
+ }
140
+ /**
141
+ * Main handler for augmentation hook.
142
+ * Extracts the latest conversation turn and sends it to Memori backend.
143
+ */
16
144
  export async function handleAugmentation(event, ctx, config, logger) {
17
145
  logger.section('AUGMENTATION HOOK START');
18
146
  if (!event.success || !event.messages || event.messages.length < 2) {
19
- logger.info('No messages or unsuccessful event. Skipping augmentation.');
20
- logger.endSection('AUGMENTATION HOOK END');
147
+ skipAugmentation(logger, 'No messages or unsuccessful event. Skipping augmentation.');
21
148
  return;
22
149
  }
23
150
  try {
24
151
  const recentMessages = event.messages.slice(-AUGMENTATION_CONFIG.MAX_CONTEXT_MESSAGES);
25
- let lastUserMsg;
26
- let lastAiMsg;
27
- for (let i = recentMessages.length - 1; i >= 0; i--) {
28
- const msg = recentMessages[i];
29
- const role = msg.role;
30
- if (role !== 'user' && role !== 'assistant')
31
- continue;
32
- const cleanedContent = cleanText(msg.content);
33
- if (!cleanedContent)
34
- continue;
35
- let finalContent = cleanedContent;
36
- if (role === 'assistant') {
37
- finalContent = finalContent.replace(/^\[\[.*?\]\]\s*/, '');
38
- }
39
- if (role === 'assistant' && !lastAiMsg) {
40
- lastAiMsg = { role, content: finalContent };
41
- }
42
- if (role === 'user' && !lastUserMsg) {
43
- lastUserMsg = { role, content: finalContent };
44
- }
45
- if (lastUserMsg && lastAiMsg)
46
- break;
47
- }
48
- if (!lastUserMsg || !lastAiMsg) {
49
- logger.info('Missing user or assistant message. Skipping.');
50
- logger.endSection('AUGMENTATION HOOK END');
152
+ const turn = parseTurnFromMessages(recentMessages);
153
+ if (!turn.userMessage || !turn.assistantMessage) {
154
+ skipAugmentation(logger, 'Missing user or assistant message. Skipping.');
51
155
  return;
52
156
  }
53
- if (isSystemMessage(lastUserMsg.content)) {
54
- logger.info('User message is a system message. Skipping augmentation.');
55
- logger.endSection('AUGMENTATION HOOK END');
157
+ if (isSystemMessage(turn.userMessage.content)) {
158
+ skipAugmentation(logger, 'User message is a system message. Skipping augmentation.');
56
159
  return;
57
160
  }
58
- if (lastAiMsg.content === 'NO_REPLY' || lastAiMsg.content === 'SILENT_REPLY') {
161
+ // Resolve synthetic responses for pure-tool executions
162
+ if (turn.assistantMessage.content === MESSAGE_CONSTANTS.SILENT_REPLY ||
163
+ turn.assistantMessage.content === MESSAGE_CONSTANTS.NO_REPLY) {
59
164
  logger.info('Assistant used tool-based messaging. Using synthetic response.');
60
- lastAiMsg = {
61
- role: 'assistant',
62
- content: "Okay, I'll remember that for you.",
63
- };
165
+ turn.assistantMessage.content = MESSAGE_CONSTANTS.SYNTHETIC_RESPONSE;
64
166
  }
167
+ const payload = buildAugmentationPayload(turn.userMessage.content, turn.assistantMessage.content, turn.tools, event);
65
168
  const context = extractContext(event, ctx, config.entityId);
66
169
  const memoriClient = initializeMemoriClient(config.apiKey, context);
67
- const payload = {
68
- userMessage: lastUserMsg.content,
69
- agentResponse: lastAiMsg.content,
70
- metadata: extractLLMMetadata(event),
71
- };
72
170
  await memoriClient.augmentation(payload);
73
171
  logger.info('Augmentation successful!');
74
172
  }
package/dist/types.d.ts CHANGED
@@ -6,12 +6,18 @@ export interface OpenClawMessageBlock {
6
6
  type?: string;
7
7
  text?: string;
8
8
  thinking?: string;
9
+ name?: string;
10
+ id?: string;
11
+ arguments?: unknown;
9
12
  [key: string]: unknown;
10
13
  }
11
14
  export interface OpenClawMessage {
12
- role: 'user' | 'assistant' | 'system';
15
+ role: string;
13
16
  content: string | OpenClawMessageBlock[];
14
17
  timestamp?: number;
18
+ toolCallId?: string;
19
+ provider?: string;
20
+ model?: string;
15
21
  [key: string]: unknown;
16
22
  }
17
23
  export interface OpenClawEvent {
@@ -32,3 +38,19 @@ export interface OpenClawContext {
32
38
  workspaceDir?: string;
33
39
  messageProvider?: string;
34
40
  }
41
+ export interface ExtractedToolCall {
42
+ name: string;
43
+ args: Record<string, unknown>;
44
+ result: unknown;
45
+ }
46
+ export interface ParsedTurn {
47
+ userMessage: {
48
+ role: string;
49
+ content: string;
50
+ } | null;
51
+ assistantMessage: {
52
+ role: string;
53
+ content: string;
54
+ } | null;
55
+ tools: ExtractedToolCall[];
56
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.0.5";
1
+ export declare const SDK_VERSION = "0.0.6";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = '0.0.5';
1
+ export const SDK_VERSION = '0.0.6';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorilabs/openclaw-memori",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Official MemoriLabs.ai long-term memory plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -66,6 +66,6 @@
66
66
  "@hono/node-server": "^1.19.10"
67
67
  },
68
68
  "dependencies": {
69
- "@memorilabs/memori": "^0.0.6"
69
+ "@memorilabs/memori": "^0.0.8"
70
70
  }
71
71
  }