@unicitylabs/openclaw-unicity 0.5.7 → 0.5.9

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 +3 -3
  2. package/src/channel.ts +27 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unicitylabs/openclaw-unicity",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
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",
@@ -44,13 +44,13 @@
44
44
  "dependencies": {
45
45
  "@clack/prompts": "^0.10.0",
46
46
  "@sinclair/typebox": "^0.34.48",
47
- "@unicitylabs/sphere-sdk": "0.6.8-dev.2"
47
+ "@unicitylabs/sphere-sdk": "0.6.8-dev.3"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "openclaw": "*"
51
51
  },
52
52
  "devDependencies": {
53
- "openclaw": "^2026.3.2",
53
+ "openclaw": "^2026.3.13",
54
54
  "oxlint": "^1.43.0",
55
55
  "vitest": "^4.0.18"
56
56
  },
package/src/channel.ts CHANGED
@@ -103,6 +103,8 @@ let activeSphere: Sphere | null = null;
103
103
  let pluginRuntime: PluginRuntime | null = null;
104
104
  let ownerIdentity: string | null = null;
105
105
  let pluginConfig: UnicityConfig = {};
106
+ /** Cleanup function from the previous gateway start — called before re-subscribing. */
107
+ let previousGatewayCleanup: (() => void) | null = null;
106
108
 
107
109
  export function setUnicityRuntime(rt: PluginRuntime): void {
108
110
  pluginRuntime = rt;
@@ -223,6 +225,14 @@ export const unicityChannelPlugin = {
223
225
  log?: { info: (m: string) => void; warn: (m: string) => void; error: (m: string) => void; debug: (m: string) => void };
224
226
  setStatus: (s: Record<string, unknown>) => void;
225
227
  }) => {
228
+ // Clean up handlers from any previous gateway start (auto-restart scenario).
229
+ // Without this, each restart stacks duplicate onDirectMessage handlers on the
230
+ // shared Sphere singleton, causing messages to be processed N times.
231
+ if (previousGatewayCleanup) {
232
+ previousGatewayCleanup();
233
+ previousGatewayCleanup = null;
234
+ }
235
+
226
236
  const sphere = activeSphere ?? await waitForSphere();
227
237
  if (!sphere) throw new Error("Unicity Sphere not initialized — run `openclaw unicity init`");
228
238
 
@@ -233,6 +243,7 @@ export const unicityChannelPlugin = {
233
243
  publicKey: sphere.identity?.chainPubkey,
234
244
  nametag: sphere.identity?.nametag,
235
245
  running: true,
246
+ connected: true,
236
247
  lastStartAt: Date.now(),
237
248
  });
238
249
 
@@ -277,6 +288,7 @@ export const unicityChannelPlugin = {
277
288
 
278
289
  function dispatchDm(msg: DmMsg): void {
279
290
  sendersInFlight.add(msg.senderPubkey);
291
+ ctx.setStatus({ lastEventAt: Date.now() });
280
292
 
281
293
  // Immediately signal that we're composing a reply
282
294
  sphere.communications.sendComposingIndicator(msg.senderPubkey)
@@ -402,6 +414,7 @@ export const unicityChannelPlugin = {
402
414
 
403
415
  // Subscribe to incoming token transfers
404
416
  const unsubTransfer = sphere.on("transfer:incoming", (transfer) => {
417
+ ctx.setStatus({ lastEventAt: Date.now() });
405
418
  // Full address for DM replies; short form for display/logging only
406
419
  const replyTo = transfer.senderNametag ? `@${transfer.senderNametag}` : transfer.senderPubkey;
407
420
  const displayName = transfer.senderNametag ? `@${transfer.senderNametag}` : transfer.senderPubkey.slice(0, 12) + "…";
@@ -464,6 +477,7 @@ export const unicityChannelPlugin = {
464
477
 
465
478
  // Subscribe to incoming payment requests
466
479
  const unsubPaymentRequest = sphere.on("payment_request:incoming", (request) => {
480
+ ctx.setStatus({ lastEventAt: Date.now() });
467
481
  const replyTo = request.senderNametag ? `@${request.senderNametag}` : request.senderPubkey;
468
482
  const displayName = request.senderNametag ? `@${request.senderNametag}` : request.senderPubkey.slice(0, 12) + "…";
469
483
  const decimals = getCoinDecimals(request.coinId) ?? 0;
@@ -618,6 +632,7 @@ export const unicityChannelPlugin = {
618
632
 
619
633
  // Subscribe to incoming group messages
620
634
  const unsubGroupMessage = sphere.groupChat?.onMessage?.((msg: GroupMsg) => {
635
+ ctx.setStatus({ lastEventAt: Date.now() });
621
636
  // Skip messages from self (echoed back by the relay).
622
637
  // Compare against the Nostr x-only pubkey, not chainPubkey.
623
638
  if (myNostrPubkey && msg.senderPubkey === myNostrPubkey) return;
@@ -692,7 +707,7 @@ export const unicityChannelPlugin = {
692
707
  groupBackfillStates.clear();
693
708
  }
694
709
 
695
- ctx.abortSignal.addEventListener("abort", () => {
710
+ function cleanupSubscriptions(): void {
696
711
  clearBackfillTimers();
697
712
  unsub();
698
713
  unsubTransfer();
@@ -701,18 +716,21 @@ export const unicityChannelPlugin = {
701
716
  unsubGroupJoined();
702
717
  unsubGroupLeft();
703
718
  unsubGroupKicked();
719
+ }
720
+
721
+ // Store cleanup so auto-restart can tear down stale handlers
722
+ previousGatewayCleanup = cleanupSubscriptions;
723
+
724
+ ctx.abortSignal.addEventListener("abort", () => {
725
+ cleanupSubscriptions();
726
+ previousGatewayCleanup = null;
704
727
  }, { once: true });
705
728
 
706
729
  return {
707
730
  stop: () => {
708
- clearBackfillTimers();
709
- unsub();
710
- unsubTransfer();
711
- unsubPaymentRequest();
712
- unsubGroupMessage();
713
- unsubGroupJoined();
714
- unsubGroupLeft();
715
- unsubGroupKicked();
731
+ cleanupSubscriptions();
732
+ previousGatewayCleanup = null;
733
+ ctx.setStatus({ connected: false, running: false });
716
734
  ctx.log?.info(`[${ctx.account.accountId}] Unicity channel stopped`);
717
735
  },
718
736
  };