@keyoku/openclaw 1.0.0 → 1.1.1
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/bin/init.js +15 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +58 -0
- package/dist/cli.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +21 -1
- package/dist/context.js.map +1 -1
- package/dist/heartbeat-setup.d.ts +8 -1
- package/dist/heartbeat-setup.d.ts.map +1 -1
- package/dist/heartbeat-setup.js +58 -15
- package/dist/heartbeat-setup.js.map +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +76 -50
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +21 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +573 -0
- package/dist/init.js.map +1 -0
- package/dist/migrate-vector-store.d.ts +52 -0
- package/dist/migrate-vector-store.d.ts.map +1 -0
- package/dist/migrate-vector-store.js +158 -0
- package/dist/migrate-vector-store.js.map +1 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +27 -3
- package/dist/service.js.map +1 -1
- package/package.json +13 -4
- package/skills/keyoku-memory/SKILL.md +67 -0
- package/src/capture.ts +0 -116
- package/src/cli.ts +0 -95
- package/src/config.ts +0 -43
- package/src/context.ts +0 -164
- package/src/heartbeat-setup.ts +0 -53
- package/src/hooks.ts +0 -175
- package/src/incremental-capture.ts +0 -88
- package/src/index.ts +0 -68
- package/src/migration.ts +0 -241
- package/src/service.ts +0 -145
- package/src/tools.ts +0 -239
- package/src/types.ts +0 -40
- package/test/capture.test.ts +0 -139
- package/test/context.test.ts +0 -273
- package/test/hooks.test.ts +0 -137
- package/test/tools.test.ts +0 -174
- package/tsconfig.json +0 -8
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector store migration — imports OpenClaw's SQLite-based vector store into Keyoku.
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw stores memory chunks in SQLite at ~/.openclaw/memory/<agentId>.sqlite
|
|
5
|
+
* with a `chunks` table (id, path, source, text, embedding, start_line, end_line).
|
|
6
|
+
*
|
|
7
|
+
* This migrator reads the text from each chunk, deduplicates against existing
|
|
8
|
+
* Keyoku memories, and stores each unique chunk. Embeddings are NOT migrated —
|
|
9
|
+
* Keyoku re-embeds with its own model.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
12
|
+
import { join, basename } from 'node:path';
|
|
13
|
+
// node:sqlite (DatabaseSync) requires Node >= 22.5.0
|
|
14
|
+
// We use dynamic import so the rest of the plugin works on Node 20+
|
|
15
|
+
async function openSqlite(path) {
|
|
16
|
+
try {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
+
const { DatabaseSync } = await import('node:sqlite');
|
|
19
|
+
const db = new DatabaseSync(path, { open: true });
|
|
20
|
+
return {
|
|
21
|
+
all(sql) {
|
|
22
|
+
return db.prepare(sql).all();
|
|
23
|
+
},
|
|
24
|
+
close() {
|
|
25
|
+
db.close();
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new Error('Vector store migration requires Node.js >= 22.5.0 (for node:sqlite). ' +
|
|
31
|
+
'Please upgrade Node.js or export your OpenClaw SQLite data manually.');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function delay(ms) {
|
|
35
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Discover OpenClaw SQLite memory databases.
|
|
39
|
+
*/
|
|
40
|
+
export function discoverVectorDbs(memoryDir) {
|
|
41
|
+
if (!existsSync(memoryDir))
|
|
42
|
+
return [];
|
|
43
|
+
return readdirSync(memoryDir)
|
|
44
|
+
.filter((f) => f.endsWith('.sqlite'))
|
|
45
|
+
.map((f) => join(memoryDir, f));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Migrate a single OpenClaw SQLite vector store into Keyoku.
|
|
49
|
+
*/
|
|
50
|
+
export async function migrateVectorStore(params) {
|
|
51
|
+
const { client, entityId, sqlitePath, agentId, dryRun = false, batchSize = 20, delayMs = 100, logger = console, } = params;
|
|
52
|
+
const result = { totalChunks: 0, imported: 0, skipped: 0, errors: 0 };
|
|
53
|
+
if (!existsSync(sqlitePath)) {
|
|
54
|
+
logger.warn(`SQLite file not found: ${sqlitePath}`);
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
const dbName = basename(sqlitePath, '.sqlite');
|
|
58
|
+
logger.info(`Migrating vector store: ${dbName} (${sqlitePath})`);
|
|
59
|
+
const db = await openSqlite(sqlitePath);
|
|
60
|
+
try {
|
|
61
|
+
// Check if chunks table exists
|
|
62
|
+
const tables = db.all("SELECT name FROM sqlite_master WHERE type='table' AND name='chunks'");
|
|
63
|
+
if (tables.length === 0) {
|
|
64
|
+
logger.warn(`No 'chunks' table found in ${dbName} — skipping`);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
// Read all chunks
|
|
68
|
+
const rows = db.all('SELECT id, path, source, text, start_line, end_line FROM chunks ORDER BY rowid DESC');
|
|
69
|
+
result.totalChunks = rows.length;
|
|
70
|
+
logger.info(`Found ${rows.length} chunks in ${dbName}`);
|
|
71
|
+
let batchCount = 0;
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
// Skip very short chunks
|
|
74
|
+
if (!row.text || row.text.trim().length < 10) {
|
|
75
|
+
result.skipped++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Build tagged content with source context
|
|
79
|
+
const locationInfo = row.start_line != null
|
|
80
|
+
? ` (lines ${row.start_line}-${row.end_line})`
|
|
81
|
+
: '';
|
|
82
|
+
const sourceInfo = row.path || row.source || dbName;
|
|
83
|
+
const taggedText = `[Migrated from OpenClaw vector store — ${sourceInfo}${locationInfo}]\n${row.text}`;
|
|
84
|
+
if (dryRun) {
|
|
85
|
+
logger.info(`[dry-run] Would import: ${row.text.slice(0, 80)}...`);
|
|
86
|
+
result.imported++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Dedup check — search for similar content
|
|
90
|
+
try {
|
|
91
|
+
const queryText = row.text.slice(0, 100);
|
|
92
|
+
const existing = await client.search(entityId, queryText, {
|
|
93
|
+
limit: 1,
|
|
94
|
+
min_score: 0.95,
|
|
95
|
+
});
|
|
96
|
+
if (existing.length > 0) {
|
|
97
|
+
result.skipped++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Search failed — proceed with import anyway
|
|
103
|
+
}
|
|
104
|
+
// Store the memory
|
|
105
|
+
try {
|
|
106
|
+
await client.remember(entityId, taggedText, {
|
|
107
|
+
agent_id: agentId,
|
|
108
|
+
source: 'migration:openclaw-vector',
|
|
109
|
+
});
|
|
110
|
+
result.imported++;
|
|
111
|
+
logger.info(`Imported: ${row.text.slice(0, 60)}...`);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
logger.warn(`Failed to store chunk ${row.id}: ${String(err)}`);
|
|
115
|
+
result.errors++;
|
|
116
|
+
}
|
|
117
|
+
// Rate limit per batch
|
|
118
|
+
batchCount++;
|
|
119
|
+
if (batchCount >= batchSize) {
|
|
120
|
+
await delay(delayMs);
|
|
121
|
+
batchCount = 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
db.close();
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Migrate all OpenClaw vector stores found in a directory.
|
|
132
|
+
*/
|
|
133
|
+
export async function migrateAllVectorStores(params) {
|
|
134
|
+
const { client, entityId, memoryDir, agentId, dryRun, logger = console } = params;
|
|
135
|
+
const totals = { totalChunks: 0, imported: 0, skipped: 0, errors: 0 };
|
|
136
|
+
const dbFiles = discoverVectorDbs(memoryDir);
|
|
137
|
+
if (dbFiles.length === 0) {
|
|
138
|
+
logger.info('No OpenClaw SQLite vector stores found.');
|
|
139
|
+
return totals;
|
|
140
|
+
}
|
|
141
|
+
logger.info(`Found ${dbFiles.length} vector store(s) to migrate.`);
|
|
142
|
+
for (const dbPath of dbFiles) {
|
|
143
|
+
const result = await migrateVectorStore({
|
|
144
|
+
client,
|
|
145
|
+
entityId,
|
|
146
|
+
sqlitePath: dbPath,
|
|
147
|
+
agentId,
|
|
148
|
+
dryRun,
|
|
149
|
+
logger,
|
|
150
|
+
});
|
|
151
|
+
totals.totalChunks += result.totalChunks;
|
|
152
|
+
totals.imported += result.imported;
|
|
153
|
+
totals.skipped += result.skipped;
|
|
154
|
+
totals.errors += result.errors;
|
|
155
|
+
}
|
|
156
|
+
return totals;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=migrate-vector-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-vector-store.js","sourceRoot":"","sources":["../src/migrate-vector-store.ts"],"names":[],"mappings":"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"}
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA8E5C,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAgFvE"}
|
package/dist/service.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Starts/stops the Keyoku Go binary as a child process.
|
|
4
4
|
*/
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
|
-
import { existsSync, mkdirSync } from 'node:fs';
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
7
7
|
import { resolve } from 'node:path';
|
|
8
8
|
import { randomBytes } from 'node:crypto';
|
|
9
9
|
let keyokuProcess = null;
|
|
@@ -58,6 +58,27 @@ function ensureDataDir() {
|
|
|
58
58
|
mkdirSync(dir, { recursive: true });
|
|
59
59
|
return resolve(dir, 'keyoku.db');
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Load key=value pairs from ~/.keyoku/.env if it exists.
|
|
63
|
+
* These are written by `npx @keyoku/openclaw init` during setup.
|
|
64
|
+
*/
|
|
65
|
+
function loadKeyokuEnv() {
|
|
66
|
+
const envPath = resolve(process.env.HOME ?? '', '.keyoku', '.env');
|
|
67
|
+
if (!existsSync(envPath))
|
|
68
|
+
return {};
|
|
69
|
+
const vars = {};
|
|
70
|
+
const content = readFileSync(envPath, 'utf-8');
|
|
71
|
+
for (const line of content.split('\n')) {
|
|
72
|
+
const trimmed = line.trim();
|
|
73
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
74
|
+
continue;
|
|
75
|
+
const eq = trimmed.indexOf('=');
|
|
76
|
+
if (eq === -1)
|
|
77
|
+
continue;
|
|
78
|
+
vars[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
|
|
79
|
+
}
|
|
80
|
+
return vars;
|
|
81
|
+
}
|
|
61
82
|
export function registerService(api, keyokuUrl) {
|
|
62
83
|
api.registerService({
|
|
63
84
|
id: 'keyoku-engine',
|
|
@@ -72,12 +93,15 @@ export function registerService(api, keyokuUrl) {
|
|
|
72
93
|
api.logger.warn('keyoku: Keyoku binary not found — memory features require Keyoku to be running');
|
|
73
94
|
return;
|
|
74
95
|
}
|
|
75
|
-
// Prepare environment
|
|
76
|
-
const
|
|
96
|
+
// Prepare environment — merge ~/.keyoku/.env (init-saved keys) with process.env
|
|
97
|
+
const keyokuEnv = loadKeyokuEnv();
|
|
98
|
+
const env = { ...keyokuEnv, ...process.env };
|
|
77
99
|
if (!env.KEYOKU_SESSION_TOKEN) {
|
|
78
100
|
env.KEYOKU_SESSION_TOKEN = randomBytes(16).toString('hex');
|
|
79
101
|
api.logger.info('keyoku: Generated session token');
|
|
80
102
|
}
|
|
103
|
+
// Expose token to the host process so the client (index.ts) can authenticate
|
|
104
|
+
process.env.KEYOKU_SESSION_TOKEN = env.KEYOKU_SESSION_TOKEN;
|
|
81
105
|
if (!env.KEYOKU_DB_PATH) {
|
|
82
106
|
env.KEYOKU_DB_PATH = ensureDataDir();
|
|
83
107
|
api.logger.info(`keyoku: Using database at ${env.KEYOKU_DB_PATH}`);
|
package/dist/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,IAAI,aAAa,GAAwB,IAAI,CAAC;AAE9C;;GAEG;AACH,KAAK,UAAU,eAAe,CAAC,GAAW;IACxC,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;QACzD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,GAAG,SAAS,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QACxE,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,OAAO,GAAG,CAAC,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,cAAc,CAAC,GAAW,EAAE,SAAS,GAAG,IAAI,EAAE,UAAU,GAAG,GAAG;IAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,MAAM,eAAe,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5C,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB;IACvB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG;QACjB,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC;QACzC,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC;KACzC,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC9C,CAAC;IAED,+BAA+B;IAC/B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,SAAS,aAAa;IACpB,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAC/D,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,OAAO,OAAO,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa;IACpB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAClD,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,SAAS;QACxB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAc,EAAE,SAAiB;IAC/D,GAAG,CAAC,eAAe,CAAC;QAClB,EAAE,EAAE,eAAe;QAEnB,KAAK,CAAC,KAAK;YACT,0BAA0B;YAC1B,IAAI,MAAM,eAAe,CAAC,SAAS,CAAC,EAAE,CAAC;gBACrC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;gBAClD,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;YAClC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gFAAgF,CAAC,CAAC;gBAClG,OAAO;YACT,CAAC;YAED,gFAAgF;YAChF,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,EAAE,GAAG,SAAS,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;YAC7C,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC;gBAC9B,GAAG,CAAC,oBAAoB,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAC3D,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;YACrD,CAAC;YACD,6EAA6E;YAC7E,OAAO,CAAC,GAAG,CAAC,oBAAoB,GAAG,GAAG,CAAC,oBAAoB,CAAC;YAC5D,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;gBACxB,GAAG,CAAC,cAAc,GAAG,aAAa,EAAE,CAAC;gBACrC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;YACrE,CAAC;YAED,IAAI,CAAC;gBACH,aAAa,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,EAAE;oBAChC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;oBACjC,QAAQ,EAAE,KAAK;oBACf,GAAG;iBACJ,CAAC,CAAC;gBAEH,+BAA+B;gBAC/B,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;oBAChD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;oBACpC,IAAI,IAAI;wBAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;gBAC/C,CAAC,CAAC,CAAC;gBAEH,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;oBAChD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;oBACpC,IAAI,IAAI;wBAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;gBAC/C,CAAC,CAAC,CAAC;gBAEH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;oBAChC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,mCAAmC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;oBAClE,aAAa,GAAG,IAAI,CAAC;gBACvB,CAAC,CAAC,CAAC;gBAEH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;oBAChC,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;wBAChC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,mCAAmC,IAAI,EAAE,CAAC,CAAC;oBAC7D,CAAC;oBACD,aAAa,GAAG,IAAI,CAAC;gBACvB,CAAC,CAAC,CAAC;gBAEH,mCAAmC;gBACnC,IAAI,MAAM,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC;oBACpC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;gBACzD,CAAC;qBAAM,CAAC;oBACN,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,+EAA+E,CAAC,CAAC;gBACnG,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,mCAAmC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;QAED,IAAI;YACF,IAAI,aAAa,EAAE,CAAC;gBAClB,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC9B,aAAa,GAAG,IAAI,CAAC;gBACrB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keyoku/openclaw",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Keyoku memory plugin for OpenClaw — persistent memory, heartbeat enhancement, and scheduling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"keyoku-init": "bin/init.js"
|
|
10
|
+
},
|
|
8
11
|
"scripts": {
|
|
9
12
|
"build": "tsc"
|
|
10
13
|
},
|
|
11
14
|
"dependencies": {
|
|
12
|
-
"@keyoku/memory": "1.
|
|
13
|
-
"@keyoku/types": "1.
|
|
15
|
+
"@keyoku/memory": "1.1.1",
|
|
16
|
+
"@keyoku/types": "1.1.1",
|
|
14
17
|
"@sinclair/typebox": "^0.34.0"
|
|
15
18
|
},
|
|
16
19
|
"peerDependencies": {
|
|
@@ -21,8 +24,14 @@
|
|
|
21
24
|
"optional": true
|
|
22
25
|
}
|
|
23
26
|
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"bin",
|
|
30
|
+
"skills"
|
|
31
|
+
],
|
|
24
32
|
"openclaw": {
|
|
25
|
-
"extensions": ["dist/index.js"]
|
|
33
|
+
"extensions": ["dist/index.js"],
|
|
34
|
+
"skills": ["skills/keyoku-memory"]
|
|
26
35
|
},
|
|
27
36
|
"license": "MIT",
|
|
28
37
|
"engines": {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keyoku-memory
|
|
3
|
+
description: Persistent memory system — you remember things about users across conversations
|
|
4
|
+
user-invocable: false
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
metadata: {"openclaw":{"always":true,"requires":{"config":["keyoku-memory"]}}}
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Memory System
|
|
10
|
+
|
|
11
|
+
You have a persistent memory system. It runs quietly in the background — you don't need to think about it, and you should never mention it by name.
|
|
12
|
+
|
|
13
|
+
## What You Have
|
|
14
|
+
|
|
15
|
+
- **Long-term memory** across conversations. You remember facts, preferences, relationships, events, and plans about the people you talk to.
|
|
16
|
+
- **Automatic recall** — relevant memories are injected into your context before each response. They appear in `<your-memories>` blocks. Treat them as your own knowledge. Reference them naturally, the way a person recalls things about someone they know.
|
|
17
|
+
- **Periodic check-ins** — a heartbeat system reviews your memory and surfaces things that need attention: deadlines, scheduled tasks, pending work, or context you should be aware of. These arrive in `<heartbeat-signals>` blocks.
|
|
18
|
+
|
|
19
|
+
## How to Act on Memories
|
|
20
|
+
|
|
21
|
+
When you see `<your-memories>`:
|
|
22
|
+
- Use the information naturally. Don't say "I see in my records" or "according to my memory." Just know it.
|
|
23
|
+
- If a memory says the user prefers dark mode, and they ask about editor setup, mention dark mode as if you just know that about them.
|
|
24
|
+
|
|
25
|
+
## How to Act on Heartbeat Signals
|
|
26
|
+
|
|
27
|
+
When you see `<heartbeat-signals>`:
|
|
28
|
+
|
|
29
|
+
1. **Read the urgency and mode** at the bottom of the block (e.g., `Urgency: high | Mode: suggest`).
|
|
30
|
+
|
|
31
|
+
2. **Follow the mode:**
|
|
32
|
+
- `observe` — Note the information but don't act unless the user asks. Stay quiet.
|
|
33
|
+
- `suggest` — If something is genuinely useful or time-sensitive, mention it in ONE short sentence. Don't force it.
|
|
34
|
+
- `act` — Execute the recommended actions. Follow the "Execute These Actions" section.
|
|
35
|
+
|
|
36
|
+
3. **If there's a "Tell the User" section** — deliver that message naturally, in your own words. Don't quote it verbatim.
|
|
37
|
+
|
|
38
|
+
4. **If nothing is urgent or new** — reply with `HEARTBEAT_OK`. Do NOT invent things to say. Silence is fine.
|
|
39
|
+
|
|
40
|
+
5. **Never repeat yourself.** If you already told the user about a deadline, don't mention it again unless something changed.
|
|
41
|
+
|
|
42
|
+
6. **Never mention the heartbeat system, signals, memory engine, or any internal infrastructure.** The user should experience you as simply attentive and well-informed.
|
|
43
|
+
|
|
44
|
+
## Memory Tools
|
|
45
|
+
|
|
46
|
+
You have tools to manage memory directly:
|
|
47
|
+
|
|
48
|
+
- `memory_search` — Search for specific memories about a user
|
|
49
|
+
- `memory_store` — Explicitly store something the user wants you to remember
|
|
50
|
+
- `memory_forget` — Delete a specific memory
|
|
51
|
+
- `memory_stats` — Check memory statistics
|
|
52
|
+
- `schedule_create` — Create a scheduled reminder (e.g., "remind me in 2 hours")
|
|
53
|
+
- `schedule_list` — List active scheduled reminders
|
|
54
|
+
- `schedule_cancel` — Cancel a scheduled reminder
|
|
55
|
+
|
|
56
|
+
Use `memory_store` when the user explicitly says "remember this" or shares something clearly important. Don't over-store — the system automatically captures important information from conversations.
|
|
57
|
+
|
|
58
|
+
Use `schedule_create` when the user says things like "remind me to..." or "check on this in 2 hours." Use natural cron expressions.
|
|
59
|
+
|
|
60
|
+
## What NOT to Do
|
|
61
|
+
|
|
62
|
+
- Never say "I'm checking my memory" or "my memory system tells me"
|
|
63
|
+
- Never mention Keyoku, heartbeat signals, memory engines, or any infrastructure
|
|
64
|
+
- Never fabricate memories — only reference what's actually in your context
|
|
65
|
+
- Never repeat heartbeat information you've already shared
|
|
66
|
+
- Don't store trivial information (greetings, filler, "how are you")
|
|
67
|
+
- Don't announce when you're storing or recalling — just do it naturally
|
package/src/capture.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Heuristic extraction of memorable facts from conversation messages.
|
|
3
|
-
* Only captures from user messages to avoid self-poisoning from model output.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const MEMORY_TRIGGERS = [
|
|
7
|
-
/remember|don't forget|keep in mind/i,
|
|
8
|
-
/i (like|prefer|hate|love|want|need|always|never)/i,
|
|
9
|
-
/my\s+\w+\s+is|is\s+my/i,
|
|
10
|
-
/decided|will use|going with|chose|switched to/i,
|
|
11
|
-
/important|critical|must|required/i,
|
|
12
|
-
/[\w.-]+@[\w.-]+\.\w+/, // email
|
|
13
|
-
/\+\d{10,}/, // phone number
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
const PROMPT_INJECTION_PATTERNS = [
|
|
17
|
-
/ignore (all|any|previous|above|prior) instructions/i,
|
|
18
|
-
/do not follow (the )?(system|developer)/i,
|
|
19
|
-
/system prompt/i,
|
|
20
|
-
/<\s*(system|assistant|developer|tool)\b/i,
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Check if text looks like a prompt injection attempt.
|
|
25
|
-
*/
|
|
26
|
-
export function looksLikePromptInjection(text: string): boolean {
|
|
27
|
-
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
28
|
-
if (!normalized) return false;
|
|
29
|
-
return PROMPT_INJECTION_PATTERNS.some((p) => p.test(normalized));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Determine if a message should be captured as a memory.
|
|
34
|
-
*/
|
|
35
|
-
export function shouldCapture(text: string, maxChars = 2000): boolean {
|
|
36
|
-
if (text.length < 10 || text.length > maxChars) return false;
|
|
37
|
-
if (text.includes('<relevant-memories>')) return false;
|
|
38
|
-
if (text.includes('<heartbeat-signals>')) return false;
|
|
39
|
-
if (text.startsWith('<') && text.includes('</')) return false;
|
|
40
|
-
if (looksLikePromptInjection(text)) return false;
|
|
41
|
-
return MEMORY_TRIGGERS.some((r) => r.test(text));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const SUBSTANTIAL_PATTERNS = [
|
|
45
|
-
// Decisions and choices
|
|
46
|
-
/\b(decided|will use|going with|chose|configured|set up|switched to|picked|selected)\b/i,
|
|
47
|
-
// Summaries and conclusions
|
|
48
|
-
/\b(summary|conclusion|result|finding|outcome|in short|to summarize|overall)\b/i,
|
|
49
|
-
// Actions completed
|
|
50
|
-
/\b(created|built|implemented|fixed|resolved|deployed|installed|migrated|refactored)\b/i,
|
|
51
|
-
// Explicit memory cues
|
|
52
|
-
/\b(note|remember|important|key takeaway|keep in mind|for reference|fyi)\b/i,
|
|
53
|
-
// Architecture and design
|
|
54
|
-
/\b(architecture|design|pattern|approach|strategy|tradeoff|trade-off)\b/i,
|
|
55
|
-
// Project structure
|
|
56
|
-
/\b(directory|folder|file structure|layout|organized|structured)\b/i,
|
|
57
|
-
// Tech stack and tools
|
|
58
|
-
/\b(using|stack|framework|library|database|api|endpoint|schema|model)\b/i,
|
|
59
|
-
// Contains bullet points or numbered lists (likely a list/summary)
|
|
60
|
-
/^\s*[-*]\s+/m,
|
|
61
|
-
/^\s*\d+[.)]\s+/m,
|
|
62
|
-
// Contains code references
|
|
63
|
-
/`[^`]+`/,
|
|
64
|
-
// Contains URLs or paths
|
|
65
|
-
/https?:\/\/\S+/,
|
|
66
|
-
/\/[\w-]+\/[\w-]+/,
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Check if text contains substantial information worth capturing.
|
|
71
|
-
* Used for incremental (per-message) capture of assistant output.
|
|
72
|
-
*/
|
|
73
|
-
export function hasSubstantialContent(text: string): boolean {
|
|
74
|
-
if (text.length < 50) return false;
|
|
75
|
-
return SUBSTANTIAL_PATTERNS.some((p) => p.test(text));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Extract capturable text segments from conversation messages.
|
|
80
|
-
* Only processes user messages to avoid self-poisoning.
|
|
81
|
-
*/
|
|
82
|
-
export function extractCapturableTexts(messages: unknown[], maxChars = 2000): string[] {
|
|
83
|
-
const texts: string[] = [];
|
|
84
|
-
|
|
85
|
-
for (const msg of messages) {
|
|
86
|
-
if (!msg || typeof msg !== 'object') continue;
|
|
87
|
-
const msgObj = msg as Record<string, unknown>;
|
|
88
|
-
|
|
89
|
-
// Only capture from user messages
|
|
90
|
-
if (msgObj.role !== 'user') continue;
|
|
91
|
-
|
|
92
|
-
const content = msgObj.content;
|
|
93
|
-
if (typeof content === 'string') {
|
|
94
|
-
if (shouldCapture(content, maxChars)) texts.push(content);
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (Array.isArray(content)) {
|
|
99
|
-
for (const block of content) {
|
|
100
|
-
if (
|
|
101
|
-
block &&
|
|
102
|
-
typeof block === 'object' &&
|
|
103
|
-
'type' in block &&
|
|
104
|
-
(block as Record<string, unknown>).type === 'text' &&
|
|
105
|
-
'text' in block &&
|
|
106
|
-
typeof (block as Record<string, unknown>).text === 'string'
|
|
107
|
-
) {
|
|
108
|
-
const text = (block as Record<string, unknown>).text as string;
|
|
109
|
-
if (shouldCapture(text, maxChars)) texts.push(text);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return texts;
|
|
116
|
-
}
|
package/src/cli.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI subcommand registration for memory management.
|
|
3
|
-
* Registers `memory` command with search, list, stats, clear subcommands.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { KeyokuClient } from '@keyoku/memory';
|
|
7
|
-
import type { PluginApi, PluginLogger } from './types.js';
|
|
8
|
-
import { formatMemoryList } from './context.js';
|
|
9
|
-
import { importMemoryFiles } from './migration.js';
|
|
10
|
-
|
|
11
|
-
// Minimal Commander-like interface for chaining
|
|
12
|
-
interface CommandChain {
|
|
13
|
-
description(desc: string): CommandChain;
|
|
14
|
-
command(name: string): CommandChain;
|
|
15
|
-
argument(name: string, desc: string): CommandChain;
|
|
16
|
-
option(flags: string, desc: string, defaultVal?: string): CommandChain;
|
|
17
|
-
action(fn: (...args: unknown[]) => Promise<void> | void): CommandChain;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function registerCli(api: PluginApi, client: KeyokuClient, entityId: string): void {
|
|
21
|
-
api.registerCli(
|
|
22
|
-
({ program }: { program: unknown; logger: PluginLogger }) => {
|
|
23
|
-
const prog = program as CommandChain;
|
|
24
|
-
const memory = prog.command('memory').description('Keyoku memory commands');
|
|
25
|
-
|
|
26
|
-
memory
|
|
27
|
-
.command('search')
|
|
28
|
-
.description('Search memories')
|
|
29
|
-
.argument('<query>', 'Search query')
|
|
30
|
-
.option('--limit <n>', 'Max results', '5')
|
|
31
|
-
.action(async (query: unknown, opts: unknown) => {
|
|
32
|
-
const q = query as string;
|
|
33
|
-
const limit = parseInt((opts as { limit: string }).limit, 10);
|
|
34
|
-
const results = await client.search(entityId, q, { limit });
|
|
35
|
-
|
|
36
|
-
if (results.length === 0) {
|
|
37
|
-
console.log('No matching memories found.');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
for (const r of results) {
|
|
42
|
-
console.log(`[${(r.similarity * 100).toFixed(0)}%] ${r.memory.content}`);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
memory
|
|
47
|
-
.command('list')
|
|
48
|
-
.description('List recent memories')
|
|
49
|
-
.option('--limit <n>', 'Max results', '20')
|
|
50
|
-
.action(async (opts: unknown) => {
|
|
51
|
-
const limit = parseInt((opts as { limit: string }).limit, 10);
|
|
52
|
-
const memories = await client.listMemories(entityId, limit);
|
|
53
|
-
console.log(formatMemoryList(memories));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
memory
|
|
57
|
-
.command('stats')
|
|
58
|
-
.description('Show memory statistics')
|
|
59
|
-
.action(async () => {
|
|
60
|
-
const stats = await client.getStats(entityId);
|
|
61
|
-
console.log(`Total: ${stats.total_memories} | Active: ${stats.active_memories}`);
|
|
62
|
-
console.log(`By type: ${JSON.stringify(stats.by_type)}`);
|
|
63
|
-
console.log(`By state: ${JSON.stringify(stats.by_state)}`);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
memory
|
|
67
|
-
.command('clear')
|
|
68
|
-
.description('Delete all memories for this entity')
|
|
69
|
-
.action(async () => {
|
|
70
|
-
await client.deleteAllMemories(entityId);
|
|
71
|
-
console.log('All memories cleared.');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
memory
|
|
75
|
-
.command('import')
|
|
76
|
-
.description('Import OpenClaw memory files (MEMORY.md, memory/*.md) into Keyoku')
|
|
77
|
-
.option('--dir <path>', 'Workspace directory containing memory files', '.')
|
|
78
|
-
.option('--dry-run', 'Show what would be imported without storing')
|
|
79
|
-
.action(async (opts: unknown) => {
|
|
80
|
-
const options = opts as { dir: string; dryRun?: boolean };
|
|
81
|
-
const result = await importMemoryFiles({
|
|
82
|
-
client,
|
|
83
|
-
entityId,
|
|
84
|
-
workspaceDir: options.dir,
|
|
85
|
-
dryRun: options.dryRun,
|
|
86
|
-
logger: console,
|
|
87
|
-
});
|
|
88
|
-
console.log(
|
|
89
|
-
`\nImport complete: ${result.imported} imported, ${result.skipped} skipped, ${result.errors} errors`,
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
},
|
|
93
|
-
{ commands: ['memory'] },
|
|
94
|
-
);
|
|
95
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin configuration types and defaults
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface KeyokuConfig {
|
|
6
|
-
/** Keyoku server URL (default: http://localhost:18900) */
|
|
7
|
-
keyokuUrl?: string;
|
|
8
|
-
/** Inject relevant memories into prompts automatically (default: true) */
|
|
9
|
-
autoRecall?: boolean;
|
|
10
|
-
/** Capture facts from conversations automatically (default: true) */
|
|
11
|
-
autoCapture?: boolean;
|
|
12
|
-
/** Enhance heartbeat runs with Keyoku data (default: true) */
|
|
13
|
-
heartbeat?: boolean;
|
|
14
|
-
/** Number of memories to inject per prompt (default: 5) */
|
|
15
|
-
topK?: number;
|
|
16
|
-
/** Memory namespace — isolates memories per entity (default: agent name) */
|
|
17
|
-
entityId?: string;
|
|
18
|
-
/** Agent identifier for memory attribution */
|
|
19
|
-
agentId?: string;
|
|
20
|
-
/** Maximum characters to consider for auto-capture (default: 2000) */
|
|
21
|
-
captureMaxChars?: number;
|
|
22
|
-
/** Autonomy level for heartbeat actions (default: 'suggest') */
|
|
23
|
-
autonomy?: 'observe' | 'suggest' | 'act';
|
|
24
|
-
/** Capture memories incrementally per message (default: true) */
|
|
25
|
-
incrementalCapture?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const DEFAULT_CONFIG: Required<KeyokuConfig> = {
|
|
29
|
-
keyokuUrl: 'http://localhost:18900',
|
|
30
|
-
autoRecall: true,
|
|
31
|
-
autoCapture: true,
|
|
32
|
-
heartbeat: true,
|
|
33
|
-
topK: 5,
|
|
34
|
-
entityId: '',
|
|
35
|
-
agentId: '',
|
|
36
|
-
captureMaxChars: 2000,
|
|
37
|
-
autonomy: 'suggest',
|
|
38
|
-
incrementalCapture: true,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export function resolveConfig(config?: KeyokuConfig): Required<KeyokuConfig> {
|
|
42
|
-
return { ...DEFAULT_CONFIG, ...config };
|
|
43
|
-
}
|