@alfe.ai/openclaw-memory-cloud 0.0.25 → 0.0.26
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -7
- package/dist/index.js.map +1 -1
- package/dist/session-backfill.d.ts +54 -0
- package/dist/session-backfill.d.ts.map +1 -0
- package/dist/session-backfill.js +192 -0
- package/dist/session-backfill.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/openclaw.plugin.json +20 -0
- package/package.json +4 -2
- package/src/__tests__/session-backfill.test.ts +289 -0
- package/src/index.ts +43 -8
- package/src/session-backfill.ts +220 -0
- package/src/types.ts +2 -0
- package/tsconfig.json +1 -1
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @alfe.ai/openclaw-memory-cloud
|
|
2
2
|
|
|
3
|
+
## 0.0.26
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0f90085: One-time backfill of existing chat history when memory is first installed. On
|
|
8
|
+
startup, if the server-side `sessionsBackfillSyncedAt` flag is unset, the plugin
|
|
9
|
+
reads the openclaw-chat session files (`~/.alfe/sessions/chat/*.json`), takes the
|
|
10
|
+
most recent `backfillMaxSessions` (default 50) sessions, and ingests messages
|
|
11
|
+
through the existing `/agent/memory/ingest` pipeline. Duplicate-safe by
|
|
12
|
+
construction: a timestamp cutoff excludes anything already captured live (agents
|
|
13
|
+
that had memory before this release only backfill pre-install messages), and the
|
|
14
|
+
ingest consumer's per-session `lastProcessedIndex` high-water mark makes retries
|
|
15
|
+
free. New plugin config: `backfillSessions` (default true), `backfillMaxSessions`
|
|
16
|
+
(default 50). agent-api-client: `memoryBootstrapStatus()` now also returns
|
|
17
|
+
`sessionsBackfillSynced`/`sessionsBackfillSyncedAt`, and
|
|
18
|
+
`memoryBootstrapStatusMark()` accepts an optional `scope` ("files" | "sessions").
|
|
19
|
+
- Updated dependencies [0f90085]
|
|
20
|
+
- @alfe.ai/agent-api-client@0.6.0
|
|
21
|
+
|
|
3
22
|
## 0.0.25
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAgBH,UAAU,SAAS;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE;QACN,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC3D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC5D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC3D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAC7D,CAAC;IACF,YAAY,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,KAAK,IAAI,CAAC;IACzF,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5I,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACvG,2BAA2B,EAAE,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE;QAAE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;KAAE,KAAK,MAAM,EAAE,KAAK,IAAI,CAAC;IACtG,qBAAqB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACnD;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,IAAI;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;;;;;;;kBAkCe,SAAS,GAAG,IAAI;;AAPhC,wBAoRE"}
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
|
18
18
|
import { AutoCapture } from "./auto-capture.js";
|
|
19
19
|
import { AutoRecall } from "./auto-recall.js";
|
|
20
20
|
import { formatSearchResults } from "./formatter.js";
|
|
21
|
+
import { runSessionsBackfill } from "./session-backfill.js";
|
|
21
22
|
import { createRequire } from 'node:module';
|
|
22
23
|
const require = createRequire(import.meta.url);
|
|
23
24
|
const pkg = require('../package.json');
|
|
@@ -26,6 +27,8 @@ const DEFAULT_CONFIG = {
|
|
|
26
27
|
autoRecall: true,
|
|
27
28
|
captureMaxChars: 500,
|
|
28
29
|
idleFlushSeconds: 60,
|
|
30
|
+
backfillSessions: true,
|
|
31
|
+
backfillMaxSessions: 50,
|
|
29
32
|
};
|
|
30
33
|
function resolvePluginConfig(pluginConfig) {
|
|
31
34
|
return {
|
|
@@ -33,6 +36,10 @@ function resolvePluginConfig(pluginConfig) {
|
|
|
33
36
|
autoRecall: typeof pluginConfig?.autoRecall === "boolean" ? pluginConfig.autoRecall : DEFAULT_CONFIG.autoRecall,
|
|
34
37
|
captureMaxChars: typeof pluginConfig?.captureMaxChars === "number" ? pluginConfig.captureMaxChars : DEFAULT_CONFIG.captureMaxChars,
|
|
35
38
|
idleFlushSeconds: typeof pluginConfig?.idleFlushSeconds === "number" ? pluginConfig.idleFlushSeconds : DEFAULT_CONFIG.idleFlushSeconds,
|
|
39
|
+
backfillSessions: typeof pluginConfig?.backfillSessions === "boolean" ? pluginConfig.backfillSessions : DEFAULT_CONFIG.backfillSessions,
|
|
40
|
+
backfillMaxSessions: typeof pluginConfig?.backfillMaxSessions === "number" && Number.isFinite(pluginConfig.backfillMaxSessions) && pluginConfig.backfillMaxSessions > 0
|
|
41
|
+
? Math.floor(pluginConfig.backfillMaxSessions)
|
|
42
|
+
: DEFAULT_CONFIG.backfillMaxSessions,
|
|
36
43
|
};
|
|
37
44
|
}
|
|
38
45
|
export default {
|
|
@@ -280,16 +287,20 @@ export default {
|
|
|
280
287
|
await autoCapture.onAgentEnd();
|
|
281
288
|
}
|
|
282
289
|
});
|
|
283
|
-
// Fire-and-forget first-run sync:
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
void
|
|
287
|
-
logger.warn("memory
|
|
290
|
+
// Fire-and-forget first-run sync: workspace files (MEMORY.md / memory/*.md)
|
|
291
|
+
// and pre-existing chat sessions, each idempotent via its own
|
|
292
|
+
// PalaceMetadata flag (bootstrapSyncedAt / sessionsBackfillSyncedAt).
|
|
293
|
+
void runStartupSync(client, config, logger).catch((err) => {
|
|
294
|
+
logger.warn("memory startup sync failed; will retry on next startup", { err: String(err) });
|
|
288
295
|
});
|
|
289
296
|
logger.info("memory-cloud extension registered", { autoCapture: config.autoCapture, autoRecall: config.autoRecall });
|
|
290
297
|
},
|
|
291
298
|
};
|
|
292
|
-
async function
|
|
299
|
+
async function runStartupSync(client, config, logger) {
|
|
300
|
+
// Fetch status ONCE, before the file bootstrap runs: the sessions backfill
|
|
301
|
+
// computes its dedup cutoff from the pre-bootstrap `syncedAt` (an agent
|
|
302
|
+
// that already had memory installed captured everything after that moment
|
|
303
|
+
// live, so backfilling past it would duplicate memories).
|
|
293
304
|
let status;
|
|
294
305
|
try {
|
|
295
306
|
status = await client.memoryBootstrapStatus();
|
|
@@ -298,10 +309,29 @@ async function runBootstrap(client, logger) {
|
|
|
298
309
|
logger.debug("memory bootstrap status check failed; skipping", { err: String(err) });
|
|
299
310
|
return;
|
|
300
311
|
}
|
|
312
|
+
const startupMs = Date.now();
|
|
301
313
|
if (status.synced) {
|
|
302
314
|
logger.debug("memory bootstrap: already synced, skipping");
|
|
303
|
-
return;
|
|
304
315
|
}
|
|
316
|
+
else {
|
|
317
|
+
try {
|
|
318
|
+
await runBootstrap(client, logger);
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
logger.warn("memory bootstrap failed; will retry on next startup", { err: String(err) });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Strict `=== false` on purpose: an old server (deployed before the
|
|
325
|
+
// sessions-backfill release) omits the field entirely, and its mark
|
|
326
|
+
// endpoint would clobber `bootstrapSyncedAt` — corrupting the cutoff
|
|
327
|
+
// anchor. Defer until the service is deployed and reports the flag.
|
|
328
|
+
if (config.backfillSessions && status.sessionsBackfillSynced === false) {
|
|
329
|
+
const installedAtMs = status.syncedAt ? Date.parse(status.syncedAt) : Number.POSITIVE_INFINITY;
|
|
330
|
+
const cutoffMs = Math.min(startupMs, Number.isNaN(installedAtMs) ? Number.POSITIVE_INFINITY : installedAtMs);
|
|
331
|
+
await runSessionsBackfill(client, config, logger, { cutoffMs });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function runBootstrap(client, logger) {
|
|
305
335
|
const cwd = process.cwd();
|
|
306
336
|
const filesToSync = [];
|
|
307
337
|
try {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session backfill — one-time ingestion of pre-existing chat history into
|
|
3
|
+
* cloud memory, run fire-and-forget at plugin startup (see index.ts).
|
|
4
|
+
*
|
|
5
|
+
* Reads the openclaw-chat plugin's on-disk session files directly:
|
|
6
|
+
* ~/.alfe/sessions/chat/{sessionId}.json
|
|
7
|
+
* The write side is packages/openclaw-chat/src/session-store.ts — that path
|
|
8
|
+
* and file shape are a read contract; keep the two in sync. The chat plugin
|
|
9
|
+
* is not a package dependency because its session-store functions aren't
|
|
10
|
+
* part of its public entry and the plugin isn't guaranteed to be installed
|
|
11
|
+
* alongside memory. Like the session store itself, this assumes one agent
|
|
12
|
+
* per machine (`alfe remove` deletes ~/.alfe, so files never outlive the
|
|
13
|
+
* agent they belong to).
|
|
14
|
+
*
|
|
15
|
+
* No-re-sync guarantees (in concert with services/memory):
|
|
16
|
+
* 1. Runs only while the server-side `sessionsBackfillSyncedAt` flag is
|
|
17
|
+
* unset; the flag survives integration uninstall/reinstall.
|
|
18
|
+
* 2. Only messages with timestamp < cutoffMs are ingested — for agents that
|
|
19
|
+
* already had memory installed, everything after `bootstrapSyncedAt` was
|
|
20
|
+
* already captured live by AutoCapture.
|
|
21
|
+
* 3. sessionKey = file sessionId and index = file array position, so the
|
|
22
|
+
* ingest consumer's per-session `lastProcessedIndex` high-water mark makes
|
|
23
|
+
* a crash-and-retry replay free (filtered server-side before any cost).
|
|
24
|
+
*
|
|
25
|
+
* Each session is sent as exactly ONE memoryIngest call (one SQS message,
|
|
26
|
+
* windowed to the most recent messages that fit the caps below). Splitting a
|
|
27
|
+
* session across multiple SQS messages would race the shared per-session
|
|
28
|
+
* high-water mark on the standard (non-FIFO) ingest queue: a transiently
|
|
29
|
+
* failed early chunk redelivered after a later chunk succeeds gets filtered
|
|
30
|
+
* out and silently lost.
|
|
31
|
+
*/
|
|
32
|
+
import type { MemoryCloudConfig, MemoryMessage, SessionMetadata } from "./types.js";
|
|
33
|
+
interface BackfillApi {
|
|
34
|
+
memoryIngest(sessionKey: string, messages: MemoryMessage[], metadata?: SessionMetadata): Promise<{
|
|
35
|
+
queued: boolean;
|
|
36
|
+
messageCount: number;
|
|
37
|
+
}>;
|
|
38
|
+
memoryBootstrapStatusMark(scope?: "files" | "sessions"): Promise<{
|
|
39
|
+
synced: true;
|
|
40
|
+
syncedAt: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
interface BackfillLogger {
|
|
44
|
+
info: (msg: string, ctx?: Record<string, unknown>) => void;
|
|
45
|
+
debug: (msg: string, ctx?: Record<string, unknown>) => void;
|
|
46
|
+
warn: (msg: string, ctx?: Record<string, unknown>) => void;
|
|
47
|
+
}
|
|
48
|
+
export interface SessionsBackfillOptions {
|
|
49
|
+
cutoffMs: number;
|
|
50
|
+
sessionsDir?: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function runSessionsBackfill(client: BackfillApi, config: MemoryCloudConfig, logger: BackfillLogger, opts: SessionsBackfillOptions): Promise<void>;
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=session-backfill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-backfill.d.ts","sourceRoot":"","sources":["../src/session-backfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAcpF,UAAU,WAAW;IACnB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5I,yBAAyB,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtG;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC3D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC5D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAC5D;AAWD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAmEf"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session backfill — one-time ingestion of pre-existing chat history into
|
|
3
|
+
* cloud memory, run fire-and-forget at plugin startup (see index.ts).
|
|
4
|
+
*
|
|
5
|
+
* Reads the openclaw-chat plugin's on-disk session files directly:
|
|
6
|
+
* ~/.alfe/sessions/chat/{sessionId}.json
|
|
7
|
+
* The write side is packages/openclaw-chat/src/session-store.ts — that path
|
|
8
|
+
* and file shape are a read contract; keep the two in sync. The chat plugin
|
|
9
|
+
* is not a package dependency because its session-store functions aren't
|
|
10
|
+
* part of its public entry and the plugin isn't guaranteed to be installed
|
|
11
|
+
* alongside memory. Like the session store itself, this assumes one agent
|
|
12
|
+
* per machine (`alfe remove` deletes ~/.alfe, so files never outlive the
|
|
13
|
+
* agent they belong to).
|
|
14
|
+
*
|
|
15
|
+
* No-re-sync guarantees (in concert with services/memory):
|
|
16
|
+
* 1. Runs only while the server-side `sessionsBackfillSyncedAt` flag is
|
|
17
|
+
* unset; the flag survives integration uninstall/reinstall.
|
|
18
|
+
* 2. Only messages with timestamp < cutoffMs are ingested — for agents that
|
|
19
|
+
* already had memory installed, everything after `bootstrapSyncedAt` was
|
|
20
|
+
* already captured live by AutoCapture.
|
|
21
|
+
* 3. sessionKey = file sessionId and index = file array position, so the
|
|
22
|
+
* ingest consumer's per-session `lastProcessedIndex` high-water mark makes
|
|
23
|
+
* a crash-and-retry replay free (filtered server-side before any cost).
|
|
24
|
+
*
|
|
25
|
+
* Each session is sent as exactly ONE memoryIngest call (one SQS message,
|
|
26
|
+
* windowed to the most recent messages that fit the caps below). Splitting a
|
|
27
|
+
* session across multiple SQS messages would race the shared per-session
|
|
28
|
+
* high-water mark on the standard (non-FIFO) ingest queue: a transiently
|
|
29
|
+
* failed early chunk redelivered after a later chunk succeeds gets filtered
|
|
30
|
+
* out and silently lost.
|
|
31
|
+
*/
|
|
32
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
33
|
+
import { join } from "node:path";
|
|
34
|
+
import { homedir } from "node:os";
|
|
35
|
+
const DEFAULT_SESSIONS_DIR = join(homedir(), ".alfe", "sessions", "chat");
|
|
36
|
+
// Server-side zod caps on /agent/memory/ingest (services/memory
|
|
37
|
+
// api-agents/ingest/post.ts) — exceeding any of these is a deterministic 400,
|
|
38
|
+
// which would put the startup retry loop into a permanent failure.
|
|
39
|
+
const MAX_MESSAGES_PER_SESSION = 500;
|
|
40
|
+
const MAX_SESSION_KEY_CHARS = 256;
|
|
41
|
+
const MAX_MESSAGE_INDEX = 1_000_000;
|
|
42
|
+
const MAX_CONTENT_CHARS = 100_000;
|
|
43
|
+
/** One ingest call becomes one SQS SendMessage (256KB hard limit). */
|
|
44
|
+
const MAX_BATCH_BYTES = 180_000;
|
|
45
|
+
export async function runSessionsBackfill(client, config, logger, opts) {
|
|
46
|
+
const sessionsDir = opts.sessionsDir ?? DEFAULT_SESSIONS_DIR;
|
|
47
|
+
let fileNames;
|
|
48
|
+
try {
|
|
49
|
+
fileNames = (await readdir(sessionsDir)).filter((name) => name.endsWith(".json"));
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
if (err.code === "ENOENT") {
|
|
53
|
+
// No sessions directory — nothing to backfill, ever.
|
|
54
|
+
logger.debug("sessions backfill: no sessions directory, marking complete");
|
|
55
|
+
try {
|
|
56
|
+
await client.memoryBootstrapStatusMark("sessions");
|
|
57
|
+
}
|
|
58
|
+
catch { /* retry next start */ }
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Transient (EACCES/EMFILE/...) — don't forfeit the backfill.
|
|
62
|
+
logger.warn("sessions backfill: sessions directory unreadable; will retry on next startup", { err: String(err) });
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const sessions = [];
|
|
67
|
+
for (const name of fileNames) {
|
|
68
|
+
const parsed = await parseSessionFile(join(sessionsDir, name), config.captureMaxChars, opts.cutoffMs);
|
|
69
|
+
if (parsed)
|
|
70
|
+
sessions.push(parsed);
|
|
71
|
+
}
|
|
72
|
+
// Most recent non-empty sessions first, capped. Filter BEFORE capping:
|
|
73
|
+
// on a retrofit agent the newest sessions are often entirely post-install
|
|
74
|
+
// (emptied by the cutoff) and must not occupy the cap slots.
|
|
75
|
+
const eligible = sessions
|
|
76
|
+
.filter((s) => s.messages.length > 0)
|
|
77
|
+
.sort((a, b) => b.lastMessageMs - a.lastMessageMs)
|
|
78
|
+
.slice(0, config.backfillMaxSessions);
|
|
79
|
+
if (eligible.length === 0) {
|
|
80
|
+
logger.debug("sessions backfill: no eligible sessions, marking complete");
|
|
81
|
+
try {
|
|
82
|
+
await client.memoryBootstrapStatusMark("sessions");
|
|
83
|
+
}
|
|
84
|
+
catch { /* retry next start */ }
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
logger.info("sessions backfill: ingesting sessions", {
|
|
88
|
+
sessionCount: eligible.length,
|
|
89
|
+
messageCount: eligible.reduce((n, s) => n + s.messages.length, 0),
|
|
90
|
+
});
|
|
91
|
+
for (const session of eligible) {
|
|
92
|
+
const metadata = {
|
|
93
|
+
...(session.channel ? { channelId: session.channel } : {}),
|
|
94
|
+
...(session.userId ? { userId: session.userId } : {}),
|
|
95
|
+
};
|
|
96
|
+
try {
|
|
97
|
+
await client.memoryIngest(session.sessionId, session.messages, metadata);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Abort without marking — next startup replays, and the server-side
|
|
101
|
+
// lastProcessedIndex filter discards already-processed sessions free.
|
|
102
|
+
logger.warn("sessions backfill: ingest failed; will retry on next startup", {
|
|
103
|
+
sessionId: session.sessionId,
|
|
104
|
+
err: String(err),
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await client.memoryBootstrapStatusMark("sessions");
|
|
111
|
+
logger.info("sessions backfill: complete", { sessionCount: eligible.length });
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
logger.warn("sessions backfill: mark failed; will retry on next startup", { err: String(err) });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Tolerant parse of a single session file. Returns null for corrupt or
|
|
119
|
+
* shape-mismatched files (skipped, never fatal). Message `index` is the
|
|
120
|
+
* original array position in the file — stable because the store is
|
|
121
|
+
* append-only — so retries resume idempotently server-side.
|
|
122
|
+
*/
|
|
123
|
+
async function parseSessionFile(filePath, captureMaxChars, cutoffMs) {
|
|
124
|
+
let raw;
|
|
125
|
+
try {
|
|
126
|
+
raw = JSON.parse(await readFile(filePath, "utf8"));
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
if (typeof raw !== "object" || raw === null)
|
|
132
|
+
return null;
|
|
133
|
+
const data = raw;
|
|
134
|
+
if (typeof data.sessionId !== "string" || data.sessionId.length === 0)
|
|
135
|
+
return null;
|
|
136
|
+
if (data.sessionId.length > MAX_SESSION_KEY_CHARS)
|
|
137
|
+
return null;
|
|
138
|
+
if (!Array.isArray(data.messages))
|
|
139
|
+
return null;
|
|
140
|
+
const maxContentChars = Math.min(captureMaxChars, MAX_CONTENT_CHARS);
|
|
141
|
+
let lastMessageMs = 0;
|
|
142
|
+
const filtered = [];
|
|
143
|
+
for (const [index, entry] of data.messages.entries()) {
|
|
144
|
+
if (index > MAX_MESSAGE_INDEX)
|
|
145
|
+
break;
|
|
146
|
+
if (typeof entry !== "object" || entry === null)
|
|
147
|
+
continue;
|
|
148
|
+
const msg = entry;
|
|
149
|
+
if (msg.role !== "user" && msg.role !== "assistant")
|
|
150
|
+
continue;
|
|
151
|
+
if (typeof msg.content !== "string" || msg.content.trim().length === 0)
|
|
152
|
+
continue;
|
|
153
|
+
if (typeof msg.timestamp !== "number")
|
|
154
|
+
continue;
|
|
155
|
+
lastMessageMs = Math.max(lastMessageMs, msg.timestamp);
|
|
156
|
+
if (msg.timestamp >= cutoffMs)
|
|
157
|
+
continue;
|
|
158
|
+
filtered.push({
|
|
159
|
+
role: msg.role,
|
|
160
|
+
content: msg.content.length > maxContentChars ? msg.content.slice(0, maxContentChars) : msg.content,
|
|
161
|
+
index,
|
|
162
|
+
timestamp: new Date(msg.timestamp).toISOString(),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
sessionId: data.sessionId,
|
|
167
|
+
channel: typeof data.channel === "string" ? data.channel : undefined,
|
|
168
|
+
userId: typeof data.userId === "string" ? data.userId : undefined,
|
|
169
|
+
messages: recentWindow(filtered),
|
|
170
|
+
lastMessageMs,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The most recent contiguous run of messages fitting both ingest caps
|
|
175
|
+
* (message count and serialized bytes). Walks backwards from the newest
|
|
176
|
+
* message and stops at the first one that would overflow the byte budget.
|
|
177
|
+
*/
|
|
178
|
+
function recentWindow(messages) {
|
|
179
|
+
const window = [];
|
|
180
|
+
let bytes = 0;
|
|
181
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
182
|
+
if (window.length >= MAX_MESSAGES_PER_SESSION)
|
|
183
|
+
break;
|
|
184
|
+
const msgBytes = Buffer.byteLength(JSON.stringify(messages[i])) + 1;
|
|
185
|
+
if (bytes + msgBytes > MAX_BATCH_BYTES)
|
|
186
|
+
break;
|
|
187
|
+
window.unshift(messages[i]);
|
|
188
|
+
bytes += msgBytes;
|
|
189
|
+
}
|
|
190
|
+
return window;
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=session-backfill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-backfill.js","sourceRoot":"","sources":["../src/session-backfill.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,EAAE,CAAC;IACJ,QAAQ,EAAE;QACR,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;CACL;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC/E,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QACjC,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAEhC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAElC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE5I,iBAAiB,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IAE7G,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAC3C,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KAC7H,CAAC,CAAC;IAEH,cAAc,IAAI,OAAO,CAAC;QACxB,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,EAAE,CAAA;SAAE,EAAE,CAAC;QACrE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,CAAC,CAAC;IAEH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAE9D,WAAW,IAAI,OAAO,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;CACJ"}
|
package/openclaw.plugin.json
CHANGED
|
@@ -43,6 +43,18 @@
|
|
|
43
43
|
"minimum": 10,
|
|
44
44
|
"maximum": 600,
|
|
45
45
|
"description": "Flush memories after this many seconds of no messages"
|
|
46
|
+
},
|
|
47
|
+
"backfillSessions": {
|
|
48
|
+
"type": "boolean",
|
|
49
|
+
"default": true,
|
|
50
|
+
"description": "On first install, ingest the agent's existing chat sessions into memory (one-time, duplicate-safe)"
|
|
51
|
+
},
|
|
52
|
+
"backfillMaxSessions": {
|
|
53
|
+
"type": "number",
|
|
54
|
+
"default": 50,
|
|
55
|
+
"minimum": 1,
|
|
56
|
+
"maximum": 1000,
|
|
57
|
+
"description": "Maximum number of most-recent chat sessions to backfill"
|
|
46
58
|
}
|
|
47
59
|
}
|
|
48
60
|
},
|
|
@@ -58,6 +70,14 @@
|
|
|
58
70
|
"idleFlushSeconds": {
|
|
59
71
|
"label": "Idle flush interval",
|
|
60
72
|
"description": "Seconds of inactivity before flushing pending memories"
|
|
73
|
+
},
|
|
74
|
+
"backfillSessions": {
|
|
75
|
+
"label": "Backfill chat history",
|
|
76
|
+
"description": "Ingest existing chat sessions into memory on first install"
|
|
77
|
+
},
|
|
78
|
+
"backfillMaxSessions": {
|
|
79
|
+
"label": "Backfill session cap",
|
|
80
|
+
"description": "Maximum number of most-recent chat sessions to backfill"
|
|
61
81
|
}
|
|
62
82
|
}
|
|
63
83
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-memory-cloud",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"description": "Cloud memory extension for OpenClaw — Turbopuffer vectors + DynamoDB knowledge graph",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@alfe.ai/agent-api-client": "0.
|
|
14
|
+
"@alfe.ai/agent-api-client": "0.6.0",
|
|
15
15
|
"@alfe.ai/config": "0.3.0"
|
|
16
16
|
},
|
|
17
17
|
"license": "UNLICENSED",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc",
|
|
30
|
+
"test": "vitest run --passWithNoTests",
|
|
31
|
+
"test:watch": "vitest",
|
|
30
32
|
"typecheck": "tsc --noEmit",
|
|
31
33
|
"lint": "eslint ."
|
|
32
34
|
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtemp, writeFile, rm, mkdir, chmod } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { runSessionsBackfill } from "../session-backfill.js";
|
|
6
|
+
import type { MemoryCloudConfig, MemoryMessage, SessionMetadata } from "../types.js";
|
|
7
|
+
|
|
8
|
+
const CONFIG: MemoryCloudConfig = {
|
|
9
|
+
autoCapture: true,
|
|
10
|
+
autoRecall: true,
|
|
11
|
+
captureMaxChars: 500,
|
|
12
|
+
idleFlushSeconds: 60,
|
|
13
|
+
backfillSessions: true,
|
|
14
|
+
backfillMaxSessions: 50,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const logger = { info: vi.fn(), debug: vi.fn(), warn: vi.fn() };
|
|
18
|
+
|
|
19
|
+
function makeClient() {
|
|
20
|
+
return {
|
|
21
|
+
memoryIngest: vi.fn(async (sessionKey: string, messages: MemoryMessage[], metadata?: SessionMetadata) => {
|
|
22
|
+
void sessionKey;
|
|
23
|
+
void metadata;
|
|
24
|
+
return { queued: true, messageCount: messages.length };
|
|
25
|
+
}),
|
|
26
|
+
memoryBootstrapStatusMark: vi.fn(async (scope?: "files" | "sessions") => {
|
|
27
|
+
void scope;
|
|
28
|
+
return { synced: true as const, syncedAt: new Date().toISOString() };
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sessionFile(sessionId: string, messages: { role: string; content: string; timestamp: number }[], extra?: Record<string, unknown>) {
|
|
34
|
+
return JSON.stringify({
|
|
35
|
+
sessionId,
|
|
36
|
+
agentId: "",
|
|
37
|
+
channel: "alfe",
|
|
38
|
+
createdAt: new Date(0).toISOString(),
|
|
39
|
+
updatedAt: new Date().toISOString(),
|
|
40
|
+
messages,
|
|
41
|
+
...extra,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("runSessionsBackfill", () => {
|
|
46
|
+
let dir: string;
|
|
47
|
+
|
|
48
|
+
beforeEach(async () => {
|
|
49
|
+
dir = await mkdtemp(join(tmpdir(), "memory-backfill-"));
|
|
50
|
+
vi.clearAllMocks();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(async () => {
|
|
54
|
+
await rm(dir, { recursive: true, force: true });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("marks complete immediately when the sessions directory is missing", async () => {
|
|
58
|
+
const client = makeClient();
|
|
59
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: join(dir, "nope") });
|
|
60
|
+
expect(client.memoryIngest).not.toHaveBeenCalled();
|
|
61
|
+
expect(client.memoryBootstrapStatusMark).toHaveBeenCalledWith("sessions");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("marks complete when no eligible sessions exist", async () => {
|
|
65
|
+
const client = makeClient();
|
|
66
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
67
|
+
expect(client.memoryIngest).not.toHaveBeenCalled();
|
|
68
|
+
expect(client.memoryBootstrapStatusMark).toHaveBeenCalledWith("sessions");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("skips corrupt and shape-mismatched files but ingests valid ones", async () => {
|
|
72
|
+
await writeFile(join(dir, "corrupt.json"), "{not json");
|
|
73
|
+
await writeFile(join(dir, "no-messages.json"), JSON.stringify({ sessionId: "x" }));
|
|
74
|
+
await writeFile(join(dir, "good.json"), sessionFile("alfe:dm:user1", [
|
|
75
|
+
{ role: "user", content: "hello", timestamp: 1000 },
|
|
76
|
+
{ role: "assistant", content: "hi there", timestamp: 2000 },
|
|
77
|
+
]));
|
|
78
|
+
|
|
79
|
+
const client = makeClient();
|
|
80
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
81
|
+
|
|
82
|
+
expect(client.memoryIngest).toHaveBeenCalledTimes(1);
|
|
83
|
+
const [sessionKey, messages] = client.memoryIngest.mock.calls[0];
|
|
84
|
+
expect(sessionKey).toBe("alfe:dm:user1");
|
|
85
|
+
expect(messages).toHaveLength(2);
|
|
86
|
+
expect(client.memoryBootstrapStatusMark).toHaveBeenCalledWith("sessions");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("excludes messages at or after the cutoff (retrofit dedup)", async () => {
|
|
90
|
+
await writeFile(join(dir, "s.json"), sessionFile("alfe:dm:u", [
|
|
91
|
+
{ role: "user", content: "before install", timestamp: 1000 },
|
|
92
|
+
{ role: "user", content: "after install", timestamp: 5000 },
|
|
93
|
+
]));
|
|
94
|
+
|
|
95
|
+
const client = makeClient();
|
|
96
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: 5000, sessionsDir: dir });
|
|
97
|
+
|
|
98
|
+
const [, messages] = client.memoryIngest.mock.calls[0];
|
|
99
|
+
expect(messages).toHaveLength(1);
|
|
100
|
+
expect((messages as MemoryMessage[])[0].content).toBe("before install");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("preserves file-array positions as indexes and skips invalid entries without renumbering", async () => {
|
|
104
|
+
await writeFile(join(dir, "s.json"), sessionFile("alfe:dm:u", [
|
|
105
|
+
{ role: "user", content: "first", timestamp: 1000 },
|
|
106
|
+
{ role: "system", content: "not a chat role", timestamp: 1500 },
|
|
107
|
+
{ role: "assistant", content: "second", timestamp: 2000 },
|
|
108
|
+
]));
|
|
109
|
+
|
|
110
|
+
const client = makeClient();
|
|
111
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
112
|
+
|
|
113
|
+
const [, messages] = client.memoryIngest.mock.calls[0] as [string, MemoryMessage[]];
|
|
114
|
+
expect(messages.map((m) => m.index)).toEqual([0, 2]);
|
|
115
|
+
expect(messages[0].timestamp).toBe(new Date(1000).toISOString());
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("truncates message content to captureMaxChars", async () => {
|
|
119
|
+
await writeFile(join(dir, "s.json"), sessionFile("alfe:dm:u", [
|
|
120
|
+
{ role: "user", content: "x".repeat(2000), timestamp: 1000 },
|
|
121
|
+
]));
|
|
122
|
+
|
|
123
|
+
const client = makeClient();
|
|
124
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
125
|
+
|
|
126
|
+
const [, messages] = client.memoryIngest.mock.calls[0] as [string, MemoryMessage[]];
|
|
127
|
+
expect(messages[0].content).toHaveLength(500);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("sends each session as exactly one ingest call (no cross-chunk race)", async () => {
|
|
131
|
+
const messages = Array.from({ length: 101 }, (_, i) => ({
|
|
132
|
+
role: "user",
|
|
133
|
+
content: `msg ${String(i)}`,
|
|
134
|
+
timestamp: 1000 + i,
|
|
135
|
+
}));
|
|
136
|
+
await writeFile(join(dir, "s.json"), sessionFile("alfe:dm:u", messages));
|
|
137
|
+
|
|
138
|
+
const client = makeClient();
|
|
139
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
140
|
+
|
|
141
|
+
expect(client.memoryIngest).toHaveBeenCalledTimes(1);
|
|
142
|
+
expect(client.memoryIngest.mock.calls[0][1]).toHaveLength(101);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("keeps only the most recent 500 messages of a session, preserving original indexes", async () => {
|
|
146
|
+
const messages = Array.from({ length: 600 }, (_, i) => ({
|
|
147
|
+
role: "user",
|
|
148
|
+
content: `msg ${String(i)}`,
|
|
149
|
+
timestamp: 1000 + i,
|
|
150
|
+
}));
|
|
151
|
+
await writeFile(join(dir, "s.json"), sessionFile("alfe:dm:u", messages));
|
|
152
|
+
|
|
153
|
+
const client = makeClient();
|
|
154
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
155
|
+
|
|
156
|
+
expect(client.memoryIngest).toHaveBeenCalledTimes(1);
|
|
157
|
+
const sent = client.memoryIngest.mock.calls[0][1] as MemoryMessage[];
|
|
158
|
+
expect(sent).toHaveLength(500);
|
|
159
|
+
expect(sent[0].index).toBe(100);
|
|
160
|
+
expect(sent[sent.length - 1].index).toBe(599);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("shrinks the window to the most recent messages when the byte budget would overflow", async () => {
|
|
164
|
+
// ~40KB of content per message → only 4 fit in the 180KB budget.
|
|
165
|
+
const messages = Array.from({ length: 10 }, (_, i) => ({
|
|
166
|
+
role: "user",
|
|
167
|
+
content: "x".repeat(40_000),
|
|
168
|
+
timestamp: 1000 + i,
|
|
169
|
+
}));
|
|
170
|
+
await writeFile(join(dir, "s.json"), sessionFile("alfe:dm:u", messages));
|
|
171
|
+
|
|
172
|
+
const client = makeClient();
|
|
173
|
+
await runSessionsBackfill(client, { ...CONFIG, captureMaxChars: 50_000 }, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
174
|
+
|
|
175
|
+
expect(client.memoryIngest).toHaveBeenCalledTimes(1);
|
|
176
|
+
const sent = client.memoryIngest.mock.calls[0][1] as MemoryMessage[];
|
|
177
|
+
expect(sent).toHaveLength(4);
|
|
178
|
+
expect(sent[sent.length - 1].index).toBe(9);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("skips sessions whose sessionId exceeds the server's 256-char cap", async () => {
|
|
182
|
+
await writeFile(join(dir, "long.json"), sessionFile("x".repeat(300), [
|
|
183
|
+
{ role: "user", content: "hello", timestamp: 1000 },
|
|
184
|
+
]));
|
|
185
|
+
|
|
186
|
+
const client = makeClient();
|
|
187
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
188
|
+
|
|
189
|
+
expect(client.memoryIngest).not.toHaveBeenCalled();
|
|
190
|
+
expect(client.memoryBootstrapStatusMark).toHaveBeenCalledWith("sessions");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("does not let post-cutoff sessions starve pre-cutoff sessions out of the cap (retrofit)", async () => {
|
|
194
|
+
// Newest session is entirely post-install — emptied by the cutoff. It
|
|
195
|
+
// must not occupy the single cap slot.
|
|
196
|
+
await writeFile(join(dir, "post-install.json"), sessionFile("alfe:dm:recent", [
|
|
197
|
+
{ role: "user", content: "captured live already", timestamp: 9000 },
|
|
198
|
+
]));
|
|
199
|
+
await writeFile(join(dir, "pre-install.json"), sessionFile("alfe:dm:old", [
|
|
200
|
+
{ role: "user", content: "history worth keeping", timestamp: 1000 },
|
|
201
|
+
]));
|
|
202
|
+
|
|
203
|
+
const client = makeClient();
|
|
204
|
+
await runSessionsBackfill(client, { ...CONFIG, backfillMaxSessions: 1 }, logger, { cutoffMs: 5000, sessionsDir: dir });
|
|
205
|
+
|
|
206
|
+
expect(client.memoryIngest).toHaveBeenCalledTimes(1);
|
|
207
|
+
expect(client.memoryIngest.mock.calls[0][0]).toBe("alfe:dm:old");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("caps at backfillMaxSessions, keeping the most recent sessions", async () => {
|
|
211
|
+
await writeFile(join(dir, "old.json"), sessionFile("alfe:dm:old", [
|
|
212
|
+
{ role: "user", content: "old", timestamp: 1000 },
|
|
213
|
+
]));
|
|
214
|
+
await writeFile(join(dir, "new.json"), sessionFile("alfe:dm:new", [
|
|
215
|
+
{ role: "user", content: "new", timestamp: 9000 },
|
|
216
|
+
]));
|
|
217
|
+
|
|
218
|
+
const client = makeClient();
|
|
219
|
+
await runSessionsBackfill(client, { ...CONFIG, backfillMaxSessions: 1 }, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
220
|
+
|
|
221
|
+
expect(client.memoryIngest).toHaveBeenCalledTimes(1);
|
|
222
|
+
expect(client.memoryIngest.mock.calls[0][0]).toBe("alfe:dm:new");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("aborts without marking when an ingest call fails", async () => {
|
|
226
|
+
await writeFile(join(dir, "s.json"), sessionFile("alfe:dm:u", [
|
|
227
|
+
{ role: "user", content: "hello", timestamp: 1000 },
|
|
228
|
+
]));
|
|
229
|
+
|
|
230
|
+
const client = makeClient();
|
|
231
|
+
client.memoryIngest.mockRejectedValueOnce(new Error("boom"));
|
|
232
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
233
|
+
|
|
234
|
+
expect(client.memoryBootstrapStatusMark).not.toHaveBeenCalled();
|
|
235
|
+
expect(logger.warn).toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("passes session channel and userId as ingest metadata", async () => {
|
|
239
|
+
await writeFile(join(dir, "s.json"), sessionFile("alfe:dm:u", [
|
|
240
|
+
{ role: "user", content: "hello", timestamp: 1000 },
|
|
241
|
+
], { channel: "alfe", userId: "user_123" }));
|
|
242
|
+
|
|
243
|
+
const client = makeClient();
|
|
244
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
245
|
+
|
|
246
|
+
const metadata = client.memoryIngest.mock.calls[0][2];
|
|
247
|
+
expect(metadata).toEqual({ channelId: "alfe", userId: "user_123" });
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("does not warn or fail when the mark call itself fails (retries next startup)", async () => {
|
|
251
|
+
await writeFile(join(dir, "s.json"), sessionFile("alfe:dm:u", [
|
|
252
|
+
{ role: "user", content: "hello", timestamp: 1000 },
|
|
253
|
+
]));
|
|
254
|
+
|
|
255
|
+
const client = makeClient();
|
|
256
|
+
client.memoryBootstrapStatusMark.mockRejectedValueOnce(new Error("mark failed"));
|
|
257
|
+
await expect(
|
|
258
|
+
runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir }),
|
|
259
|
+
).resolves.toBeUndefined();
|
|
260
|
+
expect(logger.warn).toHaveBeenCalledWith(
|
|
261
|
+
"sessions backfill: mark failed; will retry on next startup",
|
|
262
|
+
expect.anything(),
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("does not mark complete when the directory read fails for a non-ENOENT reason", async () => {
|
|
267
|
+
const locked = join(dir, "locked");
|
|
268
|
+
await mkdir(locked);
|
|
269
|
+
await chmod(locked, 0o000);
|
|
270
|
+
try {
|
|
271
|
+
const client = makeClient();
|
|
272
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: locked });
|
|
273
|
+
expect(client.memoryIngest).not.toHaveBeenCalled();
|
|
274
|
+
expect(client.memoryBootstrapStatusMark).not.toHaveBeenCalled();
|
|
275
|
+
expect(logger.warn).toHaveBeenCalled();
|
|
276
|
+
} finally {
|
|
277
|
+
await chmod(locked, 0o755);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("ignores non-json files and nested directories", async () => {
|
|
282
|
+
await writeFile(join(dir, "notes.txt"), "not a session");
|
|
283
|
+
await mkdir(join(dir, "sub.json.d"));
|
|
284
|
+
const client = makeClient();
|
|
285
|
+
await runSessionsBackfill(client, CONFIG, logger, { cutoffMs: Date.now(), sessionsDir: dir });
|
|
286
|
+
expect(client.memoryIngest).not.toHaveBeenCalled();
|
|
287
|
+
expect(client.memoryBootstrapStatusMark).toHaveBeenCalledWith("sessions");
|
|
288
|
+
});
|
|
289
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
|
19
19
|
import { AutoCapture } from "./auto-capture.js";
|
|
20
20
|
import { AutoRecall } from "./auto-recall.js";
|
|
21
21
|
import { formatSearchResults } from "./formatter.js";
|
|
22
|
+
import { runSessionsBackfill } from "./session-backfill.js";
|
|
22
23
|
import type { MemoryCloudConfig } from "./types.js";
|
|
23
24
|
import { createRequire } from 'node:module';
|
|
24
25
|
const require = createRequire(import.meta.url);
|
|
@@ -63,6 +64,8 @@ const DEFAULT_CONFIG: MemoryCloudConfig = {
|
|
|
63
64
|
autoRecall: true,
|
|
64
65
|
captureMaxChars: 500,
|
|
65
66
|
idleFlushSeconds: 60,
|
|
67
|
+
backfillSessions: true,
|
|
68
|
+
backfillMaxSessions: 50,
|
|
66
69
|
};
|
|
67
70
|
|
|
68
71
|
function resolvePluginConfig(pluginConfig?: Record<string, unknown>): MemoryCloudConfig {
|
|
@@ -71,6 +74,11 @@ function resolvePluginConfig(pluginConfig?: Record<string, unknown>): MemoryClou
|
|
|
71
74
|
autoRecall: typeof pluginConfig?.autoRecall === "boolean" ? pluginConfig.autoRecall : DEFAULT_CONFIG.autoRecall,
|
|
72
75
|
captureMaxChars: typeof pluginConfig?.captureMaxChars === "number" ? pluginConfig.captureMaxChars : DEFAULT_CONFIG.captureMaxChars,
|
|
73
76
|
idleFlushSeconds: typeof pluginConfig?.idleFlushSeconds === "number" ? pluginConfig.idleFlushSeconds : DEFAULT_CONFIG.idleFlushSeconds,
|
|
77
|
+
backfillSessions: typeof pluginConfig?.backfillSessions === "boolean" ? pluginConfig.backfillSessions : DEFAULT_CONFIG.backfillSessions,
|
|
78
|
+
backfillMaxSessions:
|
|
79
|
+
typeof pluginConfig?.backfillMaxSessions === "number" && Number.isFinite(pluginConfig.backfillMaxSessions) && pluginConfig.backfillMaxSessions > 0
|
|
80
|
+
? Math.floor(pluginConfig.backfillMaxSessions)
|
|
81
|
+
: DEFAULT_CONFIG.backfillMaxSessions,
|
|
74
82
|
};
|
|
75
83
|
}
|
|
76
84
|
|
|
@@ -341,33 +349,60 @@ export default {
|
|
|
341
349
|
}
|
|
342
350
|
});
|
|
343
351
|
|
|
344
|
-
// Fire-and-forget first-run sync:
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
void
|
|
348
|
-
logger.warn("memory
|
|
352
|
+
// Fire-and-forget first-run sync: workspace files (MEMORY.md / memory/*.md)
|
|
353
|
+
// and pre-existing chat sessions, each idempotent via its own
|
|
354
|
+
// PalaceMetadata flag (bootstrapSyncedAt / sessionsBackfillSyncedAt).
|
|
355
|
+
void runStartupSync(client, config, logger).catch((err: unknown) => {
|
|
356
|
+
logger.warn("memory startup sync failed; will retry on next startup", { err: String(err) });
|
|
349
357
|
});
|
|
350
358
|
|
|
351
359
|
logger.info("memory-cloud extension registered", { autoCapture: config.autoCapture, autoRecall: config.autoRecall });
|
|
352
360
|
},
|
|
353
361
|
};
|
|
354
362
|
|
|
355
|
-
async function
|
|
363
|
+
async function runStartupSync(
|
|
356
364
|
client: AgentApiClient,
|
|
365
|
+
config: MemoryCloudConfig,
|
|
357
366
|
logger: PluginApi["logger"],
|
|
358
367
|
): Promise<void> {
|
|
359
|
-
|
|
368
|
+
// Fetch status ONCE, before the file bootstrap runs: the sessions backfill
|
|
369
|
+
// computes its dedup cutoff from the pre-bootstrap `syncedAt` (an agent
|
|
370
|
+
// that already had memory installed captured everything after that moment
|
|
371
|
+
// live, so backfilling past it would duplicate memories).
|
|
372
|
+
let status: { synced: boolean; syncedAt?: string; sessionsBackfillSynced?: boolean };
|
|
360
373
|
try {
|
|
361
374
|
status = await client.memoryBootstrapStatus();
|
|
362
375
|
} catch (err) {
|
|
363
376
|
logger.debug("memory bootstrap status check failed; skipping", { err: String(err) });
|
|
364
377
|
return;
|
|
365
378
|
}
|
|
379
|
+
const startupMs = Date.now();
|
|
380
|
+
|
|
366
381
|
if (status.synced) {
|
|
367
382
|
logger.debug("memory bootstrap: already synced, skipping");
|
|
368
|
-
|
|
383
|
+
} else {
|
|
384
|
+
try {
|
|
385
|
+
await runBootstrap(client, logger);
|
|
386
|
+
} catch (err) {
|
|
387
|
+
logger.warn("memory bootstrap failed; will retry on next startup", { err: String(err) });
|
|
388
|
+
}
|
|
369
389
|
}
|
|
370
390
|
|
|
391
|
+
// Strict `=== false` on purpose: an old server (deployed before the
|
|
392
|
+
// sessions-backfill release) omits the field entirely, and its mark
|
|
393
|
+
// endpoint would clobber `bootstrapSyncedAt` — corrupting the cutoff
|
|
394
|
+
// anchor. Defer until the service is deployed and reports the flag.
|
|
395
|
+
if (config.backfillSessions && status.sessionsBackfillSynced === false) {
|
|
396
|
+
const installedAtMs = status.syncedAt ? Date.parse(status.syncedAt) : Number.POSITIVE_INFINITY;
|
|
397
|
+
const cutoffMs = Math.min(startupMs, Number.isNaN(installedAtMs) ? Number.POSITIVE_INFINITY : installedAtMs);
|
|
398
|
+
await runSessionsBackfill(client, config, logger, { cutoffMs });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function runBootstrap(
|
|
403
|
+
client: AgentApiClient,
|
|
404
|
+
logger: PluginApi["logger"],
|
|
405
|
+
): Promise<void> {
|
|
371
406
|
const cwd = process.cwd();
|
|
372
407
|
const filesToSync: string[] = [];
|
|
373
408
|
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session backfill — one-time ingestion of pre-existing chat history into
|
|
3
|
+
* cloud memory, run fire-and-forget at plugin startup (see index.ts).
|
|
4
|
+
*
|
|
5
|
+
* Reads the openclaw-chat plugin's on-disk session files directly:
|
|
6
|
+
* ~/.alfe/sessions/chat/{sessionId}.json
|
|
7
|
+
* The write side is packages/openclaw-chat/src/session-store.ts — that path
|
|
8
|
+
* and file shape are a read contract; keep the two in sync. The chat plugin
|
|
9
|
+
* is not a package dependency because its session-store functions aren't
|
|
10
|
+
* part of its public entry and the plugin isn't guaranteed to be installed
|
|
11
|
+
* alongside memory. Like the session store itself, this assumes one agent
|
|
12
|
+
* per machine (`alfe remove` deletes ~/.alfe, so files never outlive the
|
|
13
|
+
* agent they belong to).
|
|
14
|
+
*
|
|
15
|
+
* No-re-sync guarantees (in concert with services/memory):
|
|
16
|
+
* 1. Runs only while the server-side `sessionsBackfillSyncedAt` flag is
|
|
17
|
+
* unset; the flag survives integration uninstall/reinstall.
|
|
18
|
+
* 2. Only messages with timestamp < cutoffMs are ingested — for agents that
|
|
19
|
+
* already had memory installed, everything after `bootstrapSyncedAt` was
|
|
20
|
+
* already captured live by AutoCapture.
|
|
21
|
+
* 3. sessionKey = file sessionId and index = file array position, so the
|
|
22
|
+
* ingest consumer's per-session `lastProcessedIndex` high-water mark makes
|
|
23
|
+
* a crash-and-retry replay free (filtered server-side before any cost).
|
|
24
|
+
*
|
|
25
|
+
* Each session is sent as exactly ONE memoryIngest call (one SQS message,
|
|
26
|
+
* windowed to the most recent messages that fit the caps below). Splitting a
|
|
27
|
+
* session across multiple SQS messages would race the shared per-session
|
|
28
|
+
* high-water mark on the standard (non-FIFO) ingest queue: a transiently
|
|
29
|
+
* failed early chunk redelivered after a later chunk succeeds gets filtered
|
|
30
|
+
* out and silently lost.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
34
|
+
import { join } from "node:path";
|
|
35
|
+
import { homedir } from "node:os";
|
|
36
|
+
import type { MemoryCloudConfig, MemoryMessage, SessionMetadata } from "./types.js";
|
|
37
|
+
|
|
38
|
+
const DEFAULT_SESSIONS_DIR = join(homedir(), ".alfe", "sessions", "chat");
|
|
39
|
+
|
|
40
|
+
// Server-side zod caps on /agent/memory/ingest (services/memory
|
|
41
|
+
// api-agents/ingest/post.ts) — exceeding any of these is a deterministic 400,
|
|
42
|
+
// which would put the startup retry loop into a permanent failure.
|
|
43
|
+
const MAX_MESSAGES_PER_SESSION = 500;
|
|
44
|
+
const MAX_SESSION_KEY_CHARS = 256;
|
|
45
|
+
const MAX_MESSAGE_INDEX = 1_000_000;
|
|
46
|
+
const MAX_CONTENT_CHARS = 100_000;
|
|
47
|
+
/** One ingest call becomes one SQS SendMessage (256KB hard limit). */
|
|
48
|
+
const MAX_BATCH_BYTES = 180_000;
|
|
49
|
+
|
|
50
|
+
interface BackfillApi {
|
|
51
|
+
memoryIngest(sessionKey: string, messages: MemoryMessage[], metadata?: SessionMetadata): Promise<{ queued: boolean; messageCount: number }>;
|
|
52
|
+
memoryBootstrapStatusMark(scope?: "files" | "sessions"): Promise<{ synced: true; syncedAt: string }>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface BackfillLogger {
|
|
56
|
+
info: (msg: string, ctx?: Record<string, unknown>) => void;
|
|
57
|
+
debug: (msg: string, ctx?: Record<string, unknown>) => void;
|
|
58
|
+
warn: (msg: string, ctx?: Record<string, unknown>) => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface ParsedSession {
|
|
62
|
+
sessionId: string;
|
|
63
|
+
channel?: string;
|
|
64
|
+
userId?: string;
|
|
65
|
+
/** Recent-window messages carrying their original file-array position as `index`. */
|
|
66
|
+
messages: MemoryMessage[];
|
|
67
|
+
lastMessageMs: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SessionsBackfillOptions {
|
|
71
|
+
cutoffMs: number;
|
|
72
|
+
sessionsDir?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function runSessionsBackfill(
|
|
76
|
+
client: BackfillApi,
|
|
77
|
+
config: MemoryCloudConfig,
|
|
78
|
+
logger: BackfillLogger,
|
|
79
|
+
opts: SessionsBackfillOptions,
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
const sessionsDir = opts.sessionsDir ?? DEFAULT_SESSIONS_DIR;
|
|
82
|
+
|
|
83
|
+
let fileNames: string[];
|
|
84
|
+
try {
|
|
85
|
+
fileNames = (await readdir(sessionsDir)).filter((name) => name.endsWith(".json"));
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
88
|
+
// No sessions directory — nothing to backfill, ever.
|
|
89
|
+
logger.debug("sessions backfill: no sessions directory, marking complete");
|
|
90
|
+
try { await client.memoryBootstrapStatusMark("sessions"); } catch { /* retry next start */ }
|
|
91
|
+
} else {
|
|
92
|
+
// Transient (EACCES/EMFILE/...) — don't forfeit the backfill.
|
|
93
|
+
logger.warn("sessions backfill: sessions directory unreadable; will retry on next startup", { err: String(err) });
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const sessions: ParsedSession[] = [];
|
|
99
|
+
for (const name of fileNames) {
|
|
100
|
+
const parsed = await parseSessionFile(join(sessionsDir, name), config.captureMaxChars, opts.cutoffMs);
|
|
101
|
+
if (parsed) sessions.push(parsed);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Most recent non-empty sessions first, capped. Filter BEFORE capping:
|
|
105
|
+
// on a retrofit agent the newest sessions are often entirely post-install
|
|
106
|
+
// (emptied by the cutoff) and must not occupy the cap slots.
|
|
107
|
+
const eligible = sessions
|
|
108
|
+
.filter((s) => s.messages.length > 0)
|
|
109
|
+
.sort((a, b) => b.lastMessageMs - a.lastMessageMs)
|
|
110
|
+
.slice(0, config.backfillMaxSessions);
|
|
111
|
+
|
|
112
|
+
if (eligible.length === 0) {
|
|
113
|
+
logger.debug("sessions backfill: no eligible sessions, marking complete");
|
|
114
|
+
try { await client.memoryBootstrapStatusMark("sessions"); } catch { /* retry next start */ }
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
logger.info("sessions backfill: ingesting sessions", {
|
|
119
|
+
sessionCount: eligible.length,
|
|
120
|
+
messageCount: eligible.reduce((n, s) => n + s.messages.length, 0),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
for (const session of eligible) {
|
|
124
|
+
const metadata: SessionMetadata = {
|
|
125
|
+
...(session.channel ? { channelId: session.channel } : {}),
|
|
126
|
+
...(session.userId ? { userId: session.userId } : {}),
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
await client.memoryIngest(session.sessionId, session.messages, metadata);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
// Abort without marking — next startup replays, and the server-side
|
|
132
|
+
// lastProcessedIndex filter discards already-processed sessions free.
|
|
133
|
+
logger.warn("sessions backfill: ingest failed; will retry on next startup", {
|
|
134
|
+
sessionId: session.sessionId,
|
|
135
|
+
err: String(err),
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
await client.memoryBootstrapStatusMark("sessions");
|
|
143
|
+
logger.info("sessions backfill: complete", { sessionCount: eligible.length });
|
|
144
|
+
} catch (err) {
|
|
145
|
+
logger.warn("sessions backfill: mark failed; will retry on next startup", { err: String(err) });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Tolerant parse of a single session file. Returns null for corrupt or
|
|
151
|
+
* shape-mismatched files (skipped, never fatal). Message `index` is the
|
|
152
|
+
* original array position in the file — stable because the store is
|
|
153
|
+
* append-only — so retries resume idempotently server-side.
|
|
154
|
+
*/
|
|
155
|
+
async function parseSessionFile(
|
|
156
|
+
filePath: string,
|
|
157
|
+
captureMaxChars: number,
|
|
158
|
+
cutoffMs: number,
|
|
159
|
+
): Promise<ParsedSession | null> {
|
|
160
|
+
let raw: unknown;
|
|
161
|
+
try {
|
|
162
|
+
raw = JSON.parse(await readFile(filePath, "utf8"));
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
167
|
+
|
|
168
|
+
const data = raw as Record<string, unknown>;
|
|
169
|
+
if (typeof data.sessionId !== "string" || data.sessionId.length === 0) return null;
|
|
170
|
+
if (data.sessionId.length > MAX_SESSION_KEY_CHARS) return null;
|
|
171
|
+
if (!Array.isArray(data.messages)) return null;
|
|
172
|
+
|
|
173
|
+
const maxContentChars = Math.min(captureMaxChars, MAX_CONTENT_CHARS);
|
|
174
|
+
let lastMessageMs = 0;
|
|
175
|
+
const filtered: MemoryMessage[] = [];
|
|
176
|
+
for (const [index, entry] of data.messages.entries()) {
|
|
177
|
+
if (index > MAX_MESSAGE_INDEX) break;
|
|
178
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
179
|
+
const msg = entry as Record<string, unknown>;
|
|
180
|
+
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
181
|
+
if (typeof msg.content !== "string" || msg.content.trim().length === 0) continue;
|
|
182
|
+
if (typeof msg.timestamp !== "number") continue;
|
|
183
|
+
|
|
184
|
+
lastMessageMs = Math.max(lastMessageMs, msg.timestamp);
|
|
185
|
+
if (msg.timestamp >= cutoffMs) continue;
|
|
186
|
+
|
|
187
|
+
filtered.push({
|
|
188
|
+
role: msg.role,
|
|
189
|
+
content: msg.content.length > maxContentChars ? msg.content.slice(0, maxContentChars) : msg.content,
|
|
190
|
+
index,
|
|
191
|
+
timestamp: new Date(msg.timestamp).toISOString(),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
sessionId: data.sessionId,
|
|
197
|
+
channel: typeof data.channel === "string" ? data.channel : undefined,
|
|
198
|
+
userId: typeof data.userId === "string" ? data.userId : undefined,
|
|
199
|
+
messages: recentWindow(filtered),
|
|
200
|
+
lastMessageMs,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The most recent contiguous run of messages fitting both ingest caps
|
|
206
|
+
* (message count and serialized bytes). Walks backwards from the newest
|
|
207
|
+
* message and stops at the first one that would overflow the byte budget.
|
|
208
|
+
*/
|
|
209
|
+
function recentWindow(messages: MemoryMessage[]): MemoryMessage[] {
|
|
210
|
+
const window: MemoryMessage[] = [];
|
|
211
|
+
let bytes = 0;
|
|
212
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
213
|
+
if (window.length >= MAX_MESSAGES_PER_SESSION) break;
|
|
214
|
+
const msgBytes = Buffer.byteLength(JSON.stringify(messages[i])) + 1;
|
|
215
|
+
if (bytes + msgBytes > MAX_BATCH_BYTES) break;
|
|
216
|
+
window.unshift(messages[i]);
|
|
217
|
+
bytes += msgBytes;
|
|
218
|
+
}
|
|
219
|
+
return window;
|
|
220
|
+
}
|
package/src/types.ts
CHANGED
package/tsconfig.json
CHANGED