@unicitylabs/openclaw-unicity 0.3.1 → 0.3.3

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/channel.ts +29 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unicitylabs/openclaw-unicity",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Unicity wallet identity and encrypted DMs for OpenClaw agents — powered by Sphere SDK",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/channel.ts CHANGED
@@ -13,6 +13,7 @@ const DEFAULT_ACCOUNT_ID = "default";
13
13
  /** How long (ms) to wait after the last group message before declaring backfill complete. */
14
14
  export const GROUP_BACKFILL_DEBOUNCE_MS = 3_000;
15
15
 
16
+
16
17
  interface GroupBackfillState {
17
18
  phase: "buffering" | "live";
18
19
  latestMsg: {
@@ -452,6 +453,25 @@ export const unicityChannelPlugin = {
452
453
  replyToId?: string;
453
454
  };
454
455
 
456
+ // Nostr pubkey for self-message detection and reply-to-self detection.
457
+ // Group messages use the 32-byte x-only Nostr pubkey (event.pubkey),
458
+ // NOT the 33-byte compressed chainPubkey.
459
+ const myNostrPubkey = sphere.groupChat?.getMyPublicKey?.() ?? null;
460
+
461
+ // Detect if a group message is a reply to one of the agent's own messages.
462
+ // Used to set WasMentioned so the mention gate treats replies-to-self as
463
+ // implicit mentions (same pattern Discord uses for thread replies).
464
+ function isReplyToSelf(msg: GroupMsg): boolean {
465
+ if (!msg.replyToId || !myNostrPubkey) return false;
466
+ try {
467
+ const messages = sphere.groupChat?.getMessages?.(msg.groupId) ?? [];
468
+ const repliedTo = messages.find((m: { id: string }) => m.id === msg.replyToId);
469
+ return repliedTo?.senderPubkey === myNostrPubkey;
470
+ } catch {
471
+ return false;
472
+ }
473
+ }
474
+
455
475
  function dispatchGroupMessage(msg: GroupMsg): void {
456
476
  const senderName = msg.senderNametag ?? msg.senderPubkey.slice(0, 12);
457
477
  const groupData = sphere.groupChat?.getGroup?.(msg.groupId);
@@ -460,6 +480,10 @@ export const unicityChannelPlugin = {
460
480
  const metadataHeader = `[SenderName: ${senderName} | SenderId: ${msg.senderPubkey} | GroupId: ${msg.groupId} | GroupName: ${groupName} | IsOwner: ${isOwner} | CommandAuthorized: ${isOwner}]`;
461
481
  const sanitizedContent = msg.content.replace(/\[(?:SenderName|SenderId|IsOwner|CommandAuthorized|GroupId|GroupName)\s*:/gi, "[BLOCKED:");
462
482
 
483
+ // Treat replies to the agent's own messages as implicit mentions,
484
+ // so the mention gate doesn't skip them (mirrors Discord's behavior).
485
+ const wasMentioned = isReplyToSelf(msg) || undefined;
486
+
463
487
  ctx.log?.info(`[${ctx.account.accountId}] Group message from ${senderName} in ${groupName}: ${msg.content.slice(0, 80)}`);
464
488
 
465
489
  const inboundCtx = runtime.channel.reply.finalizeInboundContext({
@@ -478,6 +502,7 @@ export const unicityChannelPlugin = {
478
502
  SenderId: msg.senderPubkey,
479
503
  IsOwner: isOwner,
480
504
  CommandAuthorized: isOwner,
505
+ WasMentioned: wasMentioned,
481
506
  });
482
507
 
483
508
  runtime.channel.reply
@@ -489,7 +514,7 @@ export const unicityChannelPlugin = {
489
514
  const text = payload.text;
490
515
  if (!text) return;
491
516
  try {
492
- await sphere.groupChat.sendMessage(msg.groupId, text);
517
+ await sphere.groupChat.sendMessage(msg.groupId, text, msg.id);
493
518
  ctx.log?.info(`[${ctx.account.accountId}] Group message sent to ${groupName}: ${text.slice(0, 80)}`);
494
519
  } catch (err) {
495
520
  ctx.log?.error(`[${ctx.account.accountId}] Failed to send group message to ${groupName}: ${err}`);
@@ -514,8 +539,9 @@ export const unicityChannelPlugin = {
514
539
 
515
540
  // Subscribe to incoming group messages
516
541
  const unsubGroupMessage = sphere.groupChat?.onMessage?.((msg: GroupMsg) => {
517
- // Skip messages from self
518
- if (msg.senderPubkey === sphere.identity?.chainPubkey) return;
542
+ // Skip messages from self (echoed back by the relay).
543
+ // Compare against the Nostr x-only pubkey, not chainPubkey.
544
+ if (myNostrPubkey && msg.senderPubkey === myNostrPubkey) return;
519
545
 
520
546
  // Lookup or create per-group backfill state
521
547
  let state = groupBackfillStates.get(msg.groupId);