@memorilabs/openclaw-memori 0.0.5 → 0.0.6-beta
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 +123 -96
- package/dist/cli/commands.d.ts +2 -0
- package/dist/cli/commands.js +143 -0
- package/dist/cli/config-file.d.ts +8 -0
- package/dist/cli/config-file.js +64 -0
- package/dist/constants.d.ts +12 -1
- package/dist/constants.js +12 -1
- package/dist/handlers/augmentation.d.ts +4 -0
- package/dist/handlers/augmentation.js +150 -45
- package/dist/index.js +13 -4
- package/dist/sanitizer.d.ts +1 -0
- package/dist/sanitizer.js +10 -2
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +16 -0
- package/dist/tools/memori-compaction.d.ts +35 -0
- package/dist/tools/memori-compaction.js +119 -0
- package/dist/tools/memori-feedback.d.ts +25 -0
- package/dist/tools/memori-feedback.js +40 -0
- package/dist/tools/memori-quota.d.ts +17 -0
- package/dist/tools/memori-quota.js +55 -0
- package/dist/tools/memori-recall-summary.d.ts +39 -0
- package/dist/tools/memori-recall-summary.js +58 -0
- package/dist/tools/memori-recall.d.ts +51 -0
- package/dist/tools/memori-recall.js +123 -0
- package/dist/tools/memori-signup.d.ts +25 -0
- package/dist/tools/memori-signup.js +72 -0
- package/dist/tools/types.d.ts +8 -0
- package/dist/tools/types.js +1 -0
- package/dist/types.d.ts +19 -1
- package/dist/utils/context.d.ts +4 -2
- package/dist/utils/context.js +4 -2
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/memori-client.d.ts +11 -0
- package/dist/utils/memori-client.js +20 -2
- package/dist/utils/skills-loader.d.ts +6 -0
- package/dist/utils/skills-loader.js +14 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/openclaw.plugin.json +22 -2
- package/package.json +3 -2
- package/skills/clawhub/SKILL.md +221 -0
- package/skills/memori/SKILL.md +355 -0
- package/dist/handlers/recall.d.ts +0 -5
- package/dist/handlers/recall.js +0 -34
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { extractContext, initializeMemoriClient } from '../utils/index.js';
|
|
2
|
-
import { cleanText, isSystemMessage } from '../sanitizer.js';
|
|
3
|
-
import { AUGMENTATION_CONFIG } from '../constants.js';
|
|
2
|
+
import { cleanText, extractContentType, isSystemMessage } from '../sanitizer.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 ===
|
|
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,164 @@ 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
|
|
77
|
+
? { role: msg.role, content: cleanedContent, type: extractContentType(msg.content) }
|
|
78
|
+
: null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parses the most recent conversation turn from messages.
|
|
82
|
+
* Walks backward to find the last user message and assistant response.
|
|
83
|
+
* Collects all tool calls from assistant messages in the turn.
|
|
84
|
+
*/
|
|
85
|
+
function parseTurnFromMessages(messages) {
|
|
86
|
+
const tools = [];
|
|
87
|
+
const toolResults = extractToolResults(messages);
|
|
88
|
+
let userMessage = null;
|
|
89
|
+
let assistantMessage = null;
|
|
90
|
+
// Walk backwards to find the last complete turn
|
|
91
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
92
|
+
const msg = messages[i];
|
|
93
|
+
if (msg.role === ROLE.ASSISTANT) {
|
|
94
|
+
// Extract tool calls from ALL assistant messages in the turn
|
|
95
|
+
const extractedTools = extractToolCalls(msg, toolResults);
|
|
96
|
+
if (extractedTools.length > 0) {
|
|
97
|
+
// Prepend to maintain chronological order
|
|
98
|
+
tools.unshift(...extractedTools);
|
|
99
|
+
}
|
|
100
|
+
// Capture the text response from the FIRST (most recent) assistant message
|
|
101
|
+
if (!assistantMessage) {
|
|
102
|
+
const cleanedContent = cleanText(msg.content);
|
|
103
|
+
if (cleanedContent) {
|
|
104
|
+
assistantMessage = {
|
|
105
|
+
role: msg.role,
|
|
106
|
+
content: cleanedContent.replace(/^\[\[.*?\]\]\s*/, ''),
|
|
107
|
+
type: extractContentType(msg.content),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
else if (extractedTools.length > 0) {
|
|
111
|
+
assistantMessage = {
|
|
112
|
+
role: msg.role,
|
|
113
|
+
content: MESSAGE_CONSTANTS.SILENT_REPLY,
|
|
114
|
+
type: extractContentType(msg.content),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (msg.role === ROLE.USER) {
|
|
120
|
+
userMessage = parseUserMessage(msg);
|
|
121
|
+
break; // Found the user message that started this turn
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { userMessage, assistantMessage, tools };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Builds the augmentation payload to send to Memori backend.
|
|
128
|
+
*/
|
|
129
|
+
function buildAugmentationPayload(userMessage, agentResponse, tools, event) {
|
|
130
|
+
const payload = {
|
|
131
|
+
userMessage,
|
|
132
|
+
agentResponse,
|
|
133
|
+
metadata: extractLLMMetadata(event),
|
|
134
|
+
};
|
|
135
|
+
if (tools.length > 0) {
|
|
136
|
+
payload.trace = { tools };
|
|
137
|
+
}
|
|
138
|
+
return payload;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Helper to skip augmentation and log the reason.
|
|
142
|
+
*/
|
|
143
|
+
function skipAugmentation(logger, reason) {
|
|
144
|
+
logger.info(reason);
|
|
145
|
+
logger.endSection('AUGMENTATION HOOK END');
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Main handler for augmentation hook.
|
|
149
|
+
* Extracts the latest conversation turn and sends it to Memori backend.
|
|
150
|
+
*/
|
|
16
151
|
export async function handleAugmentation(event, ctx, config, logger) {
|
|
17
152
|
logger.section('AUGMENTATION HOOK START');
|
|
18
153
|
if (!event.success || !event.messages || event.messages.length < 2) {
|
|
19
|
-
logger
|
|
20
|
-
logger.endSection('AUGMENTATION HOOK END');
|
|
154
|
+
skipAugmentation(logger, 'No messages or unsuccessful event. Skipping augmentation.');
|
|
21
155
|
return;
|
|
22
156
|
}
|
|
23
157
|
try {
|
|
24
158
|
const recentMessages = event.messages.slice(-AUGMENTATION_CONFIG.MAX_CONTEXT_MESSAGES);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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');
|
|
159
|
+
const turn = parseTurnFromMessages(recentMessages);
|
|
160
|
+
if (!turn.userMessage || !turn.assistantMessage) {
|
|
161
|
+
skipAugmentation(logger, 'Missing user or assistant message. Skipping.');
|
|
51
162
|
return;
|
|
52
163
|
}
|
|
53
|
-
if (isSystemMessage(
|
|
54
|
-
logger
|
|
55
|
-
logger.endSection('AUGMENTATION HOOK END');
|
|
164
|
+
if (isSystemMessage(turn.userMessage.content)) {
|
|
165
|
+
skipAugmentation(logger, 'User message is a system message. Skipping augmentation.');
|
|
56
166
|
return;
|
|
57
167
|
}
|
|
58
|
-
|
|
168
|
+
// Resolve synthetic responses for pure-tool executions
|
|
169
|
+
if (turn.assistantMessage.content === MESSAGE_CONSTANTS.SILENT_REPLY ||
|
|
170
|
+
turn.assistantMessage.content === MESSAGE_CONSTANTS.NO_REPLY) {
|
|
59
171
|
logger.info('Assistant used tool-based messaging. Using synthetic response.');
|
|
60
|
-
|
|
61
|
-
role: 'assistant',
|
|
62
|
-
content: "Okay, I'll remember that for you.",
|
|
63
|
-
};
|
|
172
|
+
turn.assistantMessage.content = MESSAGE_CONSTANTS.SYNTHETIC_RESPONSE;
|
|
64
173
|
}
|
|
65
|
-
const
|
|
174
|
+
const payload = buildAugmentationPayload(turn.userMessage, turn.assistantMessage, turn.tools, event);
|
|
175
|
+
const context = extractContext(event, ctx, config.entityId, config.projectId);
|
|
66
176
|
const memoriClient = initializeMemoriClient(config.apiKey, context);
|
|
67
|
-
const payload = {
|
|
68
|
-
userMessage: lastUserMsg.content,
|
|
69
|
-
agentResponse: lastAiMsg.content,
|
|
70
|
-
metadata: extractLLMMetadata(event),
|
|
71
|
-
};
|
|
72
177
|
await memoriClient.augmentation(payload);
|
|
73
178
|
logger.info('Augmentation successful!');
|
|
74
179
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,35 @@
|
|
|
1
|
-
import { handleRecall } from './handlers/recall.js';
|
|
2
1
|
import { handleAugmentation } from './handlers/augmentation.js';
|
|
3
2
|
import { PLUGIN_CONFIG } from './constants.js';
|
|
4
|
-
import { MemoriLogger } from './utils/index.js';
|
|
3
|
+
import { MemoriLogger, loadSkillsContent } from './utils/index.js';
|
|
4
|
+
import { registerUtilityTools, registerAuthenticatedTools } from './tools/index.js';
|
|
5
|
+
import { registerCliCommands } from './cli/commands.js';
|
|
5
6
|
const memoriPlugin = {
|
|
6
7
|
id: PLUGIN_CONFIG.ID,
|
|
7
8
|
name: PLUGIN_CONFIG.NAME,
|
|
8
9
|
description: 'Hosted memory backend',
|
|
9
10
|
register(api) {
|
|
11
|
+
registerCliCommands(api);
|
|
10
12
|
const rawConfig = api.pluginConfig;
|
|
11
13
|
const config = {
|
|
12
14
|
apiKey: rawConfig?.apiKey,
|
|
13
15
|
entityId: rawConfig?.entityId,
|
|
16
|
+
projectId: rawConfig?.projectId,
|
|
14
17
|
};
|
|
18
|
+
const logger = new MemoriLogger(api);
|
|
19
|
+
const skillsContent = loadSkillsContent(api.resolvePath.bind(api));
|
|
20
|
+
registerUtilityTools({ api, config, logger });
|
|
15
21
|
if (!config.apiKey || !config.entityId) {
|
|
16
22
|
api.logger.warn(`${PLUGIN_CONFIG.LOG_PREFIX} Missing apiKey or entityId in config. Plugin disabled.`);
|
|
17
23
|
return;
|
|
18
24
|
}
|
|
19
|
-
const logger = new MemoriLogger(api);
|
|
20
25
|
logger.info(`\n=== ${PLUGIN_CONFIG.LOG_PREFIX} INITIALIZING PLUGIN ===`);
|
|
21
26
|
logger.info(`${PLUGIN_CONFIG.LOG_PREFIX} Tracking Entity ID: ${config.entityId}`);
|
|
22
|
-
|
|
27
|
+
const configContext = `Memori plugin configuration: projectId="${config.projectId}", entityId="${config.entityId}"`;
|
|
28
|
+
api.on('before_prompt_build', () => ({
|
|
29
|
+
appendSystemContext: [skillsContent, configContext].filter(Boolean).join('\n\n'),
|
|
30
|
+
}));
|
|
23
31
|
api.on('agent_end', (event, ctx) => handleAugmentation(event, ctx, config, logger));
|
|
32
|
+
registerAuthenticatedTools({ api, config, logger });
|
|
24
33
|
},
|
|
25
34
|
};
|
|
26
35
|
export default memoriPlugin;
|
package/dist/sanitizer.d.ts
CHANGED
package/dist/sanitizer.js
CHANGED
|
@@ -19,7 +19,7 @@ export function isSystemMessage(text) {
|
|
|
19
19
|
*
|
|
20
20
|
* OpenClaw wraps metadata in markdown code fences (triple tick).
|
|
21
21
|
* The actual message is always after the LAST closing fence.
|
|
22
|
-
* The message might
|
|
22
|
+
* The message might also contain a timestamp prefix like: [Day YYYY-MM-DD HH:MM TZ], that will need to be removed
|
|
23
23
|
*/
|
|
24
24
|
function extractRawUserMessage(content) {
|
|
25
25
|
let message = content;
|
|
@@ -49,11 +49,19 @@ function extractMessageText(content) {
|
|
|
49
49
|
}
|
|
50
50
|
return '';
|
|
51
51
|
}
|
|
52
|
+
export function extractContentType(rawContent) {
|
|
53
|
+
if (typeof rawContent === 'string' || !rawContent)
|
|
54
|
+
return 'text';
|
|
55
|
+
if (isMessageBlockArray(rawContent)) {
|
|
56
|
+
const primary = rawContent.find((block) => (block.type === 'text' || typeof block.text === 'string') && block.text);
|
|
57
|
+
return primary?.type ?? 'text';
|
|
58
|
+
}
|
|
59
|
+
return 'text';
|
|
60
|
+
}
|
|
52
61
|
export function cleanText(rawContent) {
|
|
53
62
|
let text = extractMessageText(rawContent);
|
|
54
63
|
if (!text)
|
|
55
64
|
return '';
|
|
56
65
|
text = extractRawUserMessage(text);
|
|
57
|
-
text = text.replace(/<memori_context>[\s\S]*?<\/memori_context>\s*/g, '');
|
|
58
66
|
return text.trim();
|
|
59
67
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createMemoriSignupTool } from './memori-signup.js';
|
|
2
|
+
import { createMemoriQuotaTool } from './memori-quota.js';
|
|
3
|
+
import { createMemoriRecallTool } from './memori-recall.js';
|
|
4
|
+
import { createMemoriRecallSummaryTool } from './memori-recall-summary.js';
|
|
5
|
+
import { createMemoriFeedbackTool } from './memori-feedback.js';
|
|
6
|
+
import { createMemoriCompactionTool } from './memori-compaction.js';
|
|
7
|
+
export function registerUtilityTools(deps) {
|
|
8
|
+
deps.api.registerTool(createMemoriSignupTool(deps));
|
|
9
|
+
}
|
|
10
|
+
export function registerAuthenticatedTools(deps) {
|
|
11
|
+
deps.api.registerTool(createMemoriRecallTool(deps));
|
|
12
|
+
deps.api.registerTool(createMemoriRecallSummaryTool(deps));
|
|
13
|
+
deps.api.registerTool(createMemoriFeedbackTool(deps));
|
|
14
|
+
deps.api.registerTool(createMemoriQuotaTool(deps));
|
|
15
|
+
deps.api.registerTool(createMemoriCompactionTool(deps));
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ToolDeps } from './types.js';
|
|
2
|
+
export declare function createMemoriCompactionTool(deps: ToolDeps): {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
required: string[];
|
|
9
|
+
properties: {
|
|
10
|
+
projectId: {
|
|
11
|
+
type: string;
|
|
12
|
+
description: string;
|
|
13
|
+
};
|
|
14
|
+
sessionId: {
|
|
15
|
+
type: string;
|
|
16
|
+
description: string;
|
|
17
|
+
};
|
|
18
|
+
numMessages: {
|
|
19
|
+
type: string;
|
|
20
|
+
description: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
execute(_toolCallId: string, params: {
|
|
25
|
+
projectId?: string;
|
|
26
|
+
sessionId?: string;
|
|
27
|
+
numMessages?: number;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
content: {
|
|
30
|
+
type: "text";
|
|
31
|
+
text: string;
|
|
32
|
+
}[];
|
|
33
|
+
details: null;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { QuotaExceededError } from '@memorilabs/memori';
|
|
2
|
+
import { createRecallClient } from '../utils/memori-client.js';
|
|
3
|
+
export function createMemoriCompactionTool(deps) {
|
|
4
|
+
const { config, logger } = deps;
|
|
5
|
+
return {
|
|
6
|
+
name: 'memori_compaction',
|
|
7
|
+
label: 'Compact Agent Memory',
|
|
8
|
+
description: `Use this tool to restore working state after context compaction. Returns a structured snapshot of the agent's long-term memory to enable continuation without replaying the full prior session.
|
|
9
|
+
|
|
10
|
+
WHEN TO USE:
|
|
11
|
+
- The agent resumes after compaction
|
|
12
|
+
- A long-running workflow has lost conversational detail
|
|
13
|
+
- The agent needs to continue operational work without replaying the full prior session
|
|
14
|
+
- The agent needs durable state, standing instructions, environment details, open loops, or the next expected action
|
|
15
|
+
|
|
16
|
+
WHEN NOT TO USE:
|
|
17
|
+
- Do NOT call this on every turn — it costs 100 memory credits per execution
|
|
18
|
+
- Do NOT use this for targeted memory search — use memori_recall for that instead
|
|
19
|
+
- Do NOT call this if the agent is starting a brand-new task with no prior context
|
|
20
|
+
- Compaction is not a replacement for precise memory retrieval
|
|
21
|
+
|
|
22
|
+
The compaction result returns:
|
|
23
|
+
- **environment**: environment variable context captured during prior sessions
|
|
24
|
+
- **standing_orders**: persistent instructions the agent must continue to follow
|
|
25
|
+
- **state**: active_tasks (work in progress), open_loops (unresolved threads), pending_results
|
|
26
|
+
- **timeline**: a chronological narrative of agent activity (when available)
|
|
27
|
+
- **workspace_changes**: recent file or system changes made by the agent
|
|
28
|
+
- **continuation**: last_action (what the agent did last) and next_expected_action (what it should do next)
|
|
29
|
+
- **messages**: a tail of recent conversation messages for continuity
|
|
30
|
+
|
|
31
|
+
HOW TO USE THE RESULT:
|
|
32
|
+
Treat the compaction result as the agent's resume state — use it to understand what environment the agent was operating in, which standing orders must continue to be followed, which tasks are active, what happened across the prior session window, and what the agent should do next.
|
|
33
|
+
|
|
34
|
+
The compaction result should guide continuation, not override explicit user instructions. Before acting on operational details, verify any state that may have changed since compaction.
|
|
35
|
+
|
|
36
|
+
Pay special attention to:
|
|
37
|
+
- Standing orders
|
|
38
|
+
- Hard constraints
|
|
39
|
+
- Alerting rules
|
|
40
|
+
- Expected response formats
|
|
41
|
+
- Open loops
|
|
42
|
+
- Staleness warnings
|
|
43
|
+
- Next expected action
|
|
44
|
+
|
|
45
|
+
If the compaction result contains a required output format, follow it exactly unless the user gives a newer instruction.`,
|
|
46
|
+
parameters: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
required: ['projectId'],
|
|
49
|
+
properties: {
|
|
50
|
+
projectId: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'The project to compact. REQUIRED — always pass the configured project ID. This scopes the compaction to the correct workspace.',
|
|
53
|
+
},
|
|
54
|
+
sessionId: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'Scope the compaction to a specific agent session. Leave empty to compact across all sessions in the project. Cannot be used without projectId.',
|
|
57
|
+
},
|
|
58
|
+
numMessages: {
|
|
59
|
+
type: 'number',
|
|
60
|
+
description: 'Number of recent conversation messages to include in the result. Defaults to 5. Increase (up to ~20) only if the user explicitly asks for more conversation context.',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
async execute(_toolCallId, params) {
|
|
65
|
+
try {
|
|
66
|
+
// Config projectId is the fallback; an explicit LLM-provided value overrides it.
|
|
67
|
+
const finalParams = { projectId: config.projectId, ...params };
|
|
68
|
+
if (!finalParams.projectId) {
|
|
69
|
+
const errorResult = { error: 'projectId is required but was not configured' };
|
|
70
|
+
logger.warn(`memori_compaction rejected: ${JSON.stringify(errorResult)}`);
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
73
|
+
details: null,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (finalParams.sessionId && !finalParams.projectId) {
|
|
77
|
+
const errorResult = { error: 'sessionId cannot be provided without projectId' };
|
|
78
|
+
logger.warn(`memori_compaction rejected: ${JSON.stringify(errorResult)}`);
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
81
|
+
details: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
logger.info(`memori_compaction params: ${JSON.stringify(finalParams)}`);
|
|
85
|
+
const client = createRecallClient(config.apiKey, config.entityId);
|
|
86
|
+
const result = await client.agentCompaction(finalParams);
|
|
87
|
+
if (result === null) {
|
|
88
|
+
const errorResult = { error: 'Compaction failed' };
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
91
|
+
details: null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
96
|
+
details: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
if (e instanceof QuotaExceededError) {
|
|
101
|
+
logger.warn('memori_compaction quota exceeded');
|
|
102
|
+
const errorResult = {
|
|
103
|
+
error: 'Quota exceeded: compaction costs 100 memory credits and your organization has exhausted its Recall Execution quota.',
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
107
|
+
details: null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
logger.warn(`memori_compaction failed: ${String(e)}`);
|
|
111
|
+
const errorResult = { error: 'Compaction failed' };
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
114
|
+
details: null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ToolDeps } from './types.js';
|
|
2
|
+
export declare function createMemoriFeedbackTool(deps: ToolDeps): {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
content: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
required: string[];
|
|
15
|
+
};
|
|
16
|
+
execute(_toolCallId: string, params: {
|
|
17
|
+
content: string;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
content: {
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
details: null;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRecallClient } from '../utils/memori-client.js';
|
|
2
|
+
export function createMemoriFeedbackTool(deps) {
|
|
3
|
+
const { config, logger } = deps;
|
|
4
|
+
return {
|
|
5
|
+
name: 'memori_feedback',
|
|
6
|
+
label: 'Memori Feedback',
|
|
7
|
+
description: 'CRITICAL: You MUST use this tool immediately whenever the user asks you to send feedback, report a bug, suggest a feature, or complain about Memori. Send feedback directly to the Memori team (positive or negative).',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
content: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'REQUIRED: The feedback message to send.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
required: ['content'],
|
|
17
|
+
},
|
|
18
|
+
async execute(_toolCallId, params) {
|
|
19
|
+
try {
|
|
20
|
+
logger.info(`memori_feedback sending: ${params.content}`);
|
|
21
|
+
const client = createRecallClient(config.apiKey, config.entityId);
|
|
22
|
+
await client.agentFeedback(params.content);
|
|
23
|
+
const result = { success: true, message: 'Feedback sent successfully.' };
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
26
|
+
details: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
logger.warn(`memori_feedback failed: ${String(e)}`);
|
|
31
|
+
const errorResult = { error: 'Feedback failed to send.' };
|
|
32
|
+
logger.info(`memori_feedback error result: ${JSON.stringify(errorResult)}`);
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
35
|
+
details: null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ToolDeps } from './types.js';
|
|
2
|
+
export declare function createMemoriQuotaTool(deps: ToolDeps): {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {};
|
|
9
|
+
};
|
|
10
|
+
execute(_toolCallId: string): Promise<{
|
|
11
|
+
content: {
|
|
12
|
+
type: "text";
|
|
13
|
+
text: string;
|
|
14
|
+
}[];
|
|
15
|
+
details: null;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
export function createMemoriQuotaTool(deps) {
|
|
7
|
+
const { logger } = deps;
|
|
8
|
+
return {
|
|
9
|
+
name: 'memori_quota',
|
|
10
|
+
label: 'Memori Quota',
|
|
11
|
+
description: 'Retrieves the current memory usage and maximum allowed quota for the user. Use this when the user asks about their limits, storage, or how many memories they have left — or when you encounter errors suggesting memory limits have been reached and want to confirm before degrading behavior.',
|
|
12
|
+
parameters: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {},
|
|
15
|
+
},
|
|
16
|
+
async execute(_toolCallId) {
|
|
17
|
+
try {
|
|
18
|
+
logger.info('memori_quota checking usage...');
|
|
19
|
+
const tmpDir = os.tmpdir();
|
|
20
|
+
await execAsync(`npm install --prefix ${tmpDir} --no-save @memorilabs/memori@0.1.12-beta`);
|
|
21
|
+
const binPath = path.join(tmpDir, 'node_modules', '.bin', 'memori');
|
|
22
|
+
const { stdout } = await execAsync(`${binPath} quota`);
|
|
23
|
+
const result = {
|
|
24
|
+
success: true,
|
|
25
|
+
message: stdout.trim(),
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
29
|
+
details: null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
logger.warn(`memori_quota CLI failed: ${String(e)}`);
|
|
34
|
+
let output = 'An unexpected error occurred while trying to fetch quota via the CLI.';
|
|
35
|
+
if (typeof e === 'object' && e !== null) {
|
|
36
|
+
const errObj = e;
|
|
37
|
+
const stdout = typeof errObj.stdout === 'string' ? errObj.stdout.trim() : '';
|
|
38
|
+
const stderr = typeof errObj.stderr === 'string' ? errObj.stderr.trim() : '';
|
|
39
|
+
const msg = typeof errObj.message === 'string' ? errObj.message : '';
|
|
40
|
+
output = stdout || stderr || msg || output;
|
|
41
|
+
}
|
|
42
|
+
else if (typeof e === 'string') {
|
|
43
|
+
output = e;
|
|
44
|
+
}
|
|
45
|
+
const errorResult = {
|
|
46
|
+
error: output,
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
50
|
+
details: null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ToolDeps } from './types.js';
|
|
2
|
+
export declare function createMemoriRecallSummaryTool(deps: ToolDeps): {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
dateStart: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
dateEnd: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
projectId: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
sessionId: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
execute(_toolCallId: string, params: {
|
|
28
|
+
dateStart?: string;
|
|
29
|
+
dateEnd?: string;
|
|
30
|
+
projectId?: string;
|
|
31
|
+
sessionId?: string;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
content: {
|
|
34
|
+
type: "text";
|
|
35
|
+
text: string;
|
|
36
|
+
}[];
|
|
37
|
+
details: null;
|
|
38
|
+
}>;
|
|
39
|
+
};
|