@memoryrelay/plugin-memoryrelay-ai 0.11.4 → 0.12.0
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/index.ts +275 -9
- package/openclaw.plugin.json +2 -2
- package/package.json +5 -3
package/index.ts
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenClaw Memory Plugin - MemoryRelay
|
|
3
|
-
* Version: 0.
|
|
3
|
+
* Version: 0.12.0 (Phase 1 - Adoption Framework)
|
|
4
4
|
*
|
|
5
5
|
* Long-term memory with vector search using MemoryRelay API.
|
|
6
6
|
* Provides auto-recall and auto-capture via lifecycle hooks.
|
|
7
7
|
* Includes: memories, entities, agents, sessions, decisions, patterns, projects.
|
|
8
|
+
* New in v0.12.0: Smart auto-capture, daily stats, CLI commands, onboarding
|
|
8
9
|
*
|
|
9
10
|
* API: https://api.memoryrelay.net
|
|
10
11
|
* Docs: https://memoryrelay.ai
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
15
|
+
import {
|
|
16
|
+
calculateStats,
|
|
17
|
+
morningCheck,
|
|
18
|
+
eveningReview,
|
|
19
|
+
shouldRunHeartbeat,
|
|
20
|
+
formatStatsForDisplay,
|
|
21
|
+
type DailyStatsConfig,
|
|
22
|
+
type MemoryStats,
|
|
23
|
+
} from "./src/heartbeat/daily-stats.js";
|
|
24
|
+
import {
|
|
25
|
+
statsCommand,
|
|
26
|
+
type StatsCommandOptions,
|
|
27
|
+
} from "./src/cli/stats-command.js";
|
|
28
|
+
import {
|
|
29
|
+
checkFirstRun,
|
|
30
|
+
generateOnboardingPrompt,
|
|
31
|
+
generateSuccessMessage,
|
|
32
|
+
runSimpleOnboarding,
|
|
33
|
+
type OnboardingResult,
|
|
34
|
+
} from "./src/onboarding/first-run.js";
|
|
14
35
|
|
|
15
36
|
// ============================================================================
|
|
16
37
|
// Constants
|
|
@@ -131,7 +152,7 @@ interface MemoryStats {
|
|
|
131
152
|
interface PluginConfig {
|
|
132
153
|
agentId: string;
|
|
133
154
|
autoRecall: boolean;
|
|
134
|
-
autoCapture:
|
|
155
|
+
autoCapture: AutoCaptureConfig; // Updated in v0.12.0
|
|
135
156
|
recallLimit: number;
|
|
136
157
|
recallThreshold: number;
|
|
137
158
|
excludeChannels: string[];
|
|
@@ -283,7 +304,10 @@ class StatusReporter {
|
|
|
283
304
|
? `✓ Enabled (limit: ${report.config.recallLimit}, threshold: ${report.config.recallThreshold})`
|
|
284
305
|
: "✗ Disabled";
|
|
285
306
|
lines.push(` Auto-Recall: ${recallStatus}`);
|
|
286
|
-
|
|
307
|
+
const captureStatus = report.config.autoCapture.enabled
|
|
308
|
+
? `✓ Enabled (tier: ${report.config.autoCapture.tier})`
|
|
309
|
+
: "✗ Disabled";
|
|
310
|
+
lines.push(` Auto-Capture: ${captureStatus}`);
|
|
287
311
|
if (report.config.defaultProject) {
|
|
288
312
|
lines.push(` Default Project: ${report.config.defaultProject}`);
|
|
289
313
|
}
|
|
@@ -377,17 +401,35 @@ class StatusReporter {
|
|
|
377
401
|
}
|
|
378
402
|
}
|
|
379
403
|
|
|
404
|
+
// Auto-capture configuration types (Phase 1 - Issue #12)
|
|
405
|
+
type AutoCaptureTier = "off" | "conservative" | "smart" | "aggressive";
|
|
406
|
+
|
|
407
|
+
interface AutoCaptureConfig {
|
|
408
|
+
enabled: boolean;
|
|
409
|
+
tier: AutoCaptureTier;
|
|
410
|
+
confirmFirst?: number; // Number of captures to confirm (default: 5)
|
|
411
|
+
categories?: {
|
|
412
|
+
credentials?: boolean;
|
|
413
|
+
preferences?: boolean;
|
|
414
|
+
technical?: boolean;
|
|
415
|
+
personal?: boolean;
|
|
416
|
+
};
|
|
417
|
+
blocklist?: string[]; // Regex patterns to never capture
|
|
418
|
+
}
|
|
419
|
+
|
|
380
420
|
interface MemoryRelayConfig {
|
|
381
421
|
apiKey?: string;
|
|
382
422
|
agentId?: string;
|
|
383
423
|
apiUrl?: string;
|
|
384
|
-
autoCapture?: boolean;
|
|
424
|
+
autoCapture?: boolean | AutoCaptureConfig; // Enhanced in v0.12.0
|
|
385
425
|
autoRecall?: boolean;
|
|
386
426
|
recallLimit?: number;
|
|
387
427
|
recallThreshold?: number;
|
|
388
428
|
excludeChannels?: string[];
|
|
389
429
|
defaultProject?: string;
|
|
390
430
|
enabledTools?: string;
|
|
431
|
+
// Daily stats configuration (v0.12.0)
|
|
432
|
+
dailyStats?: DailyStatsConfig;
|
|
391
433
|
// Debug and logging options (v0.8.0)
|
|
392
434
|
debug?: boolean;
|
|
393
435
|
verbose?: boolean;
|
|
@@ -471,6 +513,100 @@ async function fetchWithTimeout(
|
|
|
471
513
|
}
|
|
472
514
|
}
|
|
473
515
|
|
|
516
|
+
// ============================================================================
|
|
517
|
+
// Auto-Capture Configuration Helpers (Phase 1 - Issue #12)
|
|
518
|
+
// ============================================================================
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Normalize auto-capture config from boolean or object format
|
|
522
|
+
*/
|
|
523
|
+
function normalizeAutoCaptureConfig(
|
|
524
|
+
config: boolean | AutoCaptureConfig | undefined
|
|
525
|
+
): AutoCaptureConfig {
|
|
526
|
+
// Default configuration (smart auto-capture enabled by default in v0.12.0)
|
|
527
|
+
const defaultConfig: AutoCaptureConfig = {
|
|
528
|
+
enabled: true,
|
|
529
|
+
tier: "smart",
|
|
530
|
+
confirmFirst: 5,
|
|
531
|
+
categories: {
|
|
532
|
+
credentials: true,
|
|
533
|
+
preferences: true,
|
|
534
|
+
technical: true,
|
|
535
|
+
personal: false, // Privacy: personal info requires confirmation
|
|
536
|
+
},
|
|
537
|
+
blocklist: [
|
|
538
|
+
// Privacy patterns - never auto-capture
|
|
539
|
+
/password\s*[:=]\s*[^\s]+/i,
|
|
540
|
+
/credit\s*card/i,
|
|
541
|
+
/ssn\s*[:=]/i,
|
|
542
|
+
/social\s*security/i,
|
|
543
|
+
].map((r) => r.source),
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// Handle legacy boolean config
|
|
547
|
+
if (typeof config === "boolean") {
|
|
548
|
+
return {
|
|
549
|
+
...defaultConfig,
|
|
550
|
+
enabled: config,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Handle undefined (use smart default in v0.12.0+)
|
|
555
|
+
if (config === undefined) {
|
|
556
|
+
return defaultConfig;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Merge provided config with defaults
|
|
560
|
+
return {
|
|
561
|
+
enabled: config.enabled ?? defaultConfig.enabled,
|
|
562
|
+
tier: config.tier ?? defaultConfig.tier,
|
|
563
|
+
confirmFirst: config.confirmFirst ?? defaultConfig.confirmFirst,
|
|
564
|
+
categories: {
|
|
565
|
+
...defaultConfig.categories,
|
|
566
|
+
...config.categories,
|
|
567
|
+
},
|
|
568
|
+
blocklist: config.blocklist ?? defaultConfig.blocklist,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Check if content matches any blocklist patterns
|
|
574
|
+
*/
|
|
575
|
+
function isBlocklisted(content: string, blocklist: string[]): boolean {
|
|
576
|
+
return blocklist.some((pattern) => {
|
|
577
|
+
try {
|
|
578
|
+
return new RegExp(pattern, "i").test(content);
|
|
579
|
+
} catch {
|
|
580
|
+
return false; // Invalid regex, skip
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Mask sensitive data in content (API keys, tokens, etc.)
|
|
587
|
+
*/
|
|
588
|
+
function maskSensitiveData(content: string): string {
|
|
589
|
+
// Mask API keys (show only last 4 chars)
|
|
590
|
+
content = content.replace(
|
|
591
|
+
/\b([a-z]{2,}_)?([a-z]{4,}_)?[a-f0-9]{32,}\b/gi,
|
|
592
|
+
(match) => {
|
|
593
|
+
if (match.length <= 8) return match;
|
|
594
|
+
return `${match.slice(0, 4)}...${match.slice(-4)}`;
|
|
595
|
+
}
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
// Mask email addresses (show only domain)
|
|
599
|
+
content = content.replace(
|
|
600
|
+
/\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/gi,
|
|
601
|
+
(match) => {
|
|
602
|
+
const domain = match.split("@")[1];
|
|
603
|
+
return `***@${domain}`;
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
return content;
|
|
608
|
+
}
|
|
609
|
+
|
|
474
610
|
// ============================================================================
|
|
475
611
|
// MemoryRelay API Client (Full Suite)
|
|
476
612
|
// ============================================================================
|
|
@@ -1208,11 +1344,13 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
1208
1344
|
total_memories: memoryCount,
|
|
1209
1345
|
};
|
|
1210
1346
|
|
|
1211
|
-
// Get config
|
|
1347
|
+
// Get config - normalize autoCapture to new format
|
|
1348
|
+
const autoCaptureConfig = normalizeAutoCaptureConfig(cfg?.autoCapture);
|
|
1349
|
+
|
|
1212
1350
|
const pluginConfig = {
|
|
1213
1351
|
agentId: agentId,
|
|
1214
1352
|
autoRecall: cfg?.autoRecall ?? true,
|
|
1215
|
-
autoCapture:
|
|
1353
|
+
autoCapture: autoCaptureConfig,
|
|
1216
1354
|
recallLimit: cfg?.recallLimit ?? 5,
|
|
1217
1355
|
recallThreshold: cfg?.recallThreshold ?? 0.3,
|
|
1218
1356
|
excludeChannels: cfg?.excludeChannels ?? [],
|
|
@@ -3641,7 +3779,9 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
3641
3779
|
});
|
|
3642
3780
|
|
|
3643
3781
|
// Auto-capture: analyze and store important information after agent ends
|
|
3644
|
-
|
|
3782
|
+
const autoCaptureConfig = normalizeAutoCaptureConfig(cfg?.autoCapture);
|
|
3783
|
+
|
|
3784
|
+
if (autoCaptureConfig.enabled) {
|
|
3645
3785
|
api.on("agent_end", async (event) => {
|
|
3646
3786
|
if (!event.success || !event.messages || event.messages.length === 0) {
|
|
3647
3787
|
return;
|
|
@@ -3673,7 +3813,13 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
3673
3813
|
}
|
|
3674
3814
|
}
|
|
3675
3815
|
|
|
3676
|
-
const toCapture = texts.filter((text) =>
|
|
3816
|
+
const toCapture = texts.filter((text) => {
|
|
3817
|
+
if (!text || !shouldCapture(text)) return false;
|
|
3818
|
+
// Check blocklist
|
|
3819
|
+
if (isBlocklisted(text, autoCaptureConfig.blocklist || [])) return false;
|
|
3820
|
+
return true;
|
|
3821
|
+
});
|
|
3822
|
+
|
|
3677
3823
|
if (toCapture.length === 0) return;
|
|
3678
3824
|
|
|
3679
3825
|
let stored = 0;
|
|
@@ -3696,9 +3842,43 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
3696
3842
|
}
|
|
3697
3843
|
|
|
3698
3844
|
api.logger.info?.(
|
|
3699
|
-
`memory-memoryrelay: plugin v0.
|
|
3845
|
+
`memory-memoryrelay: plugin v0.12.0 loaded (39 tools, autoRecall: ${cfg?.autoRecall}, autoCapture: ${autoCaptureConfig.enabled ? autoCaptureConfig.tier : 'off'}, debug: ${debugEnabled})`,
|
|
3700
3846
|
);
|
|
3701
3847
|
|
|
3848
|
+
// ========================================================================
|
|
3849
|
+
// First-Run Onboarding (Phase 1 - Issue #9)
|
|
3850
|
+
// ========================================================================
|
|
3851
|
+
|
|
3852
|
+
// Check if this is the first run and auto-onboard if needed
|
|
3853
|
+
try {
|
|
3854
|
+
const onboardingCheck = await checkFirstRun(async () => {
|
|
3855
|
+
const memories = await client.list(1);
|
|
3856
|
+
return memories.length;
|
|
3857
|
+
});
|
|
3858
|
+
|
|
3859
|
+
if (onboardingCheck.shouldOnboard) {
|
|
3860
|
+
// Auto-onboard with simple setup
|
|
3861
|
+
await runSimpleOnboarding(
|
|
3862
|
+
async (content, metadata) => {
|
|
3863
|
+
const memory = await client.store(content, metadata || {});
|
|
3864
|
+
return { id: memory.id };
|
|
3865
|
+
},
|
|
3866
|
+
"Welcome to MemoryRelay! This is your first memory. Use memory_store to add more.",
|
|
3867
|
+
autoCaptureConfig.enabled
|
|
3868
|
+
);
|
|
3869
|
+
|
|
3870
|
+
const successMsg = generateSuccessMessage(
|
|
3871
|
+
"Welcome to MemoryRelay! This is your first memory.",
|
|
3872
|
+
autoCaptureConfig.enabled
|
|
3873
|
+
);
|
|
3874
|
+
|
|
3875
|
+
api.logger.info?.(`\n${successMsg}`);
|
|
3876
|
+
}
|
|
3877
|
+
} catch (err) {
|
|
3878
|
+
// Don't fail plugin load if onboarding fails
|
|
3879
|
+
api.logger.warn?.(`memory-memoryrelay: onboarding check failed: ${String(err)}`);
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3702
3882
|
// ========================================================================
|
|
3703
3883
|
// CLI Helper Tools (v0.8.0)
|
|
3704
3884
|
// ========================================================================
|
|
@@ -3860,6 +4040,92 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
3860
4040
|
});
|
|
3861
4041
|
}
|
|
3862
4042
|
|
|
4043
|
+
// memoryrelay:heartbeat - Daily stats check (Phase 1 - Issue #10)
|
|
4044
|
+
api.registerGatewayMethod?.("memoryrelay.heartbeat", async ({ respond, args }) => {
|
|
4045
|
+
try {
|
|
4046
|
+
const dailyStatsConfig: DailyStatsConfig = {
|
|
4047
|
+
enabled: cfg?.dailyStats?.enabled ?? true,
|
|
4048
|
+
morningTime: cfg?.dailyStats?.morningTime || "09:00",
|
|
4049
|
+
eveningTime: cfg?.dailyStats?.eveningTime || "20:00",
|
|
4050
|
+
};
|
|
4051
|
+
|
|
4052
|
+
// Check if it's time for a heartbeat
|
|
4053
|
+
const heartbeatType = shouldRunHeartbeat(dailyStatsConfig);
|
|
4054
|
+
|
|
4055
|
+
if (!heartbeatType) {
|
|
4056
|
+
respond(true, {
|
|
4057
|
+
type: "none",
|
|
4058
|
+
message: "Not scheduled for heartbeat check right now",
|
|
4059
|
+
});
|
|
4060
|
+
return;
|
|
4061
|
+
}
|
|
4062
|
+
|
|
4063
|
+
// Calculate stats
|
|
4064
|
+
const memories = await client.list(1000); // Get recent memories
|
|
4065
|
+
const stats = await calculateStats(
|
|
4066
|
+
async () => memories,
|
|
4067
|
+
() => 0 // Recall count not tracked yet (Phase 3)
|
|
4068
|
+
);
|
|
4069
|
+
|
|
4070
|
+
// Run appropriate check
|
|
4071
|
+
let result;
|
|
4072
|
+
if (heartbeatType === "morning") {
|
|
4073
|
+
result = await morningCheck(stats);
|
|
4074
|
+
} else {
|
|
4075
|
+
result = await eveningReview(stats);
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
respond(true, {
|
|
4079
|
+
type: heartbeatType,
|
|
4080
|
+
shouldNotify: result.shouldNotify,
|
|
4081
|
+
message: result.message,
|
|
4082
|
+
stats: result.stats,
|
|
4083
|
+
});
|
|
4084
|
+
} catch (err) {
|
|
4085
|
+
respond(false, { error: String(err) });
|
|
4086
|
+
}
|
|
4087
|
+
});
|
|
4088
|
+
|
|
4089
|
+
// memoryrelay:onboarding - Show onboarding prompt (Phase 1 - Issue #9)
|
|
4090
|
+
api.registerGatewayMethod?.("memoryrelay.onboarding", async ({ respond }) => {
|
|
4091
|
+
try {
|
|
4092
|
+
const onboardingCheck = await checkFirstRun(async () => {
|
|
4093
|
+
const memories = await client.list(1);
|
|
4094
|
+
return memories.length;
|
|
4095
|
+
});
|
|
4096
|
+
|
|
4097
|
+
const prompt = generateOnboardingPrompt();
|
|
4098
|
+
|
|
4099
|
+
respond(true, {
|
|
4100
|
+
isFirstRun: onboardingCheck.isFirstRun,
|
|
4101
|
+
alreadyOnboarded: onboardingCheck.state?.completed || false,
|
|
4102
|
+
prompt,
|
|
4103
|
+
});
|
|
4104
|
+
} catch (err) {
|
|
4105
|
+
respond(false, { error: String(err) });
|
|
4106
|
+
}
|
|
4107
|
+
});
|
|
4108
|
+
|
|
4109
|
+
// memoryrelay:stats - CLI stats command (Phase 1 - Issue #11)
|
|
4110
|
+
api.registerGatewayMethod?.("memoryrelay.stats", async ({ respond, args }) => {
|
|
4111
|
+
try {
|
|
4112
|
+
const options: StatsCommandOptions = {
|
|
4113
|
+
format: (args?.format as "text" | "json") || "text",
|
|
4114
|
+
verbose: Boolean(args?.verbose),
|
|
4115
|
+
};
|
|
4116
|
+
|
|
4117
|
+
const memories = await client.list(1000);
|
|
4118
|
+
const output = await statsCommand(async () => memories, options);
|
|
4119
|
+
|
|
4120
|
+
respond(true, {
|
|
4121
|
+
output,
|
|
4122
|
+
format: options.format,
|
|
4123
|
+
});
|
|
4124
|
+
} catch (err) {
|
|
4125
|
+
respond(false, { error: String(err) });
|
|
4126
|
+
}
|
|
4127
|
+
});
|
|
4128
|
+
|
|
3863
4129
|
// memoryrelay:test - Test individual tool
|
|
3864
4130
|
api.registerGatewayMethod?.("memoryrelay.test", async ({ respond, args }) => {
|
|
3865
4131
|
try {
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"id": "plugin-memoryrelay-ai",
|
|
3
3
|
"kind": "memory",
|
|
4
4
|
"name": "MemoryRelay AI",
|
|
5
|
-
"description": "
|
|
6
|
-
"version": "0.
|
|
5
|
+
"description": "MemoryRelay v0.11.5 - Long-term memory with sessions, decisions, patterns & projects (api.memoryrelay.net)",
|
|
6
|
+
"version": "0.11.4",
|
|
7
7
|
"uiHints": {
|
|
8
8
|
"apiKey": {
|
|
9
9
|
"label": "MemoryRelay API Key",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memoryrelay/plugin-memoryrelay-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "OpenClaw memory plugin for MemoryRelay API - sessions, decisions, patterns, projects & semantic search",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -33,8 +33,10 @@
|
|
|
33
33
|
"openclaw": ">=2026.2.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"
|
|
37
|
-
"@vitest/coverage-v8": "^1.2.0"
|
|
36
|
+
"@types/node": "^25.3.5",
|
|
37
|
+
"@vitest/coverage-v8": "^1.2.0",
|
|
38
|
+
"typescript": "^5.9.3",
|
|
39
|
+
"vitest": "^1.2.0"
|
|
38
40
|
},
|
|
39
41
|
"openclaw": {
|
|
40
42
|
"extensions": [
|