diff --git a/packages/typescript/src/openclaw/index.ts b/packages/typescript/src/openclaw/index.ts
index da8269ea..791787be 100644
--- a/packages/typescript/src/openclaw/index.ts
+++ b/packages/typescript/src/openclaw/index.ts
@@ -20,9 +20,11 @@ const plugin: OpenClawPluginDefinition = {
const runtime = new OpenClawReMeRuntime(client, config, api.logger);
registerOpenClawTools(api, client, config);
- if (config.autoRecall) {
+ if (config.autoRecall || config.autoCapture) {
api.on("before_agent_start", async (event, context) => {
if (!capturesTrigger(context.trigger)) return;
+ runtime.rememberPrompt(event.prompt, context);
+ if (!config.autoRecall) return;
const query = event.prompt.trim();
if (!query) return;
const result = await client.search(query, {
@@ -41,7 +43,8 @@ const plugin: OpenClawPluginDefinition = {
}
api.on("agent_end", (event, context) => {
- if (event.success) runtime.capture(event.messages, context);
+ const prompt = runtime.takePrompt(context);
+ if (event.success) runtime.capture(event.messages, context, prompt);
});
api.registerService({
diff --git a/packages/typescript/src/openclaw/messages.ts b/packages/typescript/src/openclaw/messages.ts
index 82738d92..b7fd0b96 100644
--- a/packages/typescript/src/openclaw/messages.ts
+++ b/packages/typescript/src/openclaw/messages.ts
@@ -19,6 +19,7 @@ export function openClawSessionId(value: string): string {
export function captureLastTurn(
messages: unknown[],
sessionId: string,
+ userPrompt?: string,
): ReMeMessage[] {
let assistant: ReMeMessage | null = null;
for (let index = messages.length - 1; index >= 0; index -= 1) {
@@ -29,7 +30,13 @@ export function captureLastTurn(
continue;
}
if (assistant && record.role === "user") {
- const user = normalizeMessage(record, "user", sessionId, index);
+ const user = normalizeMessage(
+ record,
+ "user",
+ sessionId,
+ index,
+ userPrompt,
+ );
return user ? [user, assistant] : [];
}
}
@@ -41,8 +48,10 @@ function normalizeMessage(
role: "user" | "assistant",
sessionId: string,
index: number,
+ textOverride?: string,
): ReMeMessage | null {
- const text = stripAutoRecallContext(messageText(value.content));
+ const text =
+ textOverride?.trim() || stripAutoRecallContext(messageText(value.content));
if (!text) return null;
const nativeId =
typeof value.id === "string" && value.id ? value.id : `${index}\n${text}`;
@@ -58,11 +67,12 @@ function normalizeMessage(
function stripAutoRecallContext(value: string): string {
const opening = '';
- if (!value.startsWith(opening)) return value;
+ const start = value.indexOf(opening);
+ if (start === -1) return value;
const closing = "";
- const end = value.indexOf(closing, opening.length);
+ const end = value.indexOf(closing, start + opening.length);
if (end === -1) return value;
- return value.slice(end + closing.length).trim();
+ return `${value.slice(0, start)}${value.slice(end + closing.length)}`.trim();
}
function messageText(content: unknown): string {
diff --git a/packages/typescript/src/openclaw/runtime.ts b/packages/typescript/src/openclaw/runtime.ts
index 6a6fb9a7..6b26bc1b 100644
--- a/packages/typescript/src/openclaw/runtime.ts
+++ b/packages/typescript/src/openclaw/runtime.ts
@@ -14,6 +14,7 @@ export interface OpenClawAgentContext {
export class OpenClawReMeRuntime {
private writes = Promise.resolve();
private controller = new AbortController();
+ private readonly prompts = new Map();
constructor(
readonly client: ReMeClientLike,
@@ -21,7 +22,26 @@ export class OpenClawReMeRuntime {
readonly logger: LoggerLike,
) {}
- capture(messages: unknown[], context: OpenClawAgentContext): void {
+ rememberPrompt(prompt: string, context: OpenClawAgentContext): void {
+ if (!this.config.autoCapture || !capturesTrigger(context.trigger)) return;
+ const key = promptKey(context);
+ const text = prompt.trim();
+ if (key && text) this.prompts.set(key, text);
+ }
+
+ takePrompt(context: OpenClawAgentContext): string | undefined {
+ const key = promptKey(context);
+ if (!key) return undefined;
+ const prompt = this.prompts.get(key);
+ this.prompts.delete(key);
+ return prompt;
+ }
+
+ capture(
+ messages: unknown[],
+ context: OpenClawAgentContext,
+ prompt?: string,
+ ): void {
if (!this.config.autoCapture || !capturesTrigger(context.trigger)) return;
const nativeSessionId = context.sessionId || context.sessionKey;
if (!nativeSessionId) {
@@ -33,7 +53,7 @@ export class OpenClawReMeRuntime {
const sessionId = openClawSessionId(
`${context.agentId || "default"}\n${nativeSessionId}`,
);
- const captured = captureLastTurn(messages, sessionId);
+ const captured = captureLastTurn(messages, sessionId, prompt);
if (captured.length !== 2) return;
this.writes = this.writes
.then(async () => {
@@ -71,3 +91,10 @@ export class OpenClawReMeRuntime {
function capturesTrigger(trigger: string | undefined): boolean {
return trigger === undefined || trigger === "user";
}
+
+function promptKey(context: OpenClawAgentContext): string {
+ const nativeSessionId = context.sessionId || context.sessionKey;
+ return nativeSessionId
+ ? `${context.agentId || "default"}\n${nativeSessionId}`
+ : "";
+}
diff --git a/packages/typescript/tests/openclaw.test.mjs b/packages/typescript/tests/openclaw.test.mjs
index 98710099..2966c67c 100644
--- a/packages/typescript/tests/openclaw.test.mjs
+++ b/packages/typescript/tests/openclaw.test.mjs
@@ -79,6 +79,30 @@ test("removes recalled context while preserving the current OpenClaw prompt", ()
);
});
+test("uses the original prompt when other plugins prepend context around recall", () => {
+ const messages = captureLastTurn(
+ [
+ {
+ role: "user",
+ content:
+ "other plugin context\n\n" +
+ '\nremembered deployment\n\n\n' +
+ "another plugin context\n\nremember blue",
+ },
+ { role: "assistant", content: "noted" },
+ ],
+ "session",
+ "remember blue",
+ );
+ assert.deepEqual(
+ messages.map((message) => [message.role, message.content[0].text]),
+ [
+ ["user", "remember blue"],
+ ["assistant", "noted"],
+ ],
+ );
+});
+
test("registers OpenClaw recall, capture, tool, and shutdown lifecycle", async () => {
const originalFetch = globalThis.fetch;
const calls = [];
@@ -114,7 +138,7 @@ test("registers OpenClaw recall, capture, tool, and shutdown lifecycle", async (
assert.equal(tools[0].name, "reme_search");
const recalled = await hooks.get("before_agent_start")(
{ prompt: "deployment" },
- { trigger: "user" },
+ { trigger: "user", agentId: "main", sessionId: "session-1" },
);
assert.match(recalled.prependContext, /remembered deployment/);
@@ -122,7 +146,13 @@ test("registers OpenClaw recall, capture, tool, and shutdown lifecycle", async (
{
success: true,
messages: [
- { role: "user", content: "remember blue" },
+ {
+ role: "user",
+ content:
+ "other plugin context\n\n" +
+ recalled.prependContext +
+ "\n\ndeployment",
+ },
{ role: "assistant", content: "noted" },
],
},
@@ -134,6 +164,7 @@ test("registers OpenClaw recall, capture, tool, and shutdown lifecycle", async (
calls.map((call) => call.url),
["http://127.0.0.1:2333/search", "http://127.0.0.1:2333/auto_memory"],
);
+ assert.equal(calls[1].body.messages[0].content[0].text, "deployment");
} finally {
globalThis.fetch = originalFetch;
}