openclaw-memory-atmem 2.2.0 → 2.2.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/README.md CHANGED
@@ -5,11 +5,11 @@ This npm package is the host bridge for AtMem. It is not a standalone memory eng
5
5
  Use the Python-owned installer:
6
6
 
7
7
  ```bash
8
- python -m pip install --upgrade atmem==2.2.0
8
+ python -m pip install --upgrade atmem==2.2.1
9
9
  atmem openclaw install
10
10
  ```
11
11
 
12
- The installer pins `openclaw-memory-atmem@2.2.0`, binds the exact `atmem` executable, copies existing OpenClaw memory, configures shadow mode, restarts the gateway and verifies the loaded plugin. Direct npm installation cannot perform or prove those steps.
12
+ The installer pins `openclaw-memory-atmem@2.2.1`, binds the exact `atmem` executable, copies existing OpenClaw memory, configures shadow mode, restarts the gateway and verifies the loaded plugin. Direct npm installation cannot perform or prove those steps.
13
13
 
14
14
  Existing AtMem 2.1 users run `atmem openclaw upgrade` after upgrading the Python
15
15
  package. This preserves the current memory mode and migration, verifies the new
package/dist/index.js CHANGED
@@ -326,6 +326,7 @@ function register(api) {
326
326
  // Per-turn recall state. Semantic admission uses a short-lived SQLite handoff
327
327
  // because OpenClaw may run prompt hooks and agent tools in separate runtimes.
328
328
  const pendingPrompts = new Map();
329
+ const observedTurnInputs = new Map();
329
330
  const inboundAttachments = new Map();
330
331
  const inboundAttachmentGeneration = new Map();
331
332
  let nextAttachmentGeneration = 0;
@@ -432,6 +433,41 @@ function register(api) {
432
433
  ttl_seconds: 600,
433
434
  }, cfg.recall.timeoutMs);
434
435
  };
