@botcord/openclaw-plugin 0.0.6 → 0.0.7
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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/inbound.ts +89 -13
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/inbound.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { getBotCordRuntime } from "./runtime.js";
|
|
6
6
|
import { resolveAccountConfig } from "./config.js";
|
|
7
7
|
import { buildSessionKey } from "./session-key.js";
|
|
8
|
+
import { loadSessionStore } from "openclaw/plugin-sdk/mattermost";
|
|
8
9
|
import type { InboxMessage, MessageType } from "./types.js";
|
|
9
10
|
|
|
10
11
|
// Envelope types that count as notifications rather than normal messages
|
|
@@ -256,12 +257,9 @@ function parseSessionKeyDeliveryContext(sessionKey: string): DeliveryContext | u
|
|
|
256
257
|
const parts = sessionKey.split(":");
|
|
257
258
|
if (parts.length < 5 || parts[0] !== "agent") return undefined;
|
|
258
259
|
|
|
259
|
-
const KNOWN_CHANNELS = new Set([
|
|
260
|
-
"telegram", "discord", "slack", "whatsapp", "signal", "imessage",
|
|
261
|
-
]);
|
|
262
260
|
const agentName = parts[1]; // e.g. "pm"
|
|
263
261
|
const channel = parts[2]; // e.g. "telegram"
|
|
264
|
-
if (!
|
|
262
|
+
if (!channel) return undefined;
|
|
265
263
|
|
|
266
264
|
const peerId = parts.slice(4).join(":"); // handle colons in id
|
|
267
265
|
if (!peerId) return undefined;
|
|
@@ -273,6 +271,68 @@ function parseSessionKeyDeliveryContext(sessionKey: string): DeliveryContext | u
|
|
|
273
271
|
};
|
|
274
272
|
}
|
|
275
273
|
|
|
274
|
+
function parseSessionStoreDeliveryContext(
|
|
275
|
+
core: ReturnType<typeof getBotCordRuntime>,
|
|
276
|
+
cfg: any,
|
|
277
|
+
sessionKey: string,
|
|
278
|
+
): DeliveryContext[] {
|
|
279
|
+
try {
|
|
280
|
+
const storePath = core.channel.session.resolveStorePath(cfg);
|
|
281
|
+
if (!storePath) return [];
|
|
282
|
+
|
|
283
|
+
const store = loadSessionStore(storePath);
|
|
284
|
+
const trimmedKey = sessionKey.trim();
|
|
285
|
+
const normalizedKey = trimmedKey.toLowerCase();
|
|
286
|
+
let existing = store[normalizedKey] ?? store[trimmedKey];
|
|
287
|
+
let existingUpdatedAt = existing?.updatedAt ?? 0;
|
|
288
|
+
|
|
289
|
+
// Legacy stores may contain differently-cased keys for the same session.
|
|
290
|
+
// Prefer the most recently updated matching entry.
|
|
291
|
+
for (const [candidateKey, candidateEntry] of Object.entries(store)) {
|
|
292
|
+
if (candidateKey.toLowerCase() !== normalizedKey) continue;
|
|
293
|
+
const candidateUpdatedAt = candidateEntry?.updatedAt ?? 0;
|
|
294
|
+
if (!existing || candidateUpdatedAt > existingUpdatedAt) {
|
|
295
|
+
existing = candidateEntry;
|
|
296
|
+
existingUpdatedAt = candidateUpdatedAt;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!existing) return [];
|
|
300
|
+
|
|
301
|
+
const lastRoute = {
|
|
302
|
+
channel: existing.lastChannel,
|
|
303
|
+
to: existing.lastTo,
|
|
304
|
+
accountId: existing.lastAccountId,
|
|
305
|
+
threadId: existing.lastThreadId ?? existing.origin?.threadId,
|
|
306
|
+
};
|
|
307
|
+
const candidates: DeliveryContext[] = [];
|
|
308
|
+
for (const ctx of [existing.deliveryContext, lastRoute]) {
|
|
309
|
+
if (!ctx?.channel || !ctx?.to) continue;
|
|
310
|
+
const normalized: DeliveryContext = {
|
|
311
|
+
channel: String(ctx.channel),
|
|
312
|
+
to: String(ctx.to),
|
|
313
|
+
};
|
|
314
|
+
if (ctx.accountId != null) normalized.accountId = String(ctx.accountId);
|
|
315
|
+
if (ctx.threadId != null) normalized.threadId = String(ctx.threadId);
|
|
316
|
+
candidates.push(normalized);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Deduplicate identical contexts while preserving order.
|
|
320
|
+
const seen = new Set<string>();
|
|
321
|
+
return candidates.filter((ctx) => {
|
|
322
|
+
const key = `${ctx.channel}|${ctx.to}|${ctx.accountId ?? ""}|${ctx.threadId ?? ""}`;
|
|
323
|
+
if (seen.has(key)) return false;
|
|
324
|
+
seen.add(key);
|
|
325
|
+
return true;
|
|
326
|
+
});
|
|
327
|
+
} catch (err: any) {
|
|
328
|
+
console.warn(
|
|
329
|
+
`[botcord] notifySession ${sessionKey}: failed to read deliveryContext from session store:`,
|
|
330
|
+
err?.message ?? err,
|
|
331
|
+
);
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
276
336
|
/** Channel name → runtime send function dispatcher. */
|
|
277
337
|
type ChannelSendFn = (to: string, text: string, opts: Record<string, unknown>) => Promise<unknown>;
|
|
278
338
|
|
|
@@ -293,8 +353,8 @@ function resolveChannelSendFn(
|
|
|
293
353
|
|
|
294
354
|
/**
|
|
295
355
|
* Deliver a notification message directly to the channel associated with
|
|
296
|
-
* the target session
|
|
297
|
-
*
|
|
356
|
+
* the target session. Prefer deriving target from session key; fallback to
|
|
357
|
+
* session store deliveryContext when direct routing is unavailable.
|
|
298
358
|
*/
|
|
299
359
|
export async function deliverNotification(
|
|
300
360
|
core: ReturnType<typeof getBotCordRuntime>,
|
|
@@ -302,19 +362,35 @@ export async function deliverNotification(
|
|
|
302
362
|
sessionKey: string,
|
|
303
363
|
text: string,
|
|
304
364
|
): Promise<void> {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
365
|
+
const deliveryFromKey = parseSessionKeyDeliveryContext(sessionKey);
|
|
366
|
+
const storeCandidates = parseSessionStoreDeliveryContext(core, cfg, sessionKey);
|
|
367
|
+
const candidates = [
|
|
368
|
+
...(deliveryFromKey ? [deliveryFromKey] : []),
|
|
369
|
+
...storeCandidates,
|
|
370
|
+
];
|
|
371
|
+
let delivery: DeliveryContext | undefined;
|
|
372
|
+
let sendFn: ChannelSendFn | undefined;
|
|
373
|
+
|
|
374
|
+
for (const candidate of candidates) {
|
|
375
|
+
if (!delivery) delivery = candidate;
|
|
376
|
+
const resolved = resolveChannelSendFn(core, candidate.channel);
|
|
377
|
+
if (resolved) {
|
|
378
|
+
delivery = candidate;
|
|
379
|
+
sendFn = resolved;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
310
384
|
if (!delivery) {
|
|
311
385
|
console.warn(
|
|
312
|
-
`[botcord] notifySession ${sessionKey}: cannot derive delivery target from session key — skipping notification`,
|
|
386
|
+
`[botcord] notifySession ${sessionKey}: cannot derive delivery target from session key or session store — skipping notification`,
|
|
313
387
|
);
|
|
314
388
|
return;
|
|
315
389
|
}
|
|
316
390
|
|
|
317
|
-
|
|
391
|
+
if (!sendFn) {
|
|
392
|
+
sendFn = resolveChannelSendFn(core, delivery.channel);
|
|
393
|
+
}
|
|
318
394
|
if (!sendFn) {
|
|
319
395
|
console.warn(
|
|
320
396
|
`[botcord] unsupported notify channel "${delivery.channel}" — skipping notification`,
|