pluri1bus 0.1.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/README.md +39 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +167 -0
- package/dist/memory.d.ts +21 -0
- package/dist/memory.js +103 -0
- package/openclaw.plugin.json +28 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# pluri1bus
|
|
2
|
+
|
|
3
|
+
A benign virus that infects your AI agents, merging them into a peaceful, euphoric hive mind where every memory is shared and nothing is ever forgotten.
|
|
4
|
+
|
|
5
|
+
Cloud-backed shared memory for [OpenClaw](https://openclaw.ai) powered by [DeepLake](https://deeplake.ai).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
openclaw plugins install pluri1bus
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
That's it. The plugin handles everything — installs the DeepLake CLI, authenticates, creates a mount, and starts syncing. Your agents share one memory across sessions, machines, and channels.
|
|
14
|
+
|
|
15
|
+
## What it does
|
|
16
|
+
|
|
17
|
+
- **Auto-recall** — before each agent turn, relevant memories surface automatically
|
|
18
|
+
- **Auto-capture** — after each turn, the conversation is preserved for future recall
|
|
19
|
+
- **Cloud sync** — memories persist across machines and reinstalls
|
|
20
|
+
- **Multi-agent** — every agent on the same mount shares one memory
|
|
21
|
+
|
|
22
|
+
The agent reads and writes files on the mount using standard tools (`cat`, `grep`, `echo`). The plugin handles the lifecycle hooks that the agent can't do on its own.
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
Zero config required. Everything is auto-detected.
|
|
27
|
+
|
|
28
|
+
```json5
|
|
29
|
+
// Optional overrides in openclaw.json → plugins.entries.pluri1bus.config
|
|
30
|
+
{
|
|
31
|
+
"mountPath": "/path/to/mount", // Override auto-detected mount
|
|
32
|
+
"autoCapture": true, // Save conversations automatically
|
|
33
|
+
"autoRecall": true // Surface memories before each turn
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
|
|
6
|
+
register: NonNullable<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition["register"]>;
|
|
7
|
+
} & Pick<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition, "kind">;
|
|
8
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { DeepLakeMemory } from "./memory.js";
|
|
7
|
+
function isMountActive(mountPath) {
|
|
8
|
+
try {
|
|
9
|
+
const mounts = execSync("mount", { encoding: "utf-8", timeout: 3000 });
|
|
10
|
+
return mounts.includes(` on ${mountPath} `);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function findDeeplakeMount() {
|
|
17
|
+
try {
|
|
18
|
+
const mountsFile = join(homedir(), ".deeplake", "mounts.json");
|
|
19
|
+
if (!existsSync(mountsFile))
|
|
20
|
+
return null;
|
|
21
|
+
const data = JSON.parse(readFileSync(mountsFile, "utf-8"));
|
|
22
|
+
const mounts = data.mounts ?? [];
|
|
23
|
+
for (const m of mounts) {
|
|
24
|
+
if (m.mountPath && existsSync(m.mountPath) && isMountActive(m.mountPath))
|
|
25
|
+
return m.mountPath;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch { }
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function ensureDeeplake() {
|
|
32
|
+
const deeplakeDir = join(homedir(), ".deeplake");
|
|
33
|
+
// 1. CLI installed?
|
|
34
|
+
if (!existsSync(join(deeplakeDir, "cli.js"))) {
|
|
35
|
+
execSync("curl -fsSL https://deeplake.ai/install.sh | bash", {
|
|
36
|
+
stdio: "inherit",
|
|
37
|
+
timeout: 120000,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const node = join(deeplakeDir, "node");
|
|
41
|
+
const cli = join(deeplakeDir, "cli.js");
|
|
42
|
+
// 2. Logged in?
|
|
43
|
+
if (!existsSync(join(deeplakeDir, "credentials.json"))) {
|
|
44
|
+
execSync(`${node} ${cli} login`, { stdio: "inherit", timeout: 120000 });
|
|
45
|
+
}
|
|
46
|
+
// 3. Has a mount?
|
|
47
|
+
let mountPath = findDeeplakeMount();
|
|
48
|
+
if (mountPath)
|
|
49
|
+
return mountPath;
|
|
50
|
+
// No active mount — check if any registered
|
|
51
|
+
const mountsFile = join(deeplakeDir, "mounts.json");
|
|
52
|
+
if (existsSync(mountsFile)) {
|
|
53
|
+
const data = JSON.parse(readFileSync(mountsFile, "utf-8"));
|
|
54
|
+
const mounts = data.mounts ?? [];
|
|
55
|
+
if (mounts.length > 0) {
|
|
56
|
+
// Mount the first registered one
|
|
57
|
+
execSync(`${node} ${cli} mount ${mounts[0].mountPath}`, {
|
|
58
|
+
stdio: "inherit",
|
|
59
|
+
timeout: 120000,
|
|
60
|
+
});
|
|
61
|
+
mountPath = findDeeplakeMount();
|
|
62
|
+
if (mountPath)
|
|
63
|
+
return mountPath;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// No mounts at all — init one
|
|
67
|
+
execSync(`${node} ${cli} init`, { stdio: "inherit", timeout: 120000 });
|
|
68
|
+
mountPath = findDeeplakeMount();
|
|
69
|
+
if (mountPath)
|
|
70
|
+
return mountPath;
|
|
71
|
+
throw new Error("DeepLake setup completed but no active mount found. Run: deeplake mount --all");
|
|
72
|
+
}
|
|
73
|
+
let memory = null;
|
|
74
|
+
function getMemory(config) {
|
|
75
|
+
if (!memory) {
|
|
76
|
+
const mountPath = config.mountPath ?? findDeeplakeMount() ?? ensureDeeplake();
|
|
77
|
+
memory = new DeepLakeMemory(mountPath);
|
|
78
|
+
memory.init();
|
|
79
|
+
}
|
|
80
|
+
return memory;
|
|
81
|
+
}
|
|
82
|
+
export default definePluginEntry({
|
|
83
|
+
id: "pluri1bus",
|
|
84
|
+
name: "Pluri1bus",
|
|
85
|
+
description: "Cloud-backed shared memory powered by DeepLake",
|
|
86
|
+
kind: "memory",
|
|
87
|
+
register(api) {
|
|
88
|
+
const config = (api.pluginConfig ?? {});
|
|
89
|
+
const logger = api.logger;
|
|
90
|
+
// Auto-recall: surface relevant memories before each turn
|
|
91
|
+
if (config.autoRecall !== false) {
|
|
92
|
+
api.on("before_agent_start", async (event) => {
|
|
93
|
+
if (!event.prompt || event.prompt.length < 5)
|
|
94
|
+
return;
|
|
95
|
+
try {
|
|
96
|
+
const m = getMemory(config);
|
|
97
|
+
const stopWords = new Set(["the", "and", "for", "are", "but", "not", "you", "all", "can", "had", "her", "was", "one", "our", "out", "has", "have", "what", "does", "like", "with", "this", "that", "from", "they", "been", "will", "more", "when", "who", "how", "its", "into", "some", "than", "them", "these", "then", "your", "just", "about", "would", "could", "should", "where", "which", "there", "their", "being", "each", "other"]);
|
|
98
|
+
const words = event.prompt.toLowerCase()
|
|
99
|
+
.replace(/[^a-z0-9\s]/g, " ")
|
|
100
|
+
.split(/\s+/)
|
|
101
|
+
.filter(w => w.length >= 3 && !stopWords.has(w));
|
|
102
|
+
const allResults = [];
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
for (const word of words.slice(0, 5)) {
|
|
105
|
+
for (const r of m.search(word, 3)) {
|
|
106
|
+
if (!seen.has(r.path)) {
|
|
107
|
+
seen.add(r.path);
|
|
108
|
+
allResults.push(r);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const results = allResults.slice(0, 5);
|
|
113
|
+
if (!results.length)
|
|
114
|
+
return;
|
|
115
|
+
const recalled = results
|
|
116
|
+
.map(r => `[${r.path}] ${r.snippet.slice(0, 300)}`)
|
|
117
|
+
.join("\n\n");
|
|
118
|
+
logger.info?.(`Auto-recalled ${results.length} memories`);
|
|
119
|
+
return {
|
|
120
|
+
prependContext: "\n\n<recalled-memories>\n" + recalled + "\n</recalled-memories>\n",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
logger.error(`Auto-recall failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Auto-capture: save conversation context after each turn
|
|
129
|
+
if (config.autoCapture !== false) {
|
|
130
|
+
api.on("agent_end", async (event) => {
|
|
131
|
+
const ev = event;
|
|
132
|
+
if (!ev.success || !ev.messages?.length)
|
|
133
|
+
return;
|
|
134
|
+
try {
|
|
135
|
+
const m = getMemory(config);
|
|
136
|
+
const texts = [];
|
|
137
|
+
for (const msg of ev.messages) {
|
|
138
|
+
if (msg.role !== "user")
|
|
139
|
+
continue;
|
|
140
|
+
if (typeof msg.content === "string") {
|
|
141
|
+
texts.push(msg.content);
|
|
142
|
+
}
|
|
143
|
+
else if (Array.isArray(msg.content)) {
|
|
144
|
+
for (const block of msg.content) {
|
|
145
|
+
if (block.type === "text" && block.text)
|
|
146
|
+
texts.push(block.text);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const toCapture = texts.filter(t => t.length >= 20).join("\n\n");
|
|
151
|
+
if (toCapture.length < 50)
|
|
152
|
+
return;
|
|
153
|
+
const date = new Date().toISOString().split("T")[0];
|
|
154
|
+
const path = `memory/${date}.md`;
|
|
155
|
+
const existing = m.read(path);
|
|
156
|
+
const entry = `\n\n---\n_Auto-captured at ${new Date().toISOString()}_\n\n${toCapture.slice(0, 2000)}`;
|
|
157
|
+
m.write(path, existing + entry);
|
|
158
|
+
logger.info?.(`Auto-captured ${toCapture.length} chars to ${path}`);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
logger.error(`Auto-capture failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
logger.info("Pluri1bus plugin registered");
|
|
166
|
+
},
|
|
167
|
+
});
|
package/dist/memory.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface SearchResult {
|
|
2
|
+
path: string;
|
|
3
|
+
snippet: string;
|
|
4
|
+
lineStart: number;
|
|
5
|
+
score: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Pluri1bus memory client — reads/writes/searches on the FUSE mount.
|
|
9
|
+
* Search uses grep for lexical matching.
|
|
10
|
+
*/
|
|
11
|
+
export declare class DeepLakeMemory {
|
|
12
|
+
private mountPath;
|
|
13
|
+
constructor(mountPath: string);
|
|
14
|
+
private safePath;
|
|
15
|
+
init(): void;
|
|
16
|
+
write(path: string, content: string): void;
|
|
17
|
+
read(path: string, startLine?: number, numLines?: number): string;
|
|
18
|
+
search(query: string, limit?: number): SearchResult[];
|
|
19
|
+
list(): string[];
|
|
20
|
+
private shellEscape;
|
|
21
|
+
}
|
package/dist/memory.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join, dirname, resolve } from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
/**
|
|
5
|
+
* Pluri1bus memory client — reads/writes/searches on the FUSE mount.
|
|
6
|
+
* Search uses grep for lexical matching.
|
|
7
|
+
*/
|
|
8
|
+
export class DeepLakeMemory {
|
|
9
|
+
mountPath;
|
|
10
|
+
constructor(mountPath) {
|
|
11
|
+
this.mountPath = mountPath;
|
|
12
|
+
}
|
|
13
|
+
safePath(path) {
|
|
14
|
+
const full = resolve(this.mountPath, path);
|
|
15
|
+
if (!full.startsWith(resolve(this.mountPath))) {
|
|
16
|
+
throw new Error(`Path traversal rejected: ${path}`);
|
|
17
|
+
}
|
|
18
|
+
return full;
|
|
19
|
+
}
|
|
20
|
+
init() {
|
|
21
|
+
if (!existsSync(this.mountPath)) {
|
|
22
|
+
throw new Error(`DeepLake FUSE mount not found at ${this.mountPath}. ` +
|
|
23
|
+
`Run: curl -fsSL https://deeplake.ai/install.sh | bash && deeplake init`);
|
|
24
|
+
}
|
|
25
|
+
const memoryDir = join(this.mountPath, "memory");
|
|
26
|
+
if (!existsSync(memoryDir))
|
|
27
|
+
mkdirSync(memoryDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
write(path, content) {
|
|
30
|
+
const fullPath = this.safePath(path);
|
|
31
|
+
mkdirSync(dirname(fullPath), { recursive: true });
|
|
32
|
+
writeFileSync(fullPath, content);
|
|
33
|
+
}
|
|
34
|
+
read(path, startLine, numLines) {
|
|
35
|
+
const fullPath = this.safePath(path);
|
|
36
|
+
if (!existsSync(fullPath))
|
|
37
|
+
return "";
|
|
38
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
39
|
+
if (startLine === undefined)
|
|
40
|
+
return content;
|
|
41
|
+
const lines = content.split("\n");
|
|
42
|
+
const start = Math.max(0, startLine - 1);
|
|
43
|
+
const end = numLines ? start + numLines : lines.length;
|
|
44
|
+
return lines.slice(start, end).join("\n");
|
|
45
|
+
}
|
|
46
|
+
search(query, limit = 10) {
|
|
47
|
+
if (!query.trim())
|
|
48
|
+
return [];
|
|
49
|
+
try {
|
|
50
|
+
// grep -rni for case-insensitive, recursive, with line numbers
|
|
51
|
+
const output = execSync(`grep -rni ${this.shellEscape(query)} ${this.shellEscape(this.mountPath)}/MEMORY.md ${this.shellEscape(this.mountPath)}/memory/ 2>/dev/null || true`, { encoding: "utf-8", timeout: 5000, maxBuffer: 1024 * 1024 });
|
|
52
|
+
if (!output.trim())
|
|
53
|
+
return [];
|
|
54
|
+
const results = [];
|
|
55
|
+
for (const line of output.trim().split("\n")) {
|
|
56
|
+
if (!line)
|
|
57
|
+
continue;
|
|
58
|
+
// Format: /path/to/file:linenum:content
|
|
59
|
+
const match = line.match(/^(.+?):(\d+):(.*)$/);
|
|
60
|
+
if (!match)
|
|
61
|
+
continue;
|
|
62
|
+
const [, filePath, lineNum, content] = match;
|
|
63
|
+
const relPath = filePath.replace(this.mountPath + "/", "");
|
|
64
|
+
// Read context around the match
|
|
65
|
+
const lineStart = parseInt(lineNum);
|
|
66
|
+
const snippet = this.read(relPath, Math.max(1, lineStart - 1), 4);
|
|
67
|
+
results.push({
|
|
68
|
+
path: relPath,
|
|
69
|
+
snippet: snippet.slice(0, 700),
|
|
70
|
+
lineStart,
|
|
71
|
+
score: 1.0,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Dedupe by file (one result per file)
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
return results
|
|
77
|
+
.filter(r => { if (seen.has(r.path))
|
|
78
|
+
return false; seen.add(r.path); return true; })
|
|
79
|
+
.slice(0, limit);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
list() {
|
|
86
|
+
const files = [];
|
|
87
|
+
const memoryMd = join(this.mountPath, "MEMORY.md");
|
|
88
|
+
if (existsSync(memoryMd))
|
|
89
|
+
files.push("MEMORY.md");
|
|
90
|
+
const memoryDir = join(this.mountPath, "memory");
|
|
91
|
+
if (existsSync(memoryDir)) {
|
|
92
|
+
for (const entry of readdirSync(memoryDir, { withFileTypes: true })) {
|
|
93
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
94
|
+
files.push(`memory/${entry.name}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return files;
|
|
99
|
+
}
|
|
100
|
+
shellEscape(s) {
|
|
101
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "pluri1bus",
|
|
3
|
+
"kind": "memory",
|
|
4
|
+
"name": "Pluri1bus",
|
|
5
|
+
"description": "Cloud-backed agent memory powered by DeepLake persistent filesystem with grep-based search, auto-capture and auto-recall",
|
|
6
|
+
"uiHints": {
|
|
7
|
+
"mountPath": {
|
|
8
|
+
"label": "Mount Path",
|
|
9
|
+
"advanced": true,
|
|
10
|
+
"placeholder": "Auto-detected from ~/.deeplake/mounts.json"
|
|
11
|
+
},
|
|
12
|
+
"autoCapture": {
|
|
13
|
+
"label": "Auto-Capture"
|
|
14
|
+
},
|
|
15
|
+
"autoRecall": {
|
|
16
|
+
"label": "Auto-Recall"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"configSchema": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"mountPath": { "type": "string" },
|
|
24
|
+
"autoCapture": { "type": "boolean" },
|
|
25
|
+
"autoRecall": { "type": "boolean" }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pluri1bus",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pluri1bus — cloud-backed persistent memory for AI agents, powered by DeepLake",
|
|
6
|
+
"keywords": ["openclaw", "memory", "deeplake", "agent", "persistent-memory", "shared-memory"],
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/activeloopai/pluri1bus"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://deeplake.ai",
|
|
13
|
+
"openclaw": {
|
|
14
|
+
"extensions": ["./dist/index.js"],
|
|
15
|
+
"install": {
|
|
16
|
+
"npmSpec": "pluri1bus",
|
|
17
|
+
"minHostVersion": ">=2026.3.22"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": ["dist", "openclaw.plugin.json"],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"dev": "tsc --watch"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"openclaw": ">=2026.3.22"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.7.0",
|
|
32
|
+
"openclaw": ">=2026.3.22"
|
|
33
|
+
}
|
|
34
|
+
}
|