openclaw-memory-atmem 2.2.6 → 2.2.7-beta.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 +12 -3
- package/dist/index.js +15 -1
- package/dist/src/setup.js +1 -1
- package/dist/src/tool-errors.js +31 -0
- package/dist/src/tool-observations.js +8 -4
- package/dist/src/tool-result-comparison.js +45 -0
- package/openclaw.plugin.json +4 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ 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.
|
|
8
|
+
python -m pip install --upgrade atmem==2.2.7b1
|
|
9
9
|
atmem openclaw install
|
|
10
10
|
```
|
|
11
11
|
|
|
@@ -22,7 +22,7 @@ Existing AtMem 2.1 users run `atmem openclaw upgrade` after upgrading the Python
|
|
|
22
22
|
package. This preserves the current memory mode and migration, verifies the new
|
|
23
23
|
bridge with a self-test flight, and rolls back the bridge on failure.
|
|
24
24
|
|
|
25
|
-
Bridge `2.2.
|
|
25
|
+
Bridge `2.2.7-beta.1` includes exact task-context delivery, the shared
|
|
26
26
|
calibrated retrieval decision, and delegated context correlation. When the host
|
|
27
27
|
supplies `taskId` in hook context, the bridge requests only that governed task,
|
|
28
28
|
checks its byte digest, contributes it separately from recalled memory, and
|
|
@@ -37,7 +37,7 @@ The bridge also supplies Agent Black Box hooks. It records model/tool lifecycle
|
|
|
37
37
|
See the repository [OpenClaw setup](../../docs/openclaw-setup.md) and [control-plane guarantees](../../docs/control-plane.md).
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
### Delegated local CLI identity and tool evidence in 2.2.
|
|
40
|
+
### Delegated local CLI identity and tool evidence in 2.2.7b1
|
|
41
41
|
|
|
42
42
|
The default delegated user mapping requires `senderIsOwner: true`. Setting
|
|
43
43
|
`requireOwner: false` alone no longer bypasses identity checks. A dedicated local
|
|
@@ -54,3 +54,12 @@ actual terminal host tool-event results supply fallback observations when typed
|
|
|
54
54
|
completion hooks are absent. Only matched requests close, with content-free
|
|
55
55
|
bounded caching and duplicate suppression. Fully observed
|
|
56
56
|
terminal tool errors close evidence as `completed_with_tool_errors`, not success.
|
|
57
|
+
|
|
58
|
+
Beta 2.2.7b1: automatic recall now requires calibrated direct
|
|
59
|
+
query support by default (`recall.requireDirectSupport: true`). Persona is an
|
|
60
|
+
explicit opt-in (`persona.enabled: true`); setup defaults it off to avoid adding
|
|
61
|
+
unrelated personal facts to every request. When enabled, persona records are
|
|
62
|
+
excluded from recall so each appears once. Existing explicit persona settings
|
|
63
|
+
are respected; set `persona.enabled: false` to adopt selective recall. Explicit
|
|
64
|
+
memory search remains available. The legacy rank-only mode is available with
|
|
65
|
+
`recall.requireDirectSupport: false`; it can admit weak matches.
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,8 @@ import { createReadStream } from "node:fs";
|
|
|
24
24
|
import { lstat, mkdir, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
|
|
25
25
|
import { spawnSync } from "node:child_process";
|
|
26
26
|
import { ToolObservations, observationContext } from "./src/tool-observations.js";
|
|
27
|
+
import { progressCardComparison } from "./src/tool-result-comparison.js";
|
|
28
|
+
import { toolErrorReason } from "./src/tool-errors.js";
|
|
27
29
|
import { delegatedIdentity, isolatedCliProcess } from "./src/delegated-identity.js";
|
|
28
30
|
import { AtmemClient } from "./src/rpc-client.js";
|
|
29
31
|
import { runSetup } from "./src/setup.js";
|
|
@@ -97,10 +99,11 @@ function parseConfig(raw) {
|
|
|
97
99
|
maxRecords: Number(cfg.recall?.maxRecords ?? 3),
|
|
98
100
|
maxChars: Number(cfg.recall?.maxChars ?? 1200),
|
|
99
101
|
minScore: Number(cfg.recall?.minScore ?? 0.3),
|
|
102
|
+
requireDirectSupport: cfg.recall?.requireDirectSupport !== false,
|
|
100
103
|
timeoutMs: Number(cfg.recall?.timeoutMs ?? 4000),
|
|
101
104
|
},
|
|
102
105
|
persona: {
|
|
103
|
-
enabled: cfg.persona?.enabled
|
|
106
|
+
enabled: cfg.persona?.enabled === true,
|
|
104
107
|
maxChars: Number(cfg.persona?.maxChars ?? 600),
|
|
105
108
|
ttlSeconds: Number(cfg.persona?.ttlSeconds ?? 300),
|
|
106
109
|
},
|
|
@@ -995,6 +998,8 @@ function register(api) {
|
|
|
995
998
|
max_records: cfg.recall.maxRecords,
|
|
996
999
|
max_chars: cfg.recall.maxChars,
|
|
997
1000
|
min_score: cfg.recall.minScore,
|
|
1001
|
+
require_direct_support: cfg.recall.requireDirectSupport,
|
|
1002
|
+
exclude_record_ids: personaRecordIds,
|
|
998
1003
|
reference_mode: cfg.cacheAware.enabled && cfg.cacheAware.compactReferences
|
|
999
1004
|
? "compact"
|
|
1000
1005
|
: "full",
|
|
@@ -1072,6 +1077,9 @@ function register(api) {
|
|
|
1072
1077
|
context_chars: memoryContext.length,
|
|
1073
1078
|
candidate_ids: candidateIds,
|
|
1074
1079
|
context_component_event_ids: componentEventIds,
|
|
1080
|
+
persona_record_count: personaRecordIds.length,
|
|
1081
|
+
recall_record_count: (current?.injectedRecordIds ?? []).length,
|
|
1082
|
+
context_selection_profile: cfg.recall.requireDirectSupport ? "direct-support-v1" : "rank-threshold-v1",
|
|
1075
1083
|
mode: "direct",
|
|
1076
1084
|
context_location: contextLocation,
|
|
1077
1085
|
}, undefined, {
|
|
@@ -1134,8 +1142,12 @@ function register(api) {
|
|
|
1134
1142
|
tool_name: event.toolName,
|
|
1135
1143
|
tool_canonical_name: canonicalToolName(event.toolName),
|
|
1136
1144
|
result_sha256: digestJson(event.result ?? null),
|
|
1145
|
+
...(!event.error ? progressCardComparison(event.toolName, event.result, digestJson) : {}),
|
|
1137
1146
|
outcome: event.error ? "error" : "completed",
|
|
1138
1147
|
error_category: event.error ? "tool_error" : undefined,
|
|
1148
|
+
error_reason: event.error ? toolErrorReason(event.error) : undefined,
|
|
1149
|
+
error_sha256: event.error ? digestText(event.error) : undefined,
|
|
1150
|
+
result_present: event.result !== undefined,
|
|
1139
1151
|
duration_ms: event.durationMs ?? 0,
|
|
1140
1152
|
}, event.toolCallId ?? ctx.toolCallId);
|
|
1141
1153
|
});
|
|
@@ -1146,6 +1158,8 @@ function register(api) {
|
|
|
1146
1158
|
tool_name: saved.name, tool_canonical_name: canonicalToolName(saved.name),
|
|
1147
1159
|
result_sha256: saved.result.digest, outcome: saved.result.error ? "error" : "completed",
|
|
1148
1160
|
error_category: saved.result.error ? "tool_error" : undefined,
|
|
1161
|
+
error_reason: saved.result.reason,
|
|
1162
|
+
error_sha256: saved.result.errorDigest,
|
|
1149
1163
|
reason: "observed_terminal_host_tool_event",
|
|
1150
1164
|
}, saved.callId);
|
|
1151
1165
|
});
|
package/dist/src/setup.js
CHANGED
|
@@ -42,7 +42,7 @@ export function setupWrites(options) {
|
|
|
42
42
|
minScore: 0.3,
|
|
43
43
|
timeoutMs: 3000,
|
|
44
44
|
},
|
|
45
|
-
persona: { enabled:
|
|
45
|
+
persona: { enabled: false, maxChars: 600, ttlSeconds: 300 },
|
|
46
46
|
capture: { enabled: true, captureAssistant: true },
|
|
47
47
|
cacheAware: { enabled: true, compactReferences: true },
|
|
48
48
|
tools: { enabled: true },
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Retain a bounded diagnostic, never the full tool result or stack trace. */
|
|
2
|
+
export function toolErrorReason(value) {
|
|
3
|
+
let text = value;
|
|
4
|
+
if (value && typeof value === "object") {
|
|
5
|
+
const row = value;
|
|
6
|
+
text = row.error ?? row.message;
|
|
7
|
+
if (text && typeof text === "object")
|
|
8
|
+
text = text.message;
|
|
9
|
+
if (text === undefined && Array.isArray(row.content)) {
|
|
10
|
+
text = row.content.find(item => item?.type === "text" && typeof item.text === "string")?.text;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (typeof text !== "string" || !text.trim())
|
|
14
|
+
return undefined;
|
|
15
|
+
const bounded = text.slice(0, 8192);
|
|
16
|
+
// OpenClaw wraps thrown web errors as untrusted API content. Extract only
|
|
17
|
+
// its narrow status prefix, never the page text or instructions beneath it.
|
|
18
|
+
if (bounded.startsWith("SECURITY NOTICE:")) {
|
|
19
|
+
const status = bounded.match(/<<<EXTERNAL_UNTRUSTED_CONTENT id="[a-f0-9]+">>>\r?\nSource: API\r?\n---\r?\n(Web fetch failed \([1-5][0-9]{2}\)):/);
|
|
20
|
+
return status?.[1];
|
|
21
|
+
}
|
|
22
|
+
return bounded.split(/[\r\n]/, 1)[0]
|
|
23
|
+
.replace(/https?:\/\/[^\s<>]+/gi, "[redacted-url]")
|
|
24
|
+
.replace(/\b(?:Bearer|Basic)\s+\S+/gi, "[redacted-auth]")
|
|
25
|
+
.replace(/\b(?:api[_-]?key|token|password|passwd|secret|authorization|cookie)\s*[:=]\s*(?:"[^"]*"|'[^']*'|\S+)/gi, "[redacted-credential]")
|
|
26
|
+
.replace(/\b(?:sk-|ghp_|github_pat_)[A-Za-z0-9_-]+/g, "[redacted-token]")
|
|
27
|
+
.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, "[redacted-email]")
|
|
28
|
+
.replace(/(?:[A-Za-z]:\\|\/)[^\s<>"']+/g, "[redacted-path]")
|
|
29
|
+
.replace(/"[^"\r\n]*"|'[^'\r\n]*'/g, "[redacted-value]")
|
|
30
|
+
.replace(/[\x00-\x1f\x7f]/g, " ").trim().slice(0, 512) || undefined;
|
|
31
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { toolErrorReason } from "./tool-errors.js";
|
|
2
3
|
const CACHE_KEY = Symbol.for("atmem.tool-observation-cache.v1");
|
|
3
4
|
const sharedCache = globalThis;
|
|
4
5
|
const cache = sharedCache[CACHE_KEY] ??= new Map();
|
|
5
6
|
/** Some hosts deny run-context writes from tool hooks after registry reload.
|
|
6
|
-
* Keep a bounded content-
|
|
7
|
+
* Keep a bounded content-minimized process fallback, isolated by installation config.
|
|
7
8
|
*/
|
|
8
9
|
export function observationContext(host, scope) {
|
|
9
10
|
const keyFor = (runId, namespace) => JSON.stringify([scope, runId, namespace]);
|
|
@@ -45,7 +46,7 @@ function canonical(name) {
|
|
|
45
46
|
const unwrapped = name.replace(/^mcp__openclaw__/, "");
|
|
46
47
|
return { Read: "read", Bash: "exec" }[unwrapped] ?? unwrapped;
|
|
47
48
|
}
|
|
48
|
-
/** Stores
|
|
49
|
+
/** Stores scope, names, digests and redacted error diagnostics in bounded ephemeral context. */
|
|
49
50
|
export class ToolObservations {
|
|
50
51
|
access;
|
|
51
52
|
digest;
|
|
@@ -88,9 +89,12 @@ export class ToolObservations {
|
|
|
88
89
|
(event.agentId !== undefined && event.agentId !== saved.ctx.agentId) ||
|
|
89
90
|
canonical(d.name) !== canonical(saved.name))
|
|
90
91
|
return;
|
|
91
|
-
const result = { digest: this.digest(d.result), error: d.isError
|
|
92
|
+
const result = { digest: this.digest(d.result), error: d.isError,
|
|
93
|
+
reason: d.isError ? toolErrorReason(d.error ?? d.result) : undefined,
|
|
94
|
+
errorDigest: d.isError && d.error !== undefined ? this.digest(d.error) : undefined };
|
|
92
95
|
const conflict = saved.conflict || (saved.result !== undefined &&
|
|
93
|
-
(saved.result.digest !== result.digest || saved.result.error !== result.error
|
|
96
|
+
(saved.result.digest !== result.digest || saved.result.error !== result.error ||
|
|
97
|
+
saved.result.errorDigest !== result.errorDigest));
|
|
94
98
|
this.access.setRunContext({ ...key, value: { ...saved, result, conflict } });
|
|
95
99
|
}
|
|
96
100
|
async flush(ctx, record) {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Exact OpenClaw progress_card ↔ native relay equivalence. Never discard raw digests.
|
|
2
|
+
export function progressCardComparison(name, result, digest) {
|
|
3
|
+
if (name !== "progress_card")
|
|
4
|
+
return {};
|
|
5
|
+
const record = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
6
|
+
const keys = (v, expected) => Object.keys(v).sort().join(",") === [...expected].sort().join(",");
|
|
7
|
+
const native = Array.isArray(result);
|
|
8
|
+
if (!native && (!record(result) || !keys(result, ["content", "details"])))
|
|
9
|
+
return {};
|
|
10
|
+
const content = native ? result : result.content;
|
|
11
|
+
if (!Array.isArray(content) || content.length !== 2 || !content.every(v => record(v) && keys(v, ["type", "text"]) && v.type === (native ? "input_text" : "text") &&
|
|
12
|
+
typeof v.text === "string" && v.text.length <= 1024))
|
|
13
|
+
return {};
|
|
14
|
+
let payload;
|
|
15
|
+
try {
|
|
16
|
+
payload = JSON.parse(content[1].text);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
if (!record(payload) || !keys(payload, ["revision", "steps"]))
|
|
22
|
+
return {};
|
|
23
|
+
const { revision, steps } = payload;
|
|
24
|
+
if (revision !== null && (!Number.isSafeInteger(revision) || revision < 1))
|
|
25
|
+
return {};
|
|
26
|
+
if (steps !== null && (!record(steps) || !keys(steps, ["completed", "total"]) ||
|
|
27
|
+
!Number.isSafeInteger(steps.completed) || !Number.isSafeInteger(steps.total) ||
|
|
28
|
+
steps.completed < 0 || steps.total < 1 || steps.total > 50 ||
|
|
29
|
+
steps.completed > steps.total))
|
|
30
|
+
return {};
|
|
31
|
+
if (revision === null && steps !== null)
|
|
32
|
+
return {};
|
|
33
|
+
const expected = revision === null ? "Progress card cleared" : steps === null
|
|
34
|
+
? `Progress card updated (rev ${revision})`
|
|
35
|
+
: `Progress card updated (rev ${revision}, ${steps.completed}/${steps.total} done)`;
|
|
36
|
+
if (content[0].text !== expected || content[1].text !== JSON.stringify(payload, null, 2))
|
|
37
|
+
return {};
|
|
38
|
+
if (!native && digest(result.details) !== digest(payload))
|
|
39
|
+
return {};
|
|
40
|
+
return {
|
|
41
|
+
result_comparison_profile: "openclaw-progress-card-v1",
|
|
42
|
+
result_comparison_sha256: digest(payload),
|
|
43
|
+
result_observation_shape: native ? "native_input_text" : "tool_content_details",
|
|
44
|
+
};
|
|
45
|
+
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "2.2.7-beta.1",
|
|
6
6
|
"commandAliases": ["memory-atmem"],
|
|
7
7
|
"activation": {
|
|
8
8
|
"onStartup": true,
|
|
@@ -81,15 +81,16 @@
|
|
|
81
81
|
"enabled": { "type": "boolean", "default": true },
|
|
82
82
|
"maxRecords": { "type": "number", "default": 3, "description": "Max memories injected per turn" },
|
|
83
83
|
"maxChars": { "type": "number", "default": 1200, "description": "Total character budget for the injected block" },
|
|
84
|
+
"requireDirectSupport": { "type": "boolean", "default": true, "description": "Require direct query relevance for automatic recall; rank score alone is insufficient" },
|
|
84
85
|
"minScore": { "type": "number", "default": 0.3, "description": "Minimum relevance score; 0.3 requires a lexical match (priors alone never inject)" },
|
|
85
86
|
"timeoutMs": { "type": "number", "default": 4000, "description": "Recall timeout; on timeout the turn proceeds without injection" }
|
|
86
87
|
}
|
|
87
88
|
},
|
|
88
89
|
"persona": {
|
|
89
90
|
"type": "object",
|
|
90
|
-
"description": "
|
|
91
|
+
"description": "Optional always-on persona snapshot; disabled by default to avoid unrelated context",
|
|
91
92
|
"properties": {
|
|
92
|
-
"enabled": { "type": "boolean", "default":
|
|
93
|
+
"enabled": { "type": "boolean", "default": false },
|
|
93
94
|
"maxChars": { "type": "number", "default": 600, "description": "Character budget for the <user_persona> block" },
|
|
94
95
|
"ttlSeconds": { "type": "number", "default": 300, "description": "Persona cache lifetime; also invalidated whenever new memory is captured" }
|
|
95
96
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-memory-atmem",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.7-beta.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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsc -p tsconfig.json",
|
|
42
42
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
43
|
-
"test": "node test/manifest.mjs && node test/hook-context-compat.mjs && node test/setup.mjs && node test/hooks.mjs && node test/delegated-identity.mjs && node test/tool-observations.mjs && node test/task-tools.mjs && node test/delegated-context-contract.mjs",
|
|
43
|
+
"test": "node test/manifest.mjs && node test/hook-context-compat.mjs && node test/setup.mjs && node test/hooks.mjs && node test/delegated-identity.mjs && node test/tool-observations.mjs && node test/tool-result-comparison.mjs && node test/blackbox-diagnostics.mjs && node test/task-tools.mjs && node test/delegated-context-contract.mjs",
|
|
44
44
|
"smoke": "node test/smoke.mjs && node test/task-journey.mjs && node test/delegated-journey.mjs",
|
|
45
45
|
"prepack": "npm run build && npm run typecheck && npm test && npm run smoke",
|
|
46
46
|
"hook-context": "node test/hook-context-compat.mjs",
|