fix(nostr): replace non-existent handleInboundMessage API with dispatchReplyFromConfig

Fixes #4547

The Nostr plugin was calling runtime.channel.reply.handleInboundMessage(),
which doesn't exist in the PluginRuntime API. This caused DM processing to
fail with 'handleInboundMessage is not a function'.

Changed to use the correct message dispatch pattern:
1. Build proper MsgContext with finalizeInboundContext()
2. Create reply dispatcher with createReplyDispatcherWithTyping()
3. Dispatch replies with dispatchReplyFromConfig()

This matches the pattern used by other channel extensions like Matrix
and Discord.

The fix ensures:
- DMs are properly routed through the agent pipeline
- Session state is recorded correctly
- Reply text is formatted with markdown table conversion
- Human delay and typing indicators work as expected
This commit is contained in:
spiceoogway 2026-01-30 08:51:46 -05:00
parent da71eaebd2
commit 35576d1481

View File

@ -221,18 +221,105 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
onMessage: async (senderPubkey, text, reply) => {
ctx.log?.debug(`[${account.accountId}] DM from ${senderPubkey}: ${text.slice(0, 50)}...`);
// Forward to OpenClaw's message pipeline
await runtime.channel.reply.handleInboundMessage({
// Load current config for routing
const cfg = runtime.config.loadConfig();
// Resolve agent route for this message
const route = runtime.channel.routing.resolveAgentRoute({
cfg,
channel: "nostr",
accountId: account.accountId,
senderId: senderPubkey,
chatType: "direct",
chatId: senderPubkey, // For DMs, chatId is the sender's pubkey
text,
reply: async (responseText: string) => {
await reply(responseText);
senderId: senderPubkey,
});
// Build session key for this conversation
const sessionKey = `nostr:${account.accountId}:direct:${senderPubkey}`;
// Format the nostr prefix for sender
const from = `nostr:${senderPubkey}`;
const to = `nostr:${account.publicKey}`;
// Build the message context
const msgContext = runtime.channel.reply.finalizeInboundContext({
Body: text,
RawBody: text,
CommandBody: text,
From: from,
To: to,
SessionKey: sessionKey,
AccountId: account.accountId,
ChatType: "direct" as const,
ConversationLabel: `Nostr DM`,
SenderName: senderPubkey.slice(0, 8),
SenderId: senderPubkey,
Provider: "nostr" as const,
Surface: "nostr" as const,
Timestamp: Date.now(),
CommandAuthorized: true,
CommandSource: "text" as const,
OriginatingChannel: "nostr" as const,
OriginatingTo: to,
});
// Record the inbound session
const storePath = runtime.channel.session.resolveStorePath(cfg.session?.store, {
agentId: route.agentId,
});
await runtime.channel.session.recordInboundSession({
storePath,
sessionKey,
ctx: msgContext,
updateLastRoute: {
sessionKey: route.mainSessionKey,
channel: "nostr",
to: from,
accountId: account.accountId,
},
onRecordError: (err) => {
ctx.log?.debug(`[${account.accountId}] Failed updating session meta: ${String(err)}`);
},
});
// Create reply dispatcher with typing
const tableMode = runtime.channel.text.resolveMarkdownTableMode({
cfg,
channel: "nostr",
accountId: account.accountId,
});
const { dispatcher, replyOptions, markDispatchIdle } =
runtime.channel.reply.createReplyDispatcherWithTyping({
responsePrefix: null,
responsePrefixContextProvider: null,
humanDelay: runtime.channel.reply.resolveHumanDelayConfig(cfg, route.agentId),
deliver: async (payload) => {
const replyText = payload.text ?? "";
const convertedText = runtime.channel.text.convertMarkdownTables(replyText, tableMode);
await reply(convertedText);
},
onError: (err, info) => {
ctx.log?.error(`[${account.accountId}] Nostr ${info.kind} reply failed: ${String(err)}`);
},
});
// Dispatch the reply
const { queuedFinal, counts } = await runtime.channel.reply.dispatchReplyFromConfig({
ctx: msgContext,
cfg,
dispatcher,
replyOptions,
});
markDispatchIdle();
if (queuedFinal) {
const finalCount = counts.final;
ctx.log?.debug(
`[${account.accountId}] Delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${senderPubkey.slice(0, 8)}`,
);
}
},
onError: (error, context) => {
ctx.log?.error(`[${account.accountId}] Nostr error (${context}): ${error.message}`);