diff --git a/extensions/nostr/src/channel.ts b/extensions/nostr/src/channel.ts index e6df0872c..580cdbdbf 100644 --- a/extensions/nostr/src/channel.ts +++ b/extensions/nostr/src/channel.ts @@ -218,21 +218,72 @@ export const nostrPlugin: ChannelPlugin = { accountId: account.accountId, privateKey: account.privateKey, relays: account.relays, - onMessage: async (senderPubkey, text, reply) => { + onMessage: async (senderPubkey, text, replyFn) => { ctx.log?.debug(`[${account.accountId}] DM from ${senderPubkey}: ${text.slice(0, 50)}...`); - // Forward to clawdbot's message pipeline - await runtime.channel.reply.handleInboundMessage({ + // Load config and resolve routing + const cfg = await runtime.config.loadConfig(); + 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); - }, + chatId: senderPubkey, + senderId: senderPubkey, }); + + // Build the inbound context + const ctxPayload = runtime.channel.reply.finalizeInboundContext({ + Body: text, + RawBody: text, + CommandBody: text, + From: `nostr:${senderPubkey}`, + To: `nostr:dm:${senderPubkey}`, + SessionKey: route.sessionKey, + AccountId: account.accountId, + ChatType: "direct", + ConversationLabel: `nostr:${senderPubkey.slice(0, 8)}`, + SenderName: senderPubkey.slice(0, 8), + SenderId: senderPubkey, + Provider: "nostr" as const, + Surface: "nostr" as const, + CommandSource: "text" as const, + OriginatingChannel: "nostr" as const, + OriginatingTo: `nostr:dm:${senderPubkey}`, + }); + + // Create reply dispatcher + const { dispatcher, replyOptions, markDispatchIdle } = + runtime.channel.reply.createReplyDispatcherWithTyping({ + humanDelay: runtime.channel.reply.resolveHumanDelayConfig(cfg, route.agentId), + deliver: async (payload) => { + if (payload.text) { + await replyFn(payload.text); + } + }, + onError: (err, info) => { + ctx.log?.error(`nostr ${info.kind} reply failed: ${String(err)}`); + }, + }); + + // Dispatch to agent + try { + const { queuedFinal, counts } = await runtime.channel.reply.dispatchReplyFromConfig({ + ctx: ctxPayload, + 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)}`, + ); + } + } catch (err) { + ctx.log?.error(`[${account.accountId}] dispatch failed: ${String(err)}`); + } }, onError: (error, context) => { ctx.log?.error(`[${account.accountId}] Nostr error (${context}): ${error.message}`); diff --git a/extensions/nostr/src/nostr-bus.ts b/extensions/nostr/src/nostr-bus.ts index 810d0a479..f7aa4e666 100644 --- a/extensions/nostr/src/nostr-bus.ts +++ b/extensions/nostr/src/nostr-bus.ts @@ -657,6 +657,7 @@ async function sendEncryptedDm( const startTime = Date.now(); try { + // pool.publish returns Promise[], await the first promise await pool.publish([relay], reply)[0]; const latency = Date.now() - startTime; diff --git a/extensions/nostr/src/nostr-profile.ts b/extensions/nostr/src/nostr-profile.ts index 1dba8e100..20dc13101 100644 --- a/extensions/nostr/src/nostr-profile.ts +++ b/extensions/nostr/src/nostr-profile.ts @@ -150,7 +150,8 @@ export async function publishProfileEvent( setTimeout(() => reject(new Error("timeout")), RELAY_PUBLISH_TIMEOUT_MS); }); - await Promise.race([pool.publish([relay], event), timeoutPromise]); + // pool.publish returns Promise[], get the first promise + await Promise.race([pool.publish([relay], event)[0], timeoutPromise]); successes.push(relay); } catch (err) {