Merge pull request #1084 from thedotmack/fix/openclaw-plugin-project-query-and-feed-bottoken
fix(openclaw): fix MEMORY.md project query mismatch and add feed botToken support
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
"id": "claude-mem",
|
"id": "claude-mem",
|
||||||
"name": "Claude-Mem (Persistent Memory)",
|
"name": "Claude-Mem (Persistent Memory)",
|
||||||
"description": "Official OpenClaw plugin for Claude-Mem. Records observations from embedded runner sessions and streams them to messaging channels.",
|
"description": "Official OpenClaw plugin for Claude-Mem. Records observations from embedded runner sessions and streams them to messaging channels.",
|
||||||
"kind": "integration",
|
"kind": "memory",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "thedotmack",
|
"author": "thedotmack",
|
||||||
"homepage": "https://claude-mem.com",
|
"homepage": "https://claude-mem.com",
|
||||||
@@ -41,6 +41,10 @@
|
|||||||
"to": {
|
"to": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Target chat/user ID to send observations to"
|
"description": "Target chat/user ID to send observations to"
|
||||||
|
},
|
||||||
|
"botToken": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional dedicated Telegram bot token for the feed (bypasses gateway channel)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-11
@@ -1,5 +1,5 @@
|
|||||||
import { writeFile } from "fs/promises";
|
import { writeFile } from "fs/promises";
|
||||||
import { basename, join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
// Minimal type declarations for the OpenClaw Plugin SDK.
|
// Minimal type declarations for the OpenClaw Plugin SDK.
|
||||||
// These match the real OpenClawPluginApi provided by the gateway at runtime.
|
// These match the real OpenClawPluginApi provided by the gateway at runtime.
|
||||||
@@ -164,6 +164,7 @@ interface ClaudeMemPluginConfig {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
channel?: string;
|
channel?: string;
|
||||||
to?: string;
|
to?: string;
|
||||||
|
botToken?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,12 +306,44 @@ const CHANNEL_SEND_MAP: Record<string, { namespace: string; functionName: string
|
|||||||
line: { namespace: "line", functionName: "sendMessageLine" },
|
line: { namespace: "line", functionName: "sendMessageLine" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function sendDirectTelegram(
|
||||||
|
botToken: string,
|
||||||
|
chatId: string,
|
||||||
|
text: string,
|
||||||
|
logger: PluginLogger
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
chat_id: chatId,
|
||||||
|
text,
|
||||||
|
parse_mode: "Markdown",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const body = await response.text();
|
||||||
|
logger.warn(`[claude-mem] Direct Telegram send failed (${response.status}): ${body}`);
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
logger.warn(`[claude-mem] Direct Telegram send error: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function sendToChannel(
|
function sendToChannel(
|
||||||
api: OpenClawPluginApi,
|
api: OpenClawPluginApi,
|
||||||
channel: string,
|
channel: string,
|
||||||
to: string,
|
to: string,
|
||||||
text: string
|
text: string,
|
||||||
|
botToken?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
// If a dedicated bot token is provided for Telegram, send directly
|
||||||
|
if (botToken && channel === "telegram") {
|
||||||
|
return sendDirectTelegram(botToken, to, text, api.logger);
|
||||||
|
}
|
||||||
|
|
||||||
const mapping = CHANNEL_SEND_MAP[channel];
|
const mapping = CHANNEL_SEND_MAP[channel];
|
||||||
if (!mapping) {
|
if (!mapping) {
|
||||||
api.logger.warn(`[claude-mem] Unsupported channel type: ${channel}`);
|
api.logger.warn(`[claude-mem] Unsupported channel type: ${channel}`);
|
||||||
@@ -346,7 +379,8 @@ async function connectToSSEStream(
|
|||||||
channel: string,
|
channel: string,
|
||||||
to: string,
|
to: string,
|
||||||
abortController: AbortController,
|
abortController: AbortController,
|
||||||
setConnectionState: (state: ConnectionState) => void
|
setConnectionState: (state: ConnectionState) => void,
|
||||||
|
botToken?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let backoffMs = 1000;
|
let backoffMs = 1000;
|
||||||
const maxBackoffMs = 30000;
|
const maxBackoffMs = 30000;
|
||||||
@@ -407,7 +441,7 @@ async function connectToSSEStream(
|
|||||||
if (parsed.type === "new_observation" && parsed.observation) {
|
if (parsed.type === "new_observation" && parsed.observation) {
|
||||||
const event = parsed as SSENewObservationEvent;
|
const event = parsed as SSENewObservationEvent;
|
||||||
const message = formatObservationMessage(event.observation);
|
const message = formatObservationMessage(event.observation);
|
||||||
await sendToChannel(api, channel, to, message);
|
await sendToChannel(api, channel, to, message, botToken);
|
||||||
}
|
}
|
||||||
} catch (parseError: unknown) {
|
} catch (parseError: unknown) {
|
||||||
const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
|
const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
|
||||||
@@ -464,12 +498,16 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|||||||
return sessionIds.get(key)!;
|
return sessionIds.get(key)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncMemoryToWorkspace(workspaceDir: string): Promise<void> {
|
async function syncMemoryToWorkspace(workspaceDir: string, ctx?: EventContext): Promise<void> {
|
||||||
// Derive project name from workspace directory (matches Claude Code's getProjectName logic)
|
// Include both the base project and agent-scoped project (e.g. "openclaw" + "openclaw-main")
|
||||||
const workspaceProject = basename(workspaceDir) || baseProjectName;
|
const projects = [baseProjectName];
|
||||||
|
const agentProject = ctx ? getProjectName(ctx) : null;
|
||||||
|
if (agentProject && agentProject !== baseProjectName) {
|
||||||
|
projects.push(agentProject);
|
||||||
|
}
|
||||||
const contextText = await workerGetText(
|
const contextText = await workerGetText(
|
||||||
workerPort,
|
workerPort,
|
||||||
`/api/context/inject?projects=${encodeURIComponent(workspaceProject)}`,
|
`/api/context/inject?projects=${encodeURIComponent(projects.join(","))}`,
|
||||||
api.logger
|
api.logger
|
||||||
);
|
);
|
||||||
if (contextText && contextText.trim().length > 0) {
|
if (contextText && contextText.trim().length > 0) {
|
||||||
@@ -547,7 +585,7 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|||||||
|
|
||||||
// Sync MEMORY.md before agent runs (provides context to agent)
|
// Sync MEMORY.md before agent runs (provides context to agent)
|
||||||
if (syncMemoryFile && ctx.workspaceDir) {
|
if (syncMemoryFile && ctx.workspaceDir) {
|
||||||
await syncMemoryToWorkspace(ctx.workspaceDir);
|
await syncMemoryToWorkspace(ctx.workspaceDir, ctx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -582,7 +620,7 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|||||||
|
|
||||||
const workspaceDir = ctx.workspaceDir || workspaceDirsBySessionKey.get(ctx.sessionKey || "default");
|
const workspaceDir = ctx.workspaceDir || workspaceDirsBySessionKey.get(ctx.sessionKey || "default");
|
||||||
if (syncMemoryFile && workspaceDir) {
|
if (syncMemoryFile && workspaceDir) {
|
||||||
syncMemoryToWorkspace(workspaceDir);
|
syncMemoryToWorkspace(workspaceDir, ctx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -681,7 +719,8 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|||||||
feedConfig.channel,
|
feedConfig.channel,
|
||||||
feedConfig.to,
|
feedConfig.to,
|
||||||
sseAbortController,
|
sseAbortController,
|
||||||
(state) => { connectionState = state; }
|
(state) => { connectionState = state; },
|
||||||
|
feedConfig.botToken
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
stop: async (_ctx) => {
|
stop: async (_ctx) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user