|
1 | 1 | import { writeFile } from "fs/promises"; |
2 | | -import { basename, join } from "path"; |
| 2 | +import { join } from "path"; |
3 | 3 |
|
4 | 4 | // Minimal type declarations for the OpenClaw Plugin SDK. |
5 | 5 | // These match the real OpenClawPluginApi provided by the gateway at runtime. |
@@ -164,6 +164,7 @@ interface ClaudeMemPluginConfig { |
164 | 164 | enabled?: boolean; |
165 | 165 | channel?: string; |
166 | 166 | to?: string; |
| 167 | + botToken?: string; |
167 | 168 | }; |
168 | 169 | } |
169 | 170 |
|
@@ -305,12 +306,44 @@ const CHANNEL_SEND_MAP: Record<string, { namespace: string; functionName: string |
305 | 306 | line: { namespace: "line", functionName: "sendMessageLine" }, |
306 | 307 | }; |
307 | 308 |
|
| 309 | +async function sendDirectTelegram( |
| 310 | + botToken: string, |
| 311 | + chatId: string, |
| 312 | + text: string, |
| 313 | + logger: PluginLogger |
| 314 | +): Promise<void> { |
| 315 | + try { |
| 316 | + const response = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, { |
| 317 | + method: "POST", |
| 318 | + headers: { "Content-Type": "application/json" }, |
| 319 | + body: JSON.stringify({ |
| 320 | + chat_id: chatId, |
| 321 | + text, |
| 322 | + parse_mode: "Markdown", |
| 323 | + }), |
| 324 | + }); |
| 325 | + if (!response.ok) { |
| 326 | + const body = await response.text(); |
| 327 | + logger.warn(`[claude-mem] Direct Telegram send failed (${response.status}): ${body}`); |
| 328 | + } |
| 329 | + } catch (error: unknown) { |
| 330 | + const message = error instanceof Error ? error.message : String(error); |
| 331 | + logger.warn(`[claude-mem] Direct Telegram send error: ${message}`); |
| 332 | + } |
| 333 | +} |
| 334 | + |
308 | 335 | function sendToChannel( |
309 | 336 | api: OpenClawPluginApi, |
310 | 337 | channel: string, |
311 | 338 | to: string, |
312 | | - text: string |
| 339 | + text: string, |
| 340 | + botToken?: string |
313 | 341 | ): Promise<void> { |
| 342 | + // If a dedicated bot token is provided for Telegram, send directly |
| 343 | + if (botToken && channel === "telegram") { |
| 344 | + return sendDirectTelegram(botToken, to, text, api.logger); |
| 345 | + } |
| 346 | + |
314 | 347 | const mapping = CHANNEL_SEND_MAP[channel]; |
315 | 348 | if (!mapping) { |
316 | 349 | api.logger.warn(`[claude-mem] Unsupported channel type: ${channel}`); |
@@ -346,7 +379,8 @@ async function connectToSSEStream( |
346 | 379 | channel: string, |
347 | 380 | to: string, |
348 | 381 | abortController: AbortController, |
349 | | - setConnectionState: (state: ConnectionState) => void |
| 382 | + setConnectionState: (state: ConnectionState) => void, |
| 383 | + botToken?: string |
350 | 384 | ): Promise<void> { |
351 | 385 | let backoffMs = 1000; |
352 | 386 | const maxBackoffMs = 30000; |
@@ -407,7 +441,7 @@ async function connectToSSEStream( |
407 | 441 | if (parsed.type === "new_observation" && parsed.observation) { |
408 | 442 | const event = parsed as SSENewObservationEvent; |
409 | 443 | const message = formatObservationMessage(event.observation); |
410 | | - await sendToChannel(api, channel, to, message); |
| 444 | + await sendToChannel(api, channel, to, message, botToken); |
411 | 445 | } |
412 | 446 | } catch (parseError: unknown) { |
413 | 447 | const errorMessage = parseError instanceof Error ? parseError.message : String(parseError); |
@@ -464,12 +498,16 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void { |
464 | 498 | return sessionIds.get(key)!; |
465 | 499 | } |
466 | 500 |
|
467 | | - async function syncMemoryToWorkspace(workspaceDir: string): Promise<void> { |
468 | | - // Derive project name from workspace directory (matches Claude Code's getProjectName logic) |
469 | | - const workspaceProject = basename(workspaceDir) || baseProjectName; |
| 501 | + async function syncMemoryToWorkspace(workspaceDir: string, ctx?: EventContext): Promise<void> { |
| 502 | + // Include both the base project and agent-scoped project (e.g. "openclaw" + "openclaw-main") |
| 503 | + const projects = [baseProjectName]; |
| 504 | + const agentProject = ctx ? getProjectName(ctx) : null; |
| 505 | + if (agentProject && agentProject !== baseProjectName) { |
| 506 | + projects.push(agentProject); |
| 507 | + } |
470 | 508 | const contextText = await workerGetText( |
471 | 509 | workerPort, |
472 | | - `/api/context/inject?projects=${encodeURIComponent(workspaceProject)}`, |
| 510 | + `/api/context/inject?projects=${encodeURIComponent(projects.join(","))}`, |
473 | 511 | api.logger |
474 | 512 | ); |
475 | 513 | if (contextText && contextText.trim().length > 0) { |
@@ -547,7 +585,7 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void { |
547 | 585 |
|
548 | 586 | // Sync MEMORY.md before agent runs (provides context to agent) |
549 | 587 | if (syncMemoryFile && ctx.workspaceDir) { |
550 | | - await syncMemoryToWorkspace(ctx.workspaceDir); |
| 588 | + await syncMemoryToWorkspace(ctx.workspaceDir, ctx); |
551 | 589 | } |
552 | 590 | }); |
553 | 591 |
|
@@ -582,7 +620,7 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void { |
582 | 620 |
|
583 | 621 | const workspaceDir = ctx.workspaceDir || workspaceDirsBySessionKey.get(ctx.sessionKey || "default"); |
584 | 622 | if (syncMemoryFile && workspaceDir) { |
585 | | - syncMemoryToWorkspace(workspaceDir); |
| 623 | + syncMemoryToWorkspace(workspaceDir, ctx); |
586 | 624 | } |
587 | 625 | }); |
588 | 626 |
|
@@ -681,7 +719,8 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void { |
681 | 719 | feedConfig.channel, |
682 | 720 | feedConfig.to, |
683 | 721 | sseAbortController, |
684 | | - (state) => { connectionState = state; } |
| 722 | + (state) => { connectionState = state; }, |
| 723 | + feedConfig.botToken |
685 | 724 | ); |
686 | 725 | }, |
687 | 726 | stop: async (_ctx) => { |
|
0 commit comments