@remnic/plugin-openclaw 1.0.31 → 1.0.32
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 +17 -1
- package/dist/index.js +174 -5
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,7 +25,10 @@ Add the plugin to your `openclaw.json`:
|
|
|
25
25
|
"slots": { "memory": "openclaw-remnic" },
|
|
26
26
|
"entries": {
|
|
27
27
|
"openclaw-remnic": {
|
|
28
|
-
"package": "@remnic/plugin-openclaw"
|
|
28
|
+
"package": "@remnic/plugin-openclaw",
|
|
29
|
+
"hooks": {
|
|
30
|
+
"allowConversationAccess": true
|
|
31
|
+
}
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
}
|
|
@@ -205,6 +208,15 @@ Remnic supports the following OpenClaw memory integration points:
|
|
|
205
208
|
| `before_prompt_build` hook (new SDK) | Supported | 2026.3.22 |
|
|
206
209
|
| `registerMemoryPromptSection()` (structured builder) | Supported | 2026.3.22 |
|
|
207
210
|
| `registerMemoryCapability()` (unified capability) | Supported | 2026.4.5 |
|
|
211
|
+
| `registerMemoryRuntime()` (split runtime surface) | Supported | 2026.4.x |
|
|
212
|
+
| `registerMemoryFlushPlan()` (split flush-plan surface) | Supported | 2026.4.x |
|
|
213
|
+
| `registerMemoryCorpusSupplement()` (read-only corpus supplement) | Supported | 2026.4.x |
|
|
214
|
+
|
|
215
|
+
On current OpenClaw SDKs, Remnic registers both the unified memory capability
|
|
216
|
+
and the compatible split surfaces. That lets OpenClaw consume Remnic through
|
|
217
|
+
the active memory runtime, the explicit flush-plan resolver, and additive
|
|
218
|
+
corpus search/read APIs without changing Remnic's ownership of storage,
|
|
219
|
+
retrieval, extraction, and QMD behavior.
|
|
208
220
|
|
|
209
221
|
### Public Artifacts (memory-wiki bridge)
|
|
210
222
|
|
|
@@ -224,6 +236,10 @@ Private runtime state (state/, questions/, transcripts/, archive/, buffers) is n
|
|
|
224
236
|
|
|
225
237
|
With this feature, `openclaw wiki status` reports Remnic artifacts, and `memory-wiki` bridge mode can discover and ingest them.
|
|
226
238
|
|
|
239
|
+
The corpus supplement exposes read-only search/get access to Remnic memories as
|
|
240
|
+
the `remnic` corpus under a service-scoped supplement ID. It does not expose
|
|
241
|
+
private plugin state, transcript buffers, auth metadata, or artifact paths.
|
|
242
|
+
|
|
227
243
|
### Session Lifecycle
|
|
228
244
|
|
|
229
245
|
| Feature | Status | Since |
|
package/dist/index.js
CHANGED
|
@@ -72718,6 +72718,9 @@ var NODE_FS_MODULE_ID = ["node", "fs"].join(":");
|
|
|
72718
72718
|
var NODE_FS_PROMISES_MODULE_ID = ["node", "fs/promises"].join(":");
|
|
72719
72719
|
var READ_FILE_SYNC_FIELD = ["read", "File", "Sync"].join("");
|
|
72720
72720
|
var EXISTS_SYNC_FIELD = ["exists", "Sync"].join("");
|
|
72721
|
+
function isMemoryArtifactPath(p) {
|
|
72722
|
+
return /(?:^|[\\/])artifacts(?:[\\/]|$)/i.test(p);
|
|
72723
|
+
}
|
|
72721
72724
|
function readTextFileNow(filePath) {
|
|
72722
72725
|
const nodeRequire = createRequire3(import.meta.url);
|
|
72723
72726
|
const fs19 = nodeRequire(NODE_FS_MODULE_ID);
|
|
@@ -74140,7 +74143,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
74140
74143
|
api.registerMemoryPromptSection(memoryBuildFn);
|
|
74141
74144
|
memoryPromptBuilder = memoryBuildFn;
|
|
74142
74145
|
}
|
|
74143
|
-
if (sdkCaps.hasRegisterMemoryCapability && typeof api.registerMemoryCapability === "function") {
|
|
74146
|
+
if (sdkCaps.hasRegisterMemoryCapability && typeof api.registerMemoryCapability === "function" || typeof api.registerMemoryRuntime === "function" || typeof api.registerMemoryFlushPlan === "function") {
|
|
74144
74147
|
const capabilityPromptBuilder = memoryPromptBuilder ? (params) => {
|
|
74145
74148
|
const key = params?.sessionKey ?? "default";
|
|
74146
74149
|
return consumePromptMemoryLines2(key, { destructive: false });
|
|
@@ -74249,11 +74252,10 @@ Keep the reflection grounded in the evidence below.
|
|
|
74249
74252
|
namespaces: namespace ? [namespace] : void 0,
|
|
74250
74253
|
mode: resolvedMode
|
|
74251
74254
|
});
|
|
74252
|
-
const isArtifactPath2 = (p) => /(?:^|[\\/])artifacts(?:[\\/]|$)/i.test(p);
|
|
74253
74255
|
return rawResults.filter((result) => {
|
|
74254
74256
|
const candidate = result;
|
|
74255
74257
|
const p = typeof candidate.path === "string" ? candidate.path : typeof candidate.id === "string" ? candidate.id : "";
|
|
74256
|
-
return !
|
|
74258
|
+
return !isMemoryArtifactPath(p);
|
|
74257
74259
|
}).map((result, index) => {
|
|
74258
74260
|
const candidate = result;
|
|
74259
74261
|
const rawPath = typeof candidate.path === "string" ? candidate.path : typeof candidate.id === "string" ? candidate.id : `memory-${index + 1}`;
|
|
@@ -74365,6 +74367,18 @@ Keep the reflection grounded in the evidence below.
|
|
|
74365
74367
|
async closeAllMemorySearchManagers() {
|
|
74366
74368
|
}
|
|
74367
74369
|
};
|
|
74370
|
+
const remnicMemoryFlushPlanResolver = () => {
|
|
74371
|
+
const maxTurnChars = typeof cfg.extractionMaxTurnChars === "number" && Number.isFinite(cfg.extractionMaxTurnChars) ? Math.max(1e3, Math.floor(cfg.extractionMaxTurnChars)) : 8e3;
|
|
74372
|
+
return {
|
|
74373
|
+
softThresholdTokens: 24e3,
|
|
74374
|
+
forceFlushTranscriptBytes: Math.max(16384, maxTurnChars * 4),
|
|
74375
|
+
reserveTokensFloor: 2e3,
|
|
74376
|
+
model: typeof cfg.summaryModel === "string" && cfg.summaryModel.length > 0 ? cfg.summaryModel : cfg.model,
|
|
74377
|
+
prompt: "Flush the recent OpenClaw transcript into Remnic memory. Preserve durable user preferences, project facts, decisions, corrections, and commitments. Ignore runtime metadata, credentials, and transient command noise.",
|
|
74378
|
+
systemPrompt: "You are Remnic's memory flush planner. Produce concise durable memory candidates only when the transcript contains information worth remembering.",
|
|
74379
|
+
relativePath: ["state", "plugins", serviceId, "flush-plan.md"].join("/")
|
|
74380
|
+
};
|
|
74381
|
+
};
|
|
74368
74382
|
const memoryCapability = {
|
|
74369
74383
|
// Include the promptBuilder so runtimes that treat unified capability
|
|
74370
74384
|
// registration as authoritative (SDK >=2026.4.5) continue to inject
|
|
@@ -74372,6 +74386,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
74372
74386
|
// Respect promptInjectionAllowed policy — omit promptBuilder if injection
|
|
74373
74387
|
// is disabled, so the capability only provides publicArtifacts.
|
|
74374
74388
|
...promptInjectionAllowed ? { promptBuilder: capabilityPromptBuilder } : {},
|
|
74389
|
+
flushPlanResolver: remnicMemoryFlushPlanResolver,
|
|
74375
74390
|
runtime: remnicMemoryRuntime,
|
|
74376
74391
|
publicArtifacts: {
|
|
74377
74392
|
listArtifacts: async (_params) => {
|
|
@@ -74388,9 +74403,18 @@ Keep the reflection grounded in the evidence below.
|
|
|
74388
74403
|
}
|
|
74389
74404
|
}
|
|
74390
74405
|
};
|
|
74391
|
-
api.registerMemoryCapability
|
|
74406
|
+
if (typeof api.registerMemoryCapability === "function") {
|
|
74407
|
+
api.registerMemoryCapability(memoryCapability);
|
|
74408
|
+
}
|
|
74409
|
+
if (typeof api.registerMemoryRuntime === "function") {
|
|
74410
|
+
api.registerMemoryRuntime(remnicMemoryRuntime);
|
|
74411
|
+
}
|
|
74412
|
+
if (typeof api.registerMemoryFlushPlan === "function") {
|
|
74413
|
+
api.registerMemoryFlushPlan(remnicMemoryFlushPlanResolver);
|
|
74414
|
+
}
|
|
74392
74415
|
const builderDesc = !promptInjectionAllowed ? " (promptBuilder omitted \u2014 injection disabled by policy)" : memoryPromptBuilder ? " and promptBuilder (from registerMemoryPromptSection)" : " and promptBuilder (capability-only fallback)";
|
|
74393
|
-
|
|
74416
|
+
const capabilityDesc = typeof api.registerMemoryCapability === "function" ? `memory capability with publicArtifacts provider${builderDesc}` : "split memory runtime/flush-plan surfaces";
|
|
74417
|
+
log.info(`registered ${capabilityDesc}`);
|
|
74394
74418
|
}
|
|
74395
74419
|
api.on(
|
|
74396
74420
|
"agent_end",
|
|
@@ -74992,6 +75016,151 @@ Keep the reflection grounded in the evidence below.
|
|
|
74992
75016
|
log.error("failed to auto-register hourly summary cron job:", err);
|
|
74993
75017
|
}
|
|
74994
75018
|
}
|
|
75019
|
+
if (typeof api.registerMemoryCorpusSupplement === "function") {
|
|
75020
|
+
const normalizeCorpusLookup = (lookup) => typeof lookup === "string" && lookup.trim().length > 0 ? lookup.trim() : null;
|
|
75021
|
+
const resolveCorpusStorage = async (agentSessionKey) => {
|
|
75022
|
+
const namespace = typeof orchestrator.resolveSelfNamespace === "function" ? orchestrator.resolveSelfNamespace(agentSessionKey) : void 0;
|
|
75023
|
+
return typeof orchestrator.getStorageForNamespace === "function" ? await orchestrator.getStorageForNamespace(namespace) : orchestrator.storage;
|
|
75024
|
+
};
|
|
75025
|
+
const normalizeCorpusPath = (value) => value.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
75026
|
+
const pathIsInside = (root, candidate) => {
|
|
75027
|
+
const relative = path99.relative(root, candidate);
|
|
75028
|
+
return relative === "" || !relative.startsWith("..") && !path99.isAbsolute(relative);
|
|
75029
|
+
};
|
|
75030
|
+
const corpusPathCandidates = (rawPath, storageDir) => {
|
|
75031
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
75032
|
+
const trimmed = rawPath.trim();
|
|
75033
|
+
if (!trimmed) return [];
|
|
75034
|
+
candidates.add(normalizeCorpusPath(trimmed));
|
|
75035
|
+
if (path99.isAbsolute(trimmed)) {
|
|
75036
|
+
const absolutePath = path99.resolve(trimmed);
|
|
75037
|
+
const absoluteStorageDir = path99.resolve(storageDir);
|
|
75038
|
+
if (pathIsInside(absoluteStorageDir, absolutePath)) {
|
|
75039
|
+
candidates.add(normalizeCorpusPath(path99.relative(absoluteStorageDir, absolutePath)));
|
|
75040
|
+
}
|
|
75041
|
+
}
|
|
75042
|
+
candidates.add(path99.basename(trimmed));
|
|
75043
|
+
return [...candidates].filter((candidate) => candidate.length > 0);
|
|
75044
|
+
};
|
|
75045
|
+
const displayCorpusPath = (rawPath, storageDir) => {
|
|
75046
|
+
const trimmed = rawPath.trim();
|
|
75047
|
+
if (!trimmed) return "";
|
|
75048
|
+
if (path99.isAbsolute(trimmed)) {
|
|
75049
|
+
const absolutePath = path99.resolve(trimmed);
|
|
75050
|
+
const absoluteStorageDir = path99.resolve(storageDir);
|
|
75051
|
+
if (pathIsInside(absoluteStorageDir, absolutePath)) {
|
|
75052
|
+
return normalizeCorpusPath(path99.relative(absoluteStorageDir, absolutePath));
|
|
75053
|
+
}
|
|
75054
|
+
}
|
|
75055
|
+
return normalizeCorpusPath(trimmed);
|
|
75056
|
+
};
|
|
75057
|
+
const readMemoryByLookup = async (lookup, agentSessionKey) => {
|
|
75058
|
+
const storage = await resolveCorpusStorage(agentSessionKey);
|
|
75059
|
+
const storageDir = typeof storage.dir === "string" && storage.dir.length > 0 ? storage.dir : orchestrator.config.memoryDir;
|
|
75060
|
+
const memories = await storage.readAllMemories();
|
|
75061
|
+
const normalizedLookup = normalizeCorpusPath(lookup);
|
|
75062
|
+
const matched = memories.find((memory) => memory.frontmatter.id === lookup) ?? memories.find(
|
|
75063
|
+
(memory) => corpusPathCandidates(memory.path, storageDir).includes(normalizedLookup)
|
|
75064
|
+
) ?? null;
|
|
75065
|
+
if (!matched) return null;
|
|
75066
|
+
const displayPath = displayCorpusPath(matched.path, storageDir);
|
|
75067
|
+
return { memory: matched, displayPath };
|
|
75068
|
+
};
|
|
75069
|
+
const corpusBackendError = (operation, err) => {
|
|
75070
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
75071
|
+
const error = new Error(`Remnic corpus ${operation} failed: ${message}`);
|
|
75072
|
+
error.cause = err;
|
|
75073
|
+
return error;
|
|
75074
|
+
};
|
|
75075
|
+
const remnicCorpusSupplement = {
|
|
75076
|
+
id: `${serviceId}:remnic-memory-corpus`,
|
|
75077
|
+
label: "Remnic Memory Corpus",
|
|
75078
|
+
async search(params) {
|
|
75079
|
+
const query = normalizeCorpusLookup(
|
|
75080
|
+
typeof params === "string" ? params : params?.query
|
|
75081
|
+
);
|
|
75082
|
+
if (!query) return [];
|
|
75083
|
+
const maxResults = typeof params === "object" && typeof params.maxResults === "number" && Number.isFinite(params.maxResults) ? Math.max(1, Math.floor(params.maxResults)) : 8;
|
|
75084
|
+
const agentSessionKey = typeof params === "object" ? params.agentSessionKey : void 0;
|
|
75085
|
+
const namespace = typeof orchestrator.resolveSelfNamespace === "function" ? orchestrator.resolveSelfNamespace(agentSessionKey) : void 0;
|
|
75086
|
+
try {
|
|
75087
|
+
const rawResults = await orchestrator.searchAcrossNamespaces({
|
|
75088
|
+
query,
|
|
75089
|
+
maxResults,
|
|
75090
|
+
namespaces: namespace ? [namespace] : void 0,
|
|
75091
|
+
mode: "search"
|
|
75092
|
+
});
|
|
75093
|
+
return rawResults.filter((result) => {
|
|
75094
|
+
const candidate = result;
|
|
75095
|
+
const p = typeof candidate.path === "string" ? candidate.path : typeof candidate.id === "string" ? candidate.id : "";
|
|
75096
|
+
return !isMemoryArtifactPath(p);
|
|
75097
|
+
}).map((result, index) => {
|
|
75098
|
+
const candidate = result;
|
|
75099
|
+
const lookupPath = typeof candidate.path === "string" ? candidate.path : typeof candidate.id === "string" ? candidate.id : `remnic-memory-${index + 1}`;
|
|
75100
|
+
const startLine = typeof candidate.startLine === "number" && Number.isFinite(candidate.startLine) ? Math.max(1, Math.floor(candidate.startLine)) : 1;
|
|
75101
|
+
const endLine = typeof candidate.endLine === "number" && Number.isFinite(candidate.endLine) ? Math.max(startLine, Math.floor(candidate.endLine)) : startLine;
|
|
75102
|
+
return {
|
|
75103
|
+
corpus: "remnic",
|
|
75104
|
+
path: lookupPath,
|
|
75105
|
+
title: typeof candidate.id === "string" && candidate.id.length > 0 ? candidate.id : lookupPath,
|
|
75106
|
+
kind: "memory",
|
|
75107
|
+
score: typeof candidate.score === "number" && Number.isFinite(candidate.score) ? candidate.score : 0,
|
|
75108
|
+
snippet: typeof candidate.snippet === "string" ? candidate.snippet : typeof candidate.text === "string" ? candidate.text : "",
|
|
75109
|
+
id: typeof candidate.id === "string" ? candidate.id : lookupPath,
|
|
75110
|
+
startLine,
|
|
75111
|
+
endLine,
|
|
75112
|
+
citation: lookupPath,
|
|
75113
|
+
source: "remnic",
|
|
75114
|
+
provenanceLabel: "Remnic",
|
|
75115
|
+
sourceType: "memory",
|
|
75116
|
+
sourcePath: lookupPath,
|
|
75117
|
+
updatedAt: typeof candidate.metadata?.updatedAt === "string" ? candidate.metadata.updatedAt : void 0
|
|
75118
|
+
};
|
|
75119
|
+
});
|
|
75120
|
+
} catch (err) {
|
|
75121
|
+
log.warn(`memory corpus search failed: ${err}`);
|
|
75122
|
+
throw corpusBackendError("search", err);
|
|
75123
|
+
}
|
|
75124
|
+
},
|
|
75125
|
+
async get(params) {
|
|
75126
|
+
const lookup = normalizeCorpusLookup(
|
|
75127
|
+
typeof params === "string" ? params : params?.lookup
|
|
75128
|
+
);
|
|
75129
|
+
if (!lookup || isMemoryArtifactPath(lookup)) return null;
|
|
75130
|
+
const agentSessionKey = typeof params === "object" ? params.agentSessionKey : void 0;
|
|
75131
|
+
try {
|
|
75132
|
+
const resolved = await readMemoryByLookup(lookup, agentSessionKey);
|
|
75133
|
+
if (!resolved) return null;
|
|
75134
|
+
const { memory, displayPath } = resolved;
|
|
75135
|
+
if (isMemoryArtifactPath(displayPath) || isMemoryArtifactPath(memory.path)) {
|
|
75136
|
+
return null;
|
|
75137
|
+
}
|
|
75138
|
+
const allLines = memory.content.split(/\r?\n/);
|
|
75139
|
+
const fromLine = typeof params === "object" && typeof params.fromLine === "number" && Number.isFinite(params.fromLine) ? Math.max(1, Math.floor(params.fromLine)) : 1;
|
|
75140
|
+
const lineCount = typeof params === "object" && typeof params.lineCount === "number" && Number.isFinite(params.lineCount) ? Math.max(1, Math.floor(params.lineCount)) : allLines.length;
|
|
75141
|
+
const selected = allLines.slice(fromLine - 1, fromLine - 1 + lineCount);
|
|
75142
|
+
return {
|
|
75143
|
+
corpus: "remnic",
|
|
75144
|
+
path: displayPath,
|
|
75145
|
+
title: memory.frontmatter.id,
|
|
75146
|
+
kind: memory.frontmatter.category,
|
|
75147
|
+
content: selected.join("\n"),
|
|
75148
|
+
fromLine,
|
|
75149
|
+
lineCount: selected.length,
|
|
75150
|
+
id: memory.frontmatter.id,
|
|
75151
|
+
provenanceLabel: "Remnic",
|
|
75152
|
+
sourceType: "memory",
|
|
75153
|
+
sourcePath: displayPath,
|
|
75154
|
+
updatedAt: memory.frontmatter.updated
|
|
75155
|
+
};
|
|
75156
|
+
} catch (err) {
|
|
75157
|
+
log.warn(`memory corpus get failed: ${err}`);
|
|
75158
|
+
throw corpusBackendError("get", err);
|
|
75159
|
+
}
|
|
75160
|
+
}
|
|
75161
|
+
};
|
|
75162
|
+
api.registerMemoryCorpusSupplement(remnicCorpusSupplement);
|
|
75163
|
+
}
|
|
74995
75164
|
if (cfg.openclawToolsEnabled !== false && typeof api.registerTool === "function") {
|
|
74996
75165
|
api.registerTool(
|
|
74997
75166
|
buildMemorySearchTool(orchestrator, {
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-remnic",
|
|
3
3
|
"name": "Remnic OpenClaw Plugin",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.32",
|
|
5
5
|
"kind": "memory",
|
|
6
6
|
"description": "Local semantic memory for OpenClaw with bundled Remnic core runtime. Requires plugins.slots.memory set to this plugin id for hooks to fire.",
|
|
7
7
|
"setup": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/plugin-openclaw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"description": "OpenClaw adapter for Remnic memory with bundled @remnic/core runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"openai": "^6.0.0",
|
|
66
|
-
"@remnic/core": "^1.1.
|
|
66
|
+
"@remnic/core": "^1.1.9"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"openclaw": ">=2026.4.8"
|