cogmem 3.6.2 → 3.6.4
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/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +2 -2
- package/README.md +60 -14
- package/RELEASE_CHECKLIST.md +13 -5
- package/dist/bin/connect.js +39 -27
- package/dist/bin/import-support.d.ts +1 -0
- package/dist/bin/import-support.js +15 -2
- package/dist/bin/mcp.js +2 -1
- package/dist/bin/migrate.js +4 -2
- package/dist/bin/openclaw.js +53 -2
- package/dist/bin/update.js +79 -22
- package/dist/episode/EpisodeStore.d.ts +7 -0
- package/dist/episode/EpisodeStore.js +75 -6
- package/dist/factory.js +3 -1
- package/dist/host/openclaw/AutoMemoryPluginInstaller.js +31 -7
- package/dist/migrations/SchemaMigrationRunner.d.ts +9 -1
- package/dist/migrations/SchemaMigrationRunner.js +47 -6
- package/examples/hermes-backend/AGENTS.md +27 -13
- package/examples/hermes-backend/README.md +25 -7
- package/examples/hermes-backend/SKILL.md +54 -17
- package/examples/hermes-backend/references/operations.md +45 -8
- package/examples/openclaw-backend/AGENTS.md +27 -6
- package/examples/openclaw-backend/README.md +26 -8
- package/examples/openclaw-backend/SKILL.md +60 -12
- package/examples/openclaw-backend/references/operations.md +49 -10
- package/install.sh +6 -0
- package/package.json +6 -2
package/dist/bin/update.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
4
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { loadCogmemConfig, resolveCogmemConfigPath } from '../config/CogmemConfig.js';
|
|
6
7
|
import { printCliJson } from './CliJson.js';
|
|
7
8
|
import { DEFAULT_NPM_PACKAGE, resolveLatestNpmSpec } from './update-release.js';
|
|
@@ -29,6 +30,8 @@ function readArgs(argv) {
|
|
|
29
30
|
json: values.json === true,
|
|
30
31
|
from: typeof values.from === 'string' ? values.from : 'latest',
|
|
31
32
|
installHome: typeof values['install-home'] === 'string' ? values['install-home'] : undefined,
|
|
33
|
+
global: values.global === true,
|
|
34
|
+
localDev: values['local-dev'] === true,
|
|
32
35
|
manager,
|
|
33
36
|
configPath: typeof values.config === 'string' ? values.config : undefined,
|
|
34
37
|
skipMigrate: values['skip-migrate'] === true,
|
|
@@ -42,7 +45,10 @@ function detectManager(cwd) {
|
|
|
42
45
|
return 'pnpm';
|
|
43
46
|
return 'npm';
|
|
44
47
|
}
|
|
45
|
-
function buildCommand(
|
|
48
|
+
function buildCommand(target, spec) {
|
|
49
|
+
if (target.kind === 'npm_global')
|
|
50
|
+
return ['npm', 'install', '-g', `cogmem@${spec}`];
|
|
51
|
+
const manager = target.manager;
|
|
46
52
|
if (manager === 'bun')
|
|
47
53
|
return ['bun', 'add', `cogmem@${spec}`];
|
|
48
54
|
if (manager === 'pnpm')
|
|
@@ -52,18 +58,18 @@ function buildCommand(manager, spec) {
|
|
|
52
58
|
function localCogmemBin(cwd) {
|
|
53
59
|
return join(cwd, 'node_modules', '.bin', 'cogmem');
|
|
54
60
|
}
|
|
55
|
-
function
|
|
61
|
+
function buildMigrationExecForTarget(target, configPath) {
|
|
56
62
|
return [
|
|
57
|
-
|
|
63
|
+
target.bin,
|
|
58
64
|
'migrate',
|
|
59
65
|
'--yes',
|
|
60
66
|
'--backup',
|
|
61
67
|
...(configPath ? ['--config', configPath] : []),
|
|
62
68
|
];
|
|
63
69
|
}
|
|
64
|
-
function
|
|
70
|
+
function buildOpenClawRepairExecForTarget(target, configPath, workspaceRoot) {
|
|
65
71
|
return [
|
|
66
|
-
|
|
72
|
+
target.bin,
|
|
67
73
|
'doctor',
|
|
68
74
|
'--fix',
|
|
69
75
|
'--agent',
|
|
@@ -99,16 +105,57 @@ function shouldUpdateCwd(cwd) {
|
|
|
99
105
|
const manifest = readPackageManifest(cwd);
|
|
100
106
|
return manifest?.name === 'cogmem' || installedSpec(cwd) !== undefined;
|
|
101
107
|
}
|
|
102
|
-
function
|
|
108
|
+
function isCogmemSourceCheckout(cwd) {
|
|
109
|
+
const manifest = readPackageManifest(cwd);
|
|
110
|
+
return manifest?.name === 'cogmem'
|
|
111
|
+
&& existsSync(join(cwd, 'src', 'bin', 'update.ts'))
|
|
112
|
+
&& existsSync(join(cwd, '.git'));
|
|
113
|
+
}
|
|
114
|
+
function ownPackageRoot() {
|
|
115
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
116
|
+
}
|
|
117
|
+
function isLikelyNpmGlobalInstall(env) {
|
|
118
|
+
if (env.COGMEM_INSTALL_KIND === 'npm_global')
|
|
119
|
+
return true;
|
|
120
|
+
const root = ownPackageRoot();
|
|
121
|
+
const marker = `${sep}node_modules${sep}cogmem`;
|
|
122
|
+
if (!root.includes(marker))
|
|
123
|
+
return false;
|
|
124
|
+
const cwd = resolve(process.cwd());
|
|
125
|
+
return !cwd.startsWith(root + sep);
|
|
126
|
+
}
|
|
127
|
+
function resolveUpdateTarget(args, env) {
|
|
103
128
|
const cwd = process.cwd();
|
|
104
|
-
if (args.
|
|
105
|
-
return
|
|
106
|
-
|
|
107
|
-
|
|
129
|
+
if (args.global || isLikelyNpmGlobalInstall(env)) {
|
|
130
|
+
return { cwd, kind: 'npm_global', manager: 'npm', bin: 'cogmem' };
|
|
131
|
+
}
|
|
132
|
+
if (args.installHome) {
|
|
133
|
+
const targetCwd = args.installHome;
|
|
134
|
+
return { cwd: targetCwd, kind: 'install_home', manager: args.manager || detectManager(targetCwd), bin: localCogmemBin(targetCwd) };
|
|
135
|
+
}
|
|
136
|
+
if (isCogmemSourceCheckout(cwd)) {
|
|
137
|
+
return {
|
|
138
|
+
cwd,
|
|
139
|
+
kind: 'source_checkout',
|
|
140
|
+
manager: args.manager || detectManager(cwd),
|
|
141
|
+
bin: localCogmemBin(cwd),
|
|
142
|
+
warning: args.localDev ? undefined : 'source_checkout_requires_local_dev_for_write',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (shouldUpdateCwd(cwd)) {
|
|
146
|
+
return { cwd, kind: 'local_project', manager: args.manager || detectManager(cwd), bin: localCogmemBin(cwd) };
|
|
147
|
+
}
|
|
108
148
|
const installHome = defaultInstallHome(env);
|
|
109
|
-
if (existsSync(join(installHome, 'package.json')))
|
|
110
|
-
return installHome;
|
|
111
|
-
|
|
149
|
+
if (existsSync(join(installHome, 'package.json'))) {
|
|
150
|
+
return { cwd: installHome, kind: 'install_home', manager: args.manager || detectManager(installHome), bin: localCogmemBin(installHome) };
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
cwd,
|
|
154
|
+
kind: 'cwd_fallback',
|
|
155
|
+
manager: args.manager || detectManager(cwd),
|
|
156
|
+
bin: localCogmemBin(cwd),
|
|
157
|
+
warning: 'no_install_home_or_project_dependency_detected',
|
|
158
|
+
};
|
|
112
159
|
}
|
|
113
160
|
function loadConfigForUpdate(args) {
|
|
114
161
|
const resolution = resolveCogmemConfigPath({ configPath: args.configPath, cwd: process.cwd() });
|
|
@@ -136,18 +183,24 @@ async function main() {
|
|
|
136
183
|
const resolvedSpec = args.from === 'latest'
|
|
137
184
|
? resolveLatestNpmSpec({ env: process.env })
|
|
138
185
|
: args.from;
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
186
|
+
const target = resolveUpdateTarget(args, process.env);
|
|
187
|
+
if (!args.dryRun && target.kind === 'source_checkout' && !args.localDev) {
|
|
188
|
+
throw new Error('Refusing to update the Cogmem source checkout. Pass --local-dev for an intentional development update, --global for npm global, or --install-home <dir> for the one-line installer home.');
|
|
189
|
+
}
|
|
190
|
+
if (!args.dryRun && target.kind === 'cwd_fallback') {
|
|
191
|
+
throw new Error('Unable to find an installed Cogmem package to update. Pass --global for npm global installs or --install-home <dir> for the one-line installer home.');
|
|
192
|
+
}
|
|
193
|
+
const manager = target.manager;
|
|
194
|
+
const command = buildCommand(target, resolvedSpec);
|
|
142
195
|
const config = loadConfigForUpdate(args);
|
|
143
196
|
const migrationExec = !args.skipMigrate && config.configPath
|
|
144
|
-
?
|
|
197
|
+
? buildMigrationExecForTarget(target, config.configPath)
|
|
145
198
|
: undefined;
|
|
146
199
|
const openclawWorkspace = config.loaded?.integrations.openclaw.enabled
|
|
147
200
|
? config.loaded.integrations.openclaw.workspaceDir || process.cwd()
|
|
148
201
|
: undefined;
|
|
149
202
|
const openclawRepairExec = !args.skipAgentRefresh && openclawWorkspace && config.configPath
|
|
150
|
-
?
|
|
203
|
+
? buildOpenClawRepairExecForTarget(target, config.configPath, openclawWorkspace)
|
|
151
204
|
: undefined;
|
|
152
205
|
const restartRequired = [
|
|
153
206
|
...(openclawWorkspace ? ['restart OpenClaw gateway or agent host'] : []),
|
|
@@ -157,13 +210,15 @@ async function main() {
|
|
|
157
210
|
command: 'update',
|
|
158
211
|
dryRun: args.dryRun,
|
|
159
212
|
manager,
|
|
213
|
+
installKind: target.kind,
|
|
160
214
|
from: args.from,
|
|
161
215
|
source: 'npm',
|
|
162
216
|
npmPackage: DEFAULT_NPM_PACKAGE,
|
|
163
217
|
packageSpec: resolvedSpec,
|
|
164
|
-
targetCwd,
|
|
165
|
-
currentSpec: installedSpec(
|
|
218
|
+
targetCwd: target.cwd,
|
|
219
|
+
currentSpec: target.kind === 'npm_global' ? 'npm:global' : installedSpec(target.cwd),
|
|
166
220
|
nextCommand: command.join(' '),
|
|
221
|
+
updateWarning: target.warning,
|
|
167
222
|
configPath: config.configPath,
|
|
168
223
|
migrationCommand: migrationExec?.join(' '),
|
|
169
224
|
migrationSkippedReason: migrationExec ? undefined : (args.skipMigrate ? 'skip_migrate_flag' : config.skippedReason),
|
|
@@ -182,6 +237,8 @@ async function main() {
|
|
|
182
237
|
else {
|
|
183
238
|
console.log(`cogmem update ${args.dryRun ? 'dry-run' : 'running'}`);
|
|
184
239
|
console.log(`target: ${result.targetCwd}`);
|
|
240
|
+
if (result.updateWarning)
|
|
241
|
+
console.log(`warning: ${result.updateWarning}`);
|
|
185
242
|
console.log(`current: ${result.currentSpec || 'not listed in package.json'}`);
|
|
186
243
|
console.log(`command: ${result.nextCommand}`);
|
|
187
244
|
if (result.migrationCommand)
|
|
@@ -191,7 +248,7 @@ async function main() {
|
|
|
191
248
|
console.log(result.followUp);
|
|
192
249
|
}
|
|
193
250
|
if (!args.dryRun) {
|
|
194
|
-
const updateExitCode = await runCommand(command,
|
|
251
|
+
const updateExitCode = await runCommand(command, target.cwd);
|
|
195
252
|
if (updateExitCode !== 0)
|
|
196
253
|
process.exit(updateExitCode);
|
|
197
254
|
if (migrationExec) {
|
|
@@ -54,6 +54,7 @@ export declare class EpisodeStore {
|
|
|
54
54
|
}): EpisodeEventLink;
|
|
55
55
|
getEventLink(eventId: string): EpisodeEventLink | undefined;
|
|
56
56
|
listEventLinks(episodeId: string): EpisodeEventLink[];
|
|
57
|
+
isEpisodeEmpty(episodeId: string): boolean;
|
|
57
58
|
addCrossReference(input: {
|
|
58
59
|
projectId: string;
|
|
59
60
|
episodeId: string;
|
|
@@ -120,6 +121,10 @@ export declare class EpisodeStore {
|
|
|
120
121
|
maxAttempts: number;
|
|
121
122
|
runId?: string;
|
|
122
123
|
}): ClaimedEpisodeDreamJob[];
|
|
124
|
+
skipEmptyDreamJobs(input: {
|
|
125
|
+
projectId?: string;
|
|
126
|
+
now?: number;
|
|
127
|
+
}): number;
|
|
123
128
|
completeDreamJob(episodeId: string, leaseId: string, candidateIds: string[], now: number): void;
|
|
124
129
|
failDreamJob(episodeId: string, leaseId: string, error: string, input: {
|
|
125
130
|
now: number;
|
|
@@ -128,6 +133,8 @@ export declare class EpisodeStore {
|
|
|
128
133
|
retryAfter?: number;
|
|
129
134
|
}): void;
|
|
130
135
|
retryFailed(projectId?: string): number;
|
|
136
|
+
private markEmptyEpisodeDreamSkipped;
|
|
137
|
+
private markEmptyEpisodeDreamSkippedMany;
|
|
131
138
|
getDreamStatus(projectId?: string): EpisodeDreamStatus;
|
|
132
139
|
countUnassignedRawEvents(projectId?: string): number;
|
|
133
140
|
markEventDisposition(input: {
|
|
@@ -142,6 +142,12 @@ export class EpisodeStore {
|
|
|
142
142
|
SELECT * FROM memory_episode_events WHERE episode_id = ? ORDER BY position
|
|
143
143
|
`).all(episodeId).map(mapEventLink);
|
|
144
144
|
}
|
|
145
|
+
isEpisodeEmpty(episodeId) {
|
|
146
|
+
const episode = this.getEpisode(episodeId);
|
|
147
|
+
if (!episode)
|
|
148
|
+
throw new Error(`episode_not_found:${episodeId}`);
|
|
149
|
+
return this.listEventLinks(episodeId).length === 0;
|
|
150
|
+
}
|
|
145
151
|
addCrossReference(input) {
|
|
146
152
|
const episode = this.getEpisode(input.episodeId);
|
|
147
153
|
if (!episode)
|
|
@@ -334,9 +340,18 @@ export class EpisodeStore {
|
|
|
334
340
|
const now = input.now ?? Date.now();
|
|
335
341
|
const episodes = this.listEpisodes({ projectId: input.projectId, statuses: ['soft_sealed'], limit: 1000 })
|
|
336
342
|
.filter((episode) => (episode.sealedAt || episode.updatedAt) <= input.sealedBefore);
|
|
337
|
-
|
|
343
|
+
let sealed = 0;
|
|
344
|
+
for (const episode of episodes) {
|
|
345
|
+
if (this.isEpisodeEmpty(episode.episodeId)) {
|
|
346
|
+
if (episode.dreamError !== 'episode_empty_soft_seal_not_promoted') {
|
|
347
|
+
this.markEmptyEpisodeDreamSkipped(episode.episodeId, now, 'episode_empty_soft_seal_not_promoted');
|
|
348
|
+
}
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
338
351
|
this.sealEpisode(episode.episodeId, { mode: 'hard', reason: 'soft_seal_stabilized', now });
|
|
339
|
-
|
|
352
|
+
sealed += 1;
|
|
353
|
+
}
|
|
354
|
+
return sealed;
|
|
340
355
|
}
|
|
341
356
|
claimDreamJobs(input) {
|
|
342
357
|
this.db.prepare(`
|
|
@@ -372,15 +387,21 @@ export class EpisodeStore {
|
|
|
372
387
|
UPDATE memory_episodes SET dream_status = 'queued', dream_error = NULL
|
|
373
388
|
WHERE episode_id IN (SELECT episode_id FROM episode_dream_jobs WHERE state = 'retry_scheduled' AND updated_at = ?)
|
|
374
389
|
`).run(input.now);
|
|
375
|
-
|
|
390
|
+
this.skipEmptyDreamJobs({ projectId: input.projectId, now: input.now });
|
|
391
|
+
const where = [`(j.state = 'pending' OR (j.state = 'retry_scheduled' AND j.attempts < ?))`];
|
|
376
392
|
const params = [input.maxAttempts];
|
|
377
393
|
if (input.projectId) {
|
|
378
|
-
where.push('project_id = ?');
|
|
394
|
+
where.push('j.project_id = ?');
|
|
379
395
|
params.push(input.projectId);
|
|
380
396
|
}
|
|
381
397
|
const rows = this.db.prepare(`
|
|
382
|
-
SELECT episode_id, project_id, mode_hint, attempts, created_at
|
|
383
|
-
|
|
398
|
+
SELECT j.episode_id, j.project_id, j.mode_hint, j.attempts, j.created_at
|
|
399
|
+
FROM episode_dream_jobs j
|
|
400
|
+
JOIN memory_episodes e ON e.episode_id = j.episode_id
|
|
401
|
+
WHERE ${where.join(' AND ')}
|
|
402
|
+
AND e.event_count > 0
|
|
403
|
+
AND EXISTS (SELECT 1 FROM memory_episode_events ee WHERE ee.episode_id = j.episode_id)
|
|
404
|
+
ORDER BY j.priority DESC, j.created_at LIMIT ?
|
|
384
405
|
`).all(...params, Math.max(1, Math.min(Math.trunc(input.limit), 100)));
|
|
385
406
|
const claimed = [];
|
|
386
407
|
for (const row of rows) {
|
|
@@ -403,6 +424,32 @@ export class EpisodeStore {
|
|
|
403
424
|
}
|
|
404
425
|
return claimed;
|
|
405
426
|
}
|
|
427
|
+
skipEmptyDreamJobs(input) {
|
|
428
|
+
const now = input.now ?? Date.now();
|
|
429
|
+
const params = [];
|
|
430
|
+
const where = [
|
|
431
|
+
`j.state IN ('pending', 'failed_retryable', 'retry_scheduled')`,
|
|
432
|
+
`(COALESCE(e.event_count, 0) = 0 OR NOT EXISTS (
|
|
433
|
+
SELECT 1 FROM memory_episode_events ee WHERE ee.episode_id = j.episode_id
|
|
434
|
+
))`,
|
|
435
|
+
];
|
|
436
|
+
if (input.projectId) {
|
|
437
|
+
where.push(`j.project_id = ?`);
|
|
438
|
+
params.push(input.projectId);
|
|
439
|
+
}
|
|
440
|
+
const rows = this.db.prepare(`
|
|
441
|
+
SELECT j.episode_id FROM episode_dream_jobs j
|
|
442
|
+
LEFT JOIN memory_episodes e ON e.episode_id = j.episode_id
|
|
443
|
+
WHERE ${where.join(' AND ')}
|
|
444
|
+
ORDER BY j.updated_at DESC
|
|
445
|
+
LIMIT 1000
|
|
446
|
+
`).all(...params);
|
|
447
|
+
const episodeIds = rows.map((row) => row.episode_id);
|
|
448
|
+
if (!episodeIds.length)
|
|
449
|
+
return 0;
|
|
450
|
+
this.markEmptyEpisodeDreamSkippedMany(episodeIds, now, 'episode_empty_skipped_no_raw_evidence');
|
|
451
|
+
return episodeIds.length;
|
|
452
|
+
}
|
|
406
453
|
completeDreamJob(episodeId, leaseId, candidateIds, now) {
|
|
407
454
|
const result = this.db.prepare(`
|
|
408
455
|
UPDATE episode_dream_jobs SET state = 'processed', candidate_ids_json = ?, lease_id = NULL,
|
|
@@ -441,6 +488,28 @@ export class EpisodeStore {
|
|
|
441
488
|
}
|
|
442
489
|
return Number(result.changes || 0);
|
|
443
490
|
}
|
|
491
|
+
markEmptyEpisodeDreamSkipped(episodeId, now, reason) {
|
|
492
|
+
this.markEmptyEpisodeDreamSkippedMany([episodeId], now, reason);
|
|
493
|
+
}
|
|
494
|
+
markEmptyEpisodeDreamSkippedMany(episodeIds, now, reason) {
|
|
495
|
+
if (!episodeIds.length)
|
|
496
|
+
return;
|
|
497
|
+
const placeholders = episodeIds.map(() => '?').join(', ');
|
|
498
|
+
this.db.transaction(() => {
|
|
499
|
+
this.db.prepare(`
|
|
500
|
+
UPDATE episode_dream_jobs SET state = 'skipped', lease_id = NULL, lease_until = NULL,
|
|
501
|
+
retry_after = NULL, failure_category = 'episode_empty', last_error = ?, candidate_ids_json = '[]',
|
|
502
|
+
updated_at = ?
|
|
503
|
+
WHERE episode_id IN (${placeholders})
|
|
504
|
+
AND state IN ('pending', 'processing', 'failed_retryable', 'retry_scheduled')
|
|
505
|
+
`).run(reason, now, ...episodeIds);
|
|
506
|
+
this.db.prepare(`
|
|
507
|
+
UPDATE memory_episodes SET dream_status = 'failed', dream_error = ?, last_dream_run_id = NULL,
|
|
508
|
+
dream_candidate_count = 0, updated_at = ?
|
|
509
|
+
WHERE episode_id IN (${placeholders})
|
|
510
|
+
`).run(reason, now, ...episodeIds);
|
|
511
|
+
})();
|
|
512
|
+
}
|
|
444
513
|
getDreamStatus(projectId) {
|
|
445
514
|
const rows = (projectId
|
|
446
515
|
? this.db.prepare(`SELECT state, COUNT(*) AS count FROM episode_dream_jobs WHERE project_id = ? GROUP BY state`).all(projectId)
|
package/dist/factory.js
CHANGED
|
@@ -83,7 +83,7 @@ import { SqliteVecStore } from './store/SqliteVecStore.js';
|
|
|
83
83
|
import { VectorStore } from './store/VectorStore.js';
|
|
84
84
|
import { config } from './utils/Config.js';
|
|
85
85
|
import { KernelRunningError, SnapshotExporter, SnapshotImporter, } from './snapshot/index.js';
|
|
86
|
-
const CORE_VERSION = '3.6.
|
|
86
|
+
const CORE_VERSION = '3.6.4';
|
|
87
87
|
const LATEST_SCHEMA_VERSION = 27;
|
|
88
88
|
export class MemoryKernel {
|
|
89
89
|
options;
|
|
@@ -983,6 +983,8 @@ export class MemoryKernel {
|
|
|
983
983
|
}
|
|
984
984
|
sealImportedEpisode(episodeId, input) {
|
|
985
985
|
const links = this.episodeStore.listEventLinks(episodeId);
|
|
986
|
+
if (!links.length)
|
|
987
|
+
throw new Error(`episode_empty:${episodeId}`);
|
|
986
988
|
const averageConfidence = links.length
|
|
987
989
|
? links.reduce((total, link) => total + link.confidence, 0) / links.length
|
|
988
990
|
: 0;
|
|
@@ -5,7 +5,7 @@ import { basename, dirname, join, resolve } from 'node:path';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { resolveCogmemConfigPath } from '../../config/CogmemConfig.js';
|
|
7
7
|
const PLUGIN_ID = 'cogmem-auto-memory';
|
|
8
|
-
const PLUGIN_VERSION = '0.6.
|
|
8
|
+
const PLUGIN_VERSION = '0.6.3';
|
|
9
9
|
function defaultPublicEntrypoint() {
|
|
10
10
|
return join(resolve(dirname(fileURLToPath(import.meta.url)), '../..'), 'public.js');
|
|
11
11
|
}
|
|
@@ -735,6 +735,7 @@ function bridgeConfig(config) {
|
|
|
735
735
|
rememberQueuePath: rememberQueuePath(config),
|
|
736
736
|
rememberMaxAttempts: config.rememberMaxAttempts || 3,
|
|
737
737
|
rememberDrainBatchSize: config.rememberDrainBatchSize || 20,
|
|
738
|
+
rememberDrainTimeoutMs: config.rememberDrainTimeoutMs || 60000,
|
|
738
739
|
};
|
|
739
740
|
}
|
|
740
741
|
|
|
@@ -988,7 +989,7 @@ function audit(config, record) {
|
|
|
988
989
|
const plugin = {
|
|
989
990
|
id: PLUGIN_ID,
|
|
990
991
|
name: 'CogMem Auto Memory',
|
|
991
|
-
version: '0.6.
|
|
992
|
+
version: '0.6.3',
|
|
992
993
|
register(api) {
|
|
993
994
|
if (!api || typeof api.on !== 'function') {
|
|
994
995
|
throw new Error('OpenClaw plugin API missing api.on');
|
|
@@ -1231,7 +1232,7 @@ module.exports.__testing = {
|
|
|
1231
1232
|
}
|
|
1232
1233
|
function pluginBridgeMjs() {
|
|
1233
1234
|
return String.raw `#!/usr/bin/env bun
|
|
1234
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
1235
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
1235
1236
|
import { dirname, join } from 'node:path';
|
|
1236
1237
|
import { pathToFileURL } from 'node:url';
|
|
1237
1238
|
|
|
@@ -1581,7 +1582,8 @@ function acquireRememberQueueLock(bridgeConfig) {
|
|
|
1581
1582
|
if (!queuePath) throw new Error('missing rememberQueuePath');
|
|
1582
1583
|
mkdirSync(dirname(queuePath), { recursive: true });
|
|
1583
1584
|
const lockPath = queuePath + '.lock';
|
|
1584
|
-
|
|
1585
|
+
const recoveredProcessing = recoverStaleProcessingFiles(bridgeConfig);
|
|
1586
|
+
if (!existsSync(queuePath)) return { acquired: false, locked: false, empty: true, lockPath, recoveredProcessing };
|
|
1585
1587
|
const timeoutMs = Number(bridgeConfig.rememberDrainTimeoutMs || 60000);
|
|
1586
1588
|
try {
|
|
1587
1589
|
mkdirSync(lockPath);
|
|
@@ -1602,12 +1604,12 @@ function acquireRememberQueueLock(bridgeConfig) {
|
|
|
1602
1604
|
staleLockRecovered: true,
|
|
1603
1605
|
queuePath,
|
|
1604
1606
|
}) + '\n');
|
|
1605
|
-
return { acquired: true, locked: false, empty: false, lockPath, staleRecovered: true };
|
|
1607
|
+
return { acquired: true, locked: false, empty: false, lockPath, staleRecovered: true, recoveredProcessing };
|
|
1606
1608
|
}
|
|
1607
1609
|
} catch {}
|
|
1608
1610
|
return { acquired: false, locked: true, empty: false, lockPath };
|
|
1609
1611
|
}
|
|
1610
|
-
return { acquired: true, locked: false, empty: false, lockPath };
|
|
1612
|
+
return { acquired: true, locked: false, empty: false, lockPath, recoveredProcessing };
|
|
1611
1613
|
}
|
|
1612
1614
|
|
|
1613
1615
|
async function drainRememberQueueWithLock(bridgeConfig, queueLock, kernel, memory) {
|
|
@@ -1655,7 +1657,29 @@ async function drainRememberQueueWithLock(bridgeConfig, queueLock, kernel, memor
|
|
|
1655
1657
|
} finally {
|
|
1656
1658
|
if (queueLock.acquired) rmSync(queueLock.lockPath, { recursive: true, force: true });
|
|
1657
1659
|
}
|
|
1658
|
-
return { drained, failed, deferred, locked: false, staleRecovered: queueLock.staleRecovered === true };
|
|
1660
|
+
return { drained, failed, deferred, locked: false, staleRecovered: queueLock.staleRecovered === true, recoveredProcessing: queueLock.recoveredProcessing || 0 };
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
function recoverStaleProcessingFiles(bridgeConfig) {
|
|
1664
|
+
const queuePath = bridgeConfig.rememberQueuePath;
|
|
1665
|
+
const dir = dirname(queuePath);
|
|
1666
|
+
const prefix = queuePath.split('/').pop() + '.';
|
|
1667
|
+
const timeoutMs = Number(bridgeConfig.rememberDrainTimeoutMs || 60000);
|
|
1668
|
+
let recovered = 0;
|
|
1669
|
+
if (!existsSync(dir)) return recovered;
|
|
1670
|
+
for (const entry of readdirSync(dir)) {
|
|
1671
|
+
if (!entry.startsWith(prefix) || !entry.endsWith('.processing')) continue;
|
|
1672
|
+
const path = join(dir, entry);
|
|
1673
|
+
try {
|
|
1674
|
+
const ageMs = Date.now() - statSync(path).mtimeMs;
|
|
1675
|
+
if (!Number.isFinite(ageMs) || ageMs < timeoutMs) continue;
|
|
1676
|
+
const lines = readFileSync(path, 'utf8').split('\n').map((line) => line.trim()).filter(Boolean);
|
|
1677
|
+
for (const line of lines) appendFileSync(queuePath, line + '\n');
|
|
1678
|
+
rmSync(path, { force: true });
|
|
1679
|
+
recovered += lines.length;
|
|
1680
|
+
} catch {}
|
|
1681
|
+
}
|
|
1682
|
+
return recovered;
|
|
1659
1683
|
}
|
|
1660
1684
|
|
|
1661
1685
|
function compactRecallItems(items, config) {
|
|
@@ -3,6 +3,9 @@ import type { Migration } from '../types/Migration.js';
|
|
|
3
3
|
export interface SchemaMigrationRunOptions {
|
|
4
4
|
dryRun?: boolean;
|
|
5
5
|
}
|
|
6
|
+
export interface SchemaMigrationRunnerOptions {
|
|
7
|
+
readonly?: boolean;
|
|
8
|
+
}
|
|
6
9
|
export interface SchemaMigrationResult {
|
|
7
10
|
pending: string[];
|
|
8
11
|
applied: string[];
|
|
@@ -12,10 +15,15 @@ export interface SchemaMigrationResult {
|
|
|
12
15
|
export declare class SchemaMigrationRunner {
|
|
13
16
|
private readonly db;
|
|
14
17
|
private readonly migrations;
|
|
15
|
-
|
|
18
|
+
private readonly options;
|
|
19
|
+
constructor(db: Database, migrations: Migration[], options?: SchemaMigrationRunnerOptions);
|
|
16
20
|
plan(): Migration[];
|
|
17
21
|
run(options?: SchemaMigrationRunOptions): SchemaMigrationResult;
|
|
18
22
|
currentVersion(): string | undefined;
|
|
23
|
+
private appliedVersions;
|
|
24
|
+
private schemaMigrationsTableExists;
|
|
25
|
+
private legacySchemaVersion;
|
|
26
|
+
private legacyCurrentVersion;
|
|
19
27
|
private adoptLegacyVersion;
|
|
20
28
|
}
|
|
21
29
|
//# sourceMappingURL=SchemaMigrationRunner.d.ts.map
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export class SchemaMigrationRunner {
|
|
2
2
|
db;
|
|
3
3
|
migrations;
|
|
4
|
-
|
|
4
|
+
options;
|
|
5
|
+
constructor(db, migrations, options = {}) {
|
|
5
6
|
this.db = db;
|
|
6
7
|
this.migrations = migrations;
|
|
8
|
+
this.options = options;
|
|
9
|
+
if (this.options.readonly)
|
|
10
|
+
return;
|
|
7
11
|
this.db.exec(`
|
|
8
12
|
CREATE TABLE IF NOT EXISTS _schema_migrations (
|
|
9
13
|
version TEXT PRIMARY KEY,
|
|
@@ -14,7 +18,7 @@ export class SchemaMigrationRunner {
|
|
|
14
18
|
this.adoptLegacyVersion();
|
|
15
19
|
}
|
|
16
20
|
plan() {
|
|
17
|
-
const applied =
|
|
21
|
+
const applied = this.appliedVersions();
|
|
18
22
|
return [...this.migrations]
|
|
19
23
|
.sort((a, b) => a.version.localeCompare(b.version))
|
|
20
24
|
.filter((migration) => !applied.has(migration.version));
|
|
@@ -39,20 +43,57 @@ export class SchemaMigrationRunner {
|
|
|
39
43
|
return { pending: pending.map((item) => item.version), applied, currentVersion: this.currentVersion(), dryRun: false };
|
|
40
44
|
}
|
|
41
45
|
currentVersion() {
|
|
46
|
+
const legacyCurrent = this.legacyCurrentVersion();
|
|
47
|
+
if (!this.schemaMigrationsTableExists()) {
|
|
48
|
+
return legacyCurrent;
|
|
49
|
+
}
|
|
42
50
|
const row = this.db.prepare(`
|
|
43
51
|
SELECT version FROM _schema_migrations ORDER BY version DESC LIMIT 1
|
|
44
52
|
`).get();
|
|
45
|
-
return row?.version;
|
|
53
|
+
return [row?.version, legacyCurrent].filter((version) => Boolean(version)).sort((a, b) => b.localeCompare(a))[0];
|
|
46
54
|
}
|
|
47
|
-
|
|
55
|
+
appliedVersions() {
|
|
56
|
+
const applied = new Set();
|
|
57
|
+
const legacyVersion = this.legacySchemaVersion();
|
|
58
|
+
for (const migration of this.migrations) {
|
|
59
|
+
if (legacyVersion !== undefined && Number.parseInt(migration.version, 10) <= legacyVersion) {
|
|
60
|
+
applied.add(migration.version);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!this.schemaMigrationsTableExists()) {
|
|
64
|
+
return applied;
|
|
65
|
+
}
|
|
66
|
+
for (const row of this.db.prepare(`SELECT version FROM _schema_migrations`).all()) {
|
|
67
|
+
applied.add(row.version);
|
|
68
|
+
}
|
|
69
|
+
return applied;
|
|
70
|
+
}
|
|
71
|
+
schemaMigrationsTableExists() {
|
|
72
|
+
return Boolean(this.db.prepare(`
|
|
73
|
+
SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_schema_migrations'
|
|
74
|
+
`).get());
|
|
75
|
+
}
|
|
76
|
+
legacySchemaVersion() {
|
|
48
77
|
const metaExists = this.db.prepare(`
|
|
49
78
|
SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_meta'
|
|
50
79
|
`).get();
|
|
51
80
|
if (!metaExists)
|
|
52
|
-
return;
|
|
81
|
+
return undefined;
|
|
53
82
|
const row = this.db.prepare(`SELECT value FROM _meta WHERE key = 'schema_version'`).get();
|
|
54
83
|
const legacyVersion = Number.parseInt(row?.value || '', 10);
|
|
55
|
-
|
|
84
|
+
return Number.isFinite(legacyVersion) ? legacyVersion : undefined;
|
|
85
|
+
}
|
|
86
|
+
legacyCurrentVersion() {
|
|
87
|
+
const legacyVersion = this.legacySchemaVersion();
|
|
88
|
+
if (legacyVersion === undefined)
|
|
89
|
+
return undefined;
|
|
90
|
+
return [...this.migrations]
|
|
91
|
+
.filter((migration) => Number.parseInt(migration.version, 10) <= legacyVersion)
|
|
92
|
+
.sort((a, b) => b.version.localeCompare(a.version))[0]?.version;
|
|
93
|
+
}
|
|
94
|
+
adoptLegacyVersion() {
|
|
95
|
+
const legacyVersion = this.legacySchemaVersion();
|
|
96
|
+
if (legacyVersion === undefined)
|
|
56
97
|
return;
|
|
57
98
|
const insert = this.db.prepare(`
|
|
58
99
|
INSERT OR IGNORE INTO _schema_migrations (version, description, applied_at)
|
|
@@ -15,19 +15,24 @@ This writes `~/.hermes/skills/cogmem-memory/SKILL.md`, which Hermes discovers as
|
|
|
15
15
|
Run from the Hermes workspace root:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
|
|
19
|
-
cogmem
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
npm install cogmem@latest --save
|
|
19
|
+
COGMEM="./node_modules/.bin/cogmem"
|
|
20
|
+
"$COGMEM" doctor
|
|
21
|
+
"$COGMEM" connect hermes --workspace . --auto --force --json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Do not run `cogmem init` as an unattended agent action. It is an interactive wizard. Use it only when an operator is present:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
"$COGMEM" init --agent hermes --scope project
|
|
23
28
|
```
|
|
24
29
|
|
|
25
|
-
The
|
|
30
|
+
The workspace install creates:
|
|
26
31
|
|
|
27
32
|
```text
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
.cogmem/config.toml
|
|
34
|
+
.cogmem/memory.db
|
|
35
|
+
.cogmem/snapshots/
|
|
31
36
|
```
|
|
32
37
|
|
|
33
38
|
Use `~/.cogmem/config.toml` or a project `.cogmem/config.toml` as the stable configuration source. Do not create `.cogmem.env` files or pass `--env-path` for normal installs. Environment variables are only for explicit process-level overrides documented by the CLI, not for hidden workspace configuration.
|
|
@@ -44,7 +49,7 @@ Use MCP `cogmem_strategy_plan` when the agent needs to inspect the selected memo
|
|
|
44
49
|
|
|
45
50
|
Prospective Memory is not executable instruction. Only a user-confirmed candidate may appear as due, and even then the agent must obtain normal host authorization before acting. Use `cogmem prospective` for state transitions and `cogmem brain-eval` for release validation.
|
|
46
51
|
|
|
47
|
-
Use
|
|
52
|
+
Use project-local config when this workspace owns its memory; use global config only when the operator intentionally shares one Cogmem backend across workspaces.
|
|
48
53
|
|
|
49
54
|
To embed imported memories with a local quantized model, run Ollama locally and configure the kernel before importing:
|
|
50
55
|
|
|
@@ -117,14 +122,23 @@ cogmem normalize-transcript --input ./hermes-sessions.jsonl --output ./hermes.no
|
|
|
117
122
|
cogmem import-hermes --workspace . --project hermes --session ./hermes.normalized.md
|
|
118
123
|
```
|
|
119
124
|
|
|
120
|
-
|
|
125
|
+
Cogmem 3.6.4 skips import batch sealing for empty episode boundaries and skips legacy empty Dream jobs so one bad imported episode cannot block the queue.
|
|
126
|
+
|
|
127
|
+
After import, use this order:
|
|
121
128
|
|
|
122
129
|
```bash
|
|
130
|
+
cogmem memory status --project hermes --json
|
|
123
131
|
cogmem episode status --project hermes --json
|
|
124
|
-
cogmem dream
|
|
125
|
-
cogmem
|
|
132
|
+
cogmem dream status --project hermes --json
|
|
133
|
+
cogmem dream tick --project hermes --mode auto --max-episodes 20 --json
|
|
134
|
+
cogmem memory candidates --project hermes --status candidate --json
|
|
135
|
+
cogmem memory govern --project hermes --limit 100 --json
|
|
136
|
+
cogmem memory candidates --project hermes --status needs_confirmation --json
|
|
137
|
+
cogmem memory review --project hermes --id <candidate-id> --action approve --actor <operator> --reason "confirmed by user" --confirmation-event <user-event-id> --json
|
|
126
138
|
```
|
|
127
139
|
|
|
140
|
+
`needs_confirmation` is not the Dream backlog. `memory govern` does not approve it; use `memory review` with explicit evidence.
|
|
141
|
+
|
|
128
142
|
## Active Memory Search
|
|
129
143
|
|
|
130
144
|
If the current prompt does not include enough Cogmem memory context, query Cogmem directly before searching legacy files:
|