@remnic/plugin-openclaw 1.0.30 → 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 +38 -1
- package/dist/index.js +198 -29
- 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
|
}
|
|
@@ -104,6 +107,18 @@ The npm package also declares this surface in `package.json` under
|
|
|
104
107
|
provider env vars, config path, and external-model routing behavior before
|
|
105
108
|
installation.
|
|
106
109
|
|
|
110
|
+
## Privacy Boundary
|
|
111
|
+
|
|
112
|
+
OpenClaw hook runtime metadata such as authorization headers, API keys, provider
|
|
113
|
+
credential objects, and bearer tokens is operational metadata. Remnic does not
|
|
114
|
+
persist those fields to transcripts, extraction buffers, recall audit entries,
|
|
115
|
+
logs, or memory content.
|
|
116
|
+
|
|
117
|
+
User-authored message text is different: Remnic is a memory plugin, so message
|
|
118
|
+
content can be stored, extracted, summarized, embedded, or recalled according to
|
|
119
|
+
the configured memory policy. Do not paste secrets into chat when you do not
|
|
120
|
+
want them treated as conversation content.
|
|
121
|
+
|
|
107
122
|
## Plugin Inspection
|
|
108
123
|
|
|
109
124
|
Run the OpenClaw plugin inspector with:
|
|
@@ -128,6 +143,15 @@ npm run test:openclaw-scenarios
|
|
|
128
143
|
The suite covers the registered memory tools and lifecycle hooks without live
|
|
129
144
|
OpenClaw, LLM credentials, or network calls.
|
|
130
145
|
|
|
146
|
+
Run the OpenClaw hook privacy suite with:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npm run test:openclaw-privacy
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The suite guards against runtime auth metadata leaking into persisted memory,
|
|
153
|
+
transcript, recall-audit, or debug-log surfaces.
|
|
154
|
+
|
|
131
155
|
## SDK Surface Drift Check
|
|
132
156
|
|
|
133
157
|
The adapter keeps a conservative OpenClaw SDK surface snapshot at
|
|
@@ -184,6 +208,15 @@ Remnic supports the following OpenClaw memory integration points:
|
|
|
184
208
|
| `before_prompt_build` hook (new SDK) | Supported | 2026.3.22 |
|
|
185
209
|
| `registerMemoryPromptSection()` (structured builder) | Supported | 2026.3.22 |
|
|
186
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.
|
|
187
220
|
|
|
188
221
|
### Public Artifacts (memory-wiki bridge)
|
|
189
222
|
|
|
@@ -203,6 +236,10 @@ Private runtime state (state/, questions/, transcripts/, archive/, buffers) is n
|
|
|
203
236
|
|
|
204
237
|
With this feature, `openclaw wiki status` reports Remnic artifacts, and `memory-wiki` bridge mode can discover and ingest them.
|
|
205
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
|
+
|
|
206
243
|
### Session Lifecycle
|
|
207
244
|
|
|
208
245
|
| Feature | Status | Since |
|
package/dist/index.js
CHANGED
|
@@ -7104,7 +7104,7 @@ Consolidate the new memories against existing ones.`
|
|
|
7104
7104
|
return { items: [], profileUpdates: [], entityUpdates: [] };
|
|
7105
7105
|
}
|
|
7106
7106
|
try {
|
|
7107
|
-
const
|
|
7107
|
+
const instructionText = `You are a memory consolidation system. Compare new memories against existing ones and decide what to do with each.
|
|
7108
7108
|
|
|
7109
7109
|
Actions:
|
|
7110
7110
|
- ADD: Keep the new memory as-is (no duplicate exists)
|
|
@@ -7132,7 +7132,7 @@ ${CONSOLIDATION_RESPONSE_SCHEMA}`;
|
|
|
7132
7132
|
const response = await this.client.chat.completions.create({
|
|
7133
7133
|
model: this.config.model,
|
|
7134
7134
|
messages: [
|
|
7135
|
-
{ role: "system", content:
|
|
7135
|
+
{ role: "system", content: instructionText },
|
|
7136
7136
|
{ role: "user", content: "Consolidate the new memories against existing ones." }
|
|
7137
7137
|
],
|
|
7138
7138
|
...this.config.reasoningEffort !== "none" ? { reasoning_effort: this.config.reasoningEffort } : {},
|
|
@@ -7321,7 +7321,7 @@ The output should be the COMPLETE consolidated profile as valid markdown, starti
|
|
|
7321
7321
|
return null;
|
|
7322
7322
|
}
|
|
7323
7323
|
try {
|
|
7324
|
-
const
|
|
7324
|
+
const instructionText = `You are a profile consolidation system. You are given a behavioral profile (markdown) that has grown too large. Your job is to produce a CONSOLIDATED version that:
|
|
7325
7325
|
|
|
7326
7326
|
1. PRESERVES all ## section headers and their structure
|
|
7327
7327
|
2. MERGES duplicate or near-duplicate bullet points into single, clear statements
|
|
@@ -7342,7 +7342,7 @@ Respond with valid JSON matching this schema:
|
|
|
7342
7342
|
const response = await this.client.chat.completions.create({
|
|
7343
7343
|
model: this.config.model,
|
|
7344
7344
|
messages: [
|
|
7345
|
-
{ role: "system", content:
|
|
7345
|
+
{ role: "system", content: instructionText },
|
|
7346
7346
|
{ role: "user", content: fullProfileContent }
|
|
7347
7347
|
],
|
|
7348
7348
|
...this.config.reasoningEffort !== "none" ? { reasoning_effort: this.config.reasoningEffort } : {},
|
|
@@ -7523,7 +7523,7 @@ The goal is to reduce a bloated file to a compact, high-signal set of learned pa
|
|
|
7523
7523
|
return null;
|
|
7524
7524
|
}
|
|
7525
7525
|
try {
|
|
7526
|
-
const
|
|
7526
|
+
const instructionText = `You are an identity consolidation system. You are given the full contents of an IDENTITY.md file that contains many individual reflection entries. Your job is to:
|
|
7527
7527
|
|
|
7528
7528
|
1. Read all the reflection entries (sections starting with "## Reflection")
|
|
7529
7529
|
2. Extract the most important, durable behavioral patterns and lessons learned
|
|
@@ -7542,7 +7542,7 @@ Respond with valid JSON matching this schema:
|
|
|
7542
7542
|
const response = await this.client.chat.completions.create({
|
|
7543
7543
|
model: this.config.model,
|
|
7544
7544
|
messages: [
|
|
7545
|
-
{ role: "system", content:
|
|
7545
|
+
{ role: "system", content: instructionText },
|
|
7546
7546
|
{ role: "user", content: fullIdentityContent }
|
|
7547
7547
|
],
|
|
7548
7548
|
...this.config.reasoningEffort !== "none" ? { reasoning_effort: this.config.reasoningEffort } : {},
|
|
@@ -7671,7 +7671,7 @@ Memory 2 (new):
|
|
|
7671
7671
|
Category: ${newMemory.category}
|
|
7672
7672
|
Content: ${newMemory.content}`;
|
|
7673
7673
|
try {
|
|
7674
|
-
const
|
|
7674
|
+
const instructionText = `You are a contradiction detection system. Analyze whether two memories contradict each other.
|
|
7675
7675
|
|
|
7676
7676
|
IMPORTANT: Not all similar memories are contradictions!
|
|
7677
7677
|
- "User likes TypeScript" and "User likes Python" are NOT contradictions (preferences can coexist)
|
|
@@ -7697,7 +7697,7 @@ Respond with valid JSON matching this schema:
|
|
|
7697
7697
|
try {
|
|
7698
7698
|
const localResponse = await this.localLlm.chatCompletion(
|
|
7699
7699
|
[
|
|
7700
|
-
{ role: "system", content:
|
|
7700
|
+
{ role: "system", content: instructionText },
|
|
7701
7701
|
{ role: "user", content: input }
|
|
7702
7702
|
],
|
|
7703
7703
|
{
|
|
@@ -7730,7 +7730,7 @@ Respond with valid JSON matching this schema:
|
|
|
7730
7730
|
if (!this.shouldUseDirectClient) {
|
|
7731
7731
|
const fallbackResponse = await this.fallbackLlm.chatCompletion(
|
|
7732
7732
|
[
|
|
7733
|
-
{ role: "system", content:
|
|
7733
|
+
{ role: "system", content: instructionText },
|
|
7734
7734
|
{ role: "user", content: input }
|
|
7735
7735
|
],
|
|
7736
7736
|
this.withGatewayAgent({ temperature: 0.3, maxTokens: 2048 })
|
|
@@ -7750,7 +7750,7 @@ Respond with valid JSON matching this schema:
|
|
|
7750
7750
|
const response = await this.client.chat.completions.create({
|
|
7751
7751
|
model: this.config.model,
|
|
7752
7752
|
messages: [
|
|
7753
|
-
{ role: "system", content:
|
|
7753
|
+
{ role: "system", content: instructionText },
|
|
7754
7754
|
{ role: "user", content: input }
|
|
7755
7755
|
],
|
|
7756
7756
|
...buildChatCompletionTokenLimit(this.config.model, 2048, {
|
|
@@ -7790,7 +7790,7 @@ Content: ${newMemory.content}
|
|
|
7790
7790
|
Candidate memories to link to:
|
|
7791
7791
|
${candidateList}`;
|
|
7792
7792
|
try {
|
|
7793
|
-
const
|
|
7793
|
+
const instructionText = `You are a memory linking system. Analyze the new memory and suggest relationships to existing memories.
|
|
7794
7794
|
|
|
7795
7795
|
Link types:
|
|
7796
7796
|
- follows: This memory is a continuation or next step (e.g., decision follows discussion)
|
|
@@ -7813,7 +7813,7 @@ Respond with valid JSON matching this schema:
|
|
|
7813
7813
|
try {
|
|
7814
7814
|
const localResponse = await this.localLlm.chatCompletion(
|
|
7815
7815
|
[
|
|
7816
|
-
{ role: "system", content:
|
|
7816
|
+
{ role: "system", content: instructionText },
|
|
7817
7817
|
{ role: "user", content: input }
|
|
7818
7818
|
],
|
|
7819
7819
|
{
|
|
@@ -7842,7 +7842,7 @@ Respond with valid JSON matching this schema:
|
|
|
7842
7842
|
if (!this.shouldUseDirectClient) {
|
|
7843
7843
|
const fallbackResponse = await this.fallbackLlm.chatCompletion(
|
|
7844
7844
|
[
|
|
7845
|
-
{ role: "system", content:
|
|
7845
|
+
{ role: "system", content: instructionText },
|
|
7846
7846
|
{ role: "user", content: input }
|
|
7847
7847
|
],
|
|
7848
7848
|
this.withGatewayAgent({ temperature: 0.3, maxTokens: 2048 })
|
|
@@ -7858,7 +7858,7 @@ Respond with valid JSON matching this schema:
|
|
|
7858
7858
|
const response = await this.client.chat.completions.create({
|
|
7859
7859
|
model: this.config.model,
|
|
7860
7860
|
messages: [
|
|
7861
|
-
{ role: "system", content:
|
|
7861
|
+
{ role: "system", content: instructionText },
|
|
7862
7862
|
{ role: "user", content: input }
|
|
7863
7863
|
],
|
|
7864
7864
|
...buildChatCompletionTokenLimit(this.config.model, 2048, {
|
|
@@ -7885,7 +7885,7 @@ Respond with valid JSON matching this schema:
|
|
|
7885
7885
|
}
|
|
7886
7886
|
const memoryContext = formatDaySummaryMemories(memories);
|
|
7887
7887
|
if (memoryContext.length === 0) return null;
|
|
7888
|
-
const
|
|
7888
|
+
const instructionText = await loadDaySummaryPrompt();
|
|
7889
7889
|
let extensionsFooter = "";
|
|
7890
7890
|
try {
|
|
7891
7891
|
extensionsFooter = await buildExtensionsFooterForSummary(this.config);
|
|
@@ -7903,7 +7903,7 @@ ${extensionsFooter}` : ""}`;
|
|
|
7903
7903
|
try {
|
|
7904
7904
|
const localResponse = await this.localLlm.chatCompletion(
|
|
7905
7905
|
[
|
|
7906
|
-
{ role: "system", content: `${
|
|
7906
|
+
{ role: "system", content: `${instructionText}
|
|
7907
7907
|
|
|
7908
7908
|
Return valid JSON only.` },
|
|
7909
7909
|
{ role: "user", content: userPrompt }
|
|
@@ -7940,7 +7940,7 @@ Return valid JSON only.` },
|
|
|
7940
7940
|
startedAt,
|
|
7941
7941
|
DaySummaryResultSchema,
|
|
7942
7942
|
[
|
|
7943
|
-
{ role: "system", content: `${
|
|
7943
|
+
{ role: "system", content: `${instructionText}
|
|
7944
7944
|
|
|
7945
7945
|
Return valid JSON only.` },
|
|
7946
7946
|
{ role: "user", content: userPrompt }
|
|
@@ -7958,7 +7958,7 @@ Return valid JSON only.` },
|
|
|
7958
7958
|
try {
|
|
7959
7959
|
const response = await this.client.responses.create({
|
|
7960
7960
|
model: this.config.model,
|
|
7961
|
-
instructions: `${
|
|
7961
|
+
instructions: `${instructionText}
|
|
7962
7962
|
|
|
7963
7963
|
Return valid JSON only.`,
|
|
7964
7964
|
input: userPrompt,
|
|
@@ -7988,7 +7988,7 @@ Return valid JSON only.`,
|
|
|
7988
7988
|
const memoryList = memories.map((m) => `[${m.id}] (${m.category}, ${m.created.slice(0, 10)})
|
|
7989
7989
|
${m.content}`).join("\n\n");
|
|
7990
7990
|
try {
|
|
7991
|
-
const
|
|
7991
|
+
const instructionText = `You are a memory summarization system. You are given a batch of old memories that need to be compressed into a summary.
|
|
7992
7992
|
|
|
7993
7993
|
Your task:
|
|
7994
7994
|
1. Write a concise summary paragraph (2-4 sentences) capturing the essence of these memories
|
|
@@ -8011,7 +8011,7 @@ Respond with valid JSON matching this schema:
|
|
|
8011
8011
|
try {
|
|
8012
8012
|
const localResponse = await this.localLlm.chatCompletion(
|
|
8013
8013
|
[
|
|
8014
|
-
{ role: "system", content:
|
|
8014
|
+
{ role: "system", content: instructionText },
|
|
8015
8015
|
{ role: "user", content: `Summarize these ${memories.length} memories:
|
|
8016
8016
|
|
|
8017
8017
|
${memoryList}` }
|
|
@@ -8044,7 +8044,7 @@ ${memoryList}` }
|
|
|
8044
8044
|
if (!this.shouldUseDirectClient) {
|
|
8045
8045
|
const fallbackResponse = await this.fallbackLlm.chatCompletion(
|
|
8046
8046
|
[
|
|
8047
|
-
{ role: "system", content:
|
|
8047
|
+
{ role: "system", content: instructionText },
|
|
8048
8048
|
{ role: "user", content: `Summarize these ${memories.length} memories:
|
|
8049
8049
|
|
|
8050
8050
|
${memoryList}` }
|
|
@@ -8062,7 +8062,7 @@ ${memoryList}` }
|
|
|
8062
8062
|
const response = await this.client.chat.completions.create({
|
|
8063
8063
|
model: this.config.model,
|
|
8064
8064
|
messages: [
|
|
8065
|
-
{ role: "system", content:
|
|
8065
|
+
{ role: "system", content: instructionText },
|
|
8066
8066
|
{ role: "user", content: `Summarize these ${memories.length} memories:
|
|
8067
8067
|
|
|
8068
8068
|
${memoryList}` }
|
|
@@ -34245,11 +34245,11 @@ var Orchestrator = class _Orchestrator {
|
|
|
34245
34245
|
});
|
|
34246
34246
|
if (config.lcmEnabled) {
|
|
34247
34247
|
const summarizeFn = async (text, targetTokens, aggressive) => {
|
|
34248
|
-
const
|
|
34248
|
+
const instructionText = aggressive ? `Compress the following into bullet points. One bullet per distinct fact or decision. Maximum ${targetTokens} tokens total. No prose.` : `Compress the following conversation segment into a dense summary. Preserve: decisions made, code artifacts mentioned, errors encountered, open questions, and any commitments or next-steps. Omit: pleasantries, restatements, and anything the agent would not need to recall later. Output a single paragraph, maximum ${targetTokens} tokens.`;
|
|
34249
34249
|
try {
|
|
34250
34250
|
const result = await this.localLlm.chatCompletion(
|
|
34251
34251
|
[
|
|
34252
|
-
{ role: "system", content:
|
|
34252
|
+
{ role: "system", content: instructionText },
|
|
34253
34253
|
{ role: "user", content: text.slice(0, 12e3) }
|
|
34254
34254
|
],
|
|
34255
34255
|
{
|
|
@@ -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"
|