436
+ const turnInputKey = (ctx) => scopedKey(flightRunId(undefined, ctx), ctx);
437
+ const observeTurnInput = async (prompt, ctx, sourceHook, imagesCount) => {
438
+ if (!prompt.trim())
439
+ return;
440
+ const key = turnInputKey(ctx);
441
+ const promptSha256 = digestText(prompt);
442
+ const existing = observedTurnInputs.get(key);
443
+ if (existing) {
444
+ if (existing.promptSha256 !== promptSha256) {
445
+ api.logger.warn(`${TAG} ${sourceHook} exposed different prompt bytes for an already observed turn; ` +
446
+ "the first authenticated turn input remains authoritative");
447
+ }
448
+ await existing.pending;
449
+ return;
450
+ }
451
+ const pending = (async () => {
452
+ await recordBlackbox("turn.input", undefined, ctx, {
453
+ prompt_sha256: promptSha256,
454
+ prompt_chars: prompt.length,
455
+ images_count: imagesCount,
456
+ });
457
+ try {
458
+ await stageInbound(prompt, ctx);
459
+ }
460
+ catch (error) {
461
+ api.logger.warn(`${TAG} semantic source handoff unavailable; memory writes will fail closed: ${error instanceof Error ? error.message : String(error)}`);
462
+ }
463
+ })();
464
+ observedTurnInputs.set(key, {
465
+ promptSha256,
466
+ observedAt: Date.now(),
467
+ pending,
468
+ });
469
+ await pending;
470
+ };
435
471
  const bindInboundAttachments = async (keys, paths, types) => {
436
472
  const generation = ++nextAttachmentGeneration;
437
473
  for (const key of keys) {
@@ -509,19 +545,7 @@ function register(api) {
509
545
  // OpenClaw documents this as the current prompt before model selection. It
510
546
  // is a typed per-turn input surface, not a rendered transcript or history.
511
547
  api.on("before_model_resolve", async (event, ctx) => {
512
- if (!event.prompt?.trim())
513
- return;
514
- await recordBlackbox("turn.input", undefined, ctx, {
515
- prompt_sha256: digestText(event.prompt),
516
- prompt_chars: event.prompt.length,
517
- images_count: Array.isArray(event.attachments) ? event.attachments.length : 0,
518
- });
519
- try {
520
- await stageInbound(event.prompt, ctx);
521
- }
522
- catch (error) {
523
- api.logger.warn(`${TAG} semantic source handoff unavailable; memory writes will fail closed: ${error instanceof Error ? error.message : String(error)}`);
524
- }
548
+ await observeTurnInput(event.prompt, ctx, "before_model_resolve", Array.isArray(event.attachments) ? event.attachments.length : 0);
525
549
  });
526
550
  api.on("llm_input", async (event, ctx) => {
527
551
  await recordBlackbox("model.input", event.runId, ctx, {
@@ -620,6 +644,10 @@ function register(api) {
620
644
  if (now - value.ts > PROMPT_CACHE_TTL_MS)
621
645
  pendingPrompts.delete(key);
622
646
  }
647
+ for (const [key, value] of observedTurnInputs) {
648
+ if (now - value.observedAt > PROMPT_CACHE_TTL_MS)
649
+ observedTurnInputs.delete(key);
650
+ }
623
651
  for (const [key, value] of inboundAttachments) {
624
652
  if (now - value.ts > PROMPT_CACHE_TTL_MS)
625
653
  inboundAttachments.delete(key);
@@ -655,6 +683,7 @@ function register(api) {
655
683
  const userText = event.prompt;
656
684
  if (!userText)
657
685
  return;
686
+ await observeTurnInput(userText, ctx, "before_prompt_build");
658
687
  const sessionKey = scopedKey(ctx.sessionKey ?? ctx.sessionId ?? "default-session", ctx);
659
688
  const takeoverGuidance = cfg.takeoverActive ? TAKEOVER_GUIDANCE : "";
660
689
  pendingPrompts.set(sessionKey, { text: userText, ts: Date.now() });
@@ -878,6 +907,7 @@ function register(api) {
878
907
  // ---- auto-capture: user turn through the pipeline, assistant as digest -
879
908
  api.on("agent_end", async (event, ctx) => {
880
909
  const sessionKey = scopedKey(ctx.sessionKey ?? ctx.sessionId ?? "default-session", ctx);
910
+ const observedTurnInputKey = turnInputKey(ctx);
881
911
  const cached = pendingPrompts.get(sessionKey);
882
912
  pendingPrompts.delete(sessionKey);
883
913
  const userText = cached?.text?.replace(INJECT_RE, "").trim();
@@ -978,6 +1008,7 @@ function register(api) {
978
1008
  contextEventId: cached?.contextEventId,
979
1009
  contextReceiptId: cached?.contextReceiptId,
980
1010
  });
1011
+ observedTurnInputs.delete(observedTurnInputKey);
981
1012
  }
982
1013
  });
983
1014
  // ---- keep injected blocks out of persisted history ---------------------
@@ -128,7 +128,7 @@ export class AtmemClient {
128
128
  capabilities: {},
129
129
  clientInfo: {
130
130
  name: "openclaw-memory-atmem",
131
- version: "2.2.0",
131
+ version: "2.2.1",
132
132
  },
133
133
  });
134
134
  this.notify("notifications/initialized", {});
@@ -2,7 +2,7 @@
2
2
  "id": "memory-atmem",
3
3
  "name": "Memory (atmem)",
4
4
  "description": "OpenClaw bridge installed and managed by the AtMem memory control plane.",
5
- "version": "2.2.0",
5
+ "version": "2.2.1",
6
6
  "commandAliases": ["memory-atmem"],
7
7
  "activation": {
8
8
  "onStartup": true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-memory-atmem",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "OpenClaw Agent Black Box and memory control-plane bridge for AtMem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",