@keychat-io/keychat-openclaw 0.1.16 → 0.1.17
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/scripts/postinstall.mjs +12 -5
- package/src/bridge-client.ts +9 -0
- package/src/channel.ts +40 -4
package/openclaw.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "keychat-openclaw",
|
|
3
|
-
"channels": ["keychat"],
|
|
3
|
+
"channels": ["keychat-openclaw"],
|
|
4
4
|
"name": "Keychat",
|
|
5
5
|
"description": "Sovereign identity + E2E encrypted chat via Signal Protocol over Nostr relays. Lightning wallet support via LNURL-pay and NWC.",
|
|
6
6
|
"version": "0.1.0",
|
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -96,7 +96,7 @@ try {
|
|
|
96
96
|
// Don't fail install — user can build manually
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// Auto-initialize config if channels
|
|
99
|
+
// Auto-initialize config if channels["keychat-openclaw"] not set
|
|
100
100
|
import { homedir } from "node:os";
|
|
101
101
|
|
|
102
102
|
const configPath = join(homedir(), ".openclaw", "openclaw.json");
|
|
@@ -106,16 +106,23 @@ try {
|
|
|
106
106
|
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
if (config.channels?.keychat) {
|
|
109
|
+
if (config.channels?.["keychat-openclaw"] || config.channels?.keychat) {
|
|
110
110
|
console.log("[keychat] Config already contains keychat settings, skipping init");
|
|
111
|
+
// Migrate old channels.keychat → channels.keychat-openclaw
|
|
112
|
+
if (config.channels?.keychat && !config.channels?.["keychat-openclaw"]) {
|
|
113
|
+
config.channels["keychat-openclaw"] = config.channels.keychat;
|
|
114
|
+
delete config.channels.keychat;
|
|
115
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
116
|
+
console.log("[keychat] ✅ Migrated channels.keychat → channels.keychat-openclaw");
|
|
117
|
+
}
|
|
111
118
|
} else {
|
|
112
119
|
if (!config.channels) config.channels = {};
|
|
113
|
-
config.channels
|
|
120
|
+
config.channels["keychat-openclaw"] = { enabled: true, dmPolicy: "open" };
|
|
114
121
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
115
|
-
console.log(
|
|
122
|
+
console.log('[keychat] ✅ Config initialized (channels.keychat-openclaw.enabled = true)');
|
|
116
123
|
console.log("[keychat] Restart gateway to activate: openclaw gateway restart");
|
|
117
124
|
}
|
|
118
125
|
} catch (err) {
|
|
119
126
|
console.warn(`[keychat] Could not auto-configure: ${err.message}`);
|
|
120
|
-
console.warn(
|
|
127
|
+
console.warn('[keychat] Run manually: openclaw config set channels.keychat-openclaw.enabled true');
|
|
121
128
|
}
|
package/src/bridge-client.ts
CHANGED
|
@@ -252,6 +252,15 @@ export class KeychatBridgeClient {
|
|
|
252
252
|
setTimeout(() => reject(new Error("health check timeout")), this.HEALTH_CHECK_TIMEOUT_MS),
|
|
253
253
|
);
|
|
254
254
|
await Promise.race([pingPromise, timeoutPromise]);
|
|
255
|
+
// Also check relay connectivity and auto-reconnect if needed
|
|
256
|
+
try {
|
|
257
|
+
const result = await this.call("relay_health_check") as { reconnected?: boolean };
|
|
258
|
+
if (result?.reconnected) {
|
|
259
|
+
console.log(`[keychat-openclaw] Relay health check: reconnected and resubscribed`);
|
|
260
|
+
}
|
|
261
|
+
} catch (relayErr) {
|
|
262
|
+
console.warn(`[keychat-openclaw] Relay health check failed: ${relayErr}`);
|
|
263
|
+
}
|
|
255
264
|
} catch {
|
|
256
265
|
console.error(`[keychat-openclaw] Health check failed — killing stale process`);
|
|
257
266
|
try { this.process?.kill(); } catch { /* ignore */ }
|
package/src/channel.ts
CHANGED
|
@@ -54,6 +54,12 @@ const pendingOutbound: PendingMessage[] = [];
|
|
|
54
54
|
const MAX_PENDING_QUEUE = 100;
|
|
55
55
|
const MAX_MESSAGE_RETRIES = 5;
|
|
56
56
|
|
|
57
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
58
|
+
// Per-account startup mutex — prevents concurrent startAccount from corrupting state
|
|
59
|
+
// during rapid hot-reloads (e.g. two config changes <1s apart)
|
|
60
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
61
|
+
const accountStartupLocks = new Map<string, Promise<void>>();
|
|
62
|
+
|
|
57
63
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
58
64
|
// Pending hello messages — queued while waiting for session establishment
|
|
59
65
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -327,9 +333,9 @@ function bech32Decode(npub: string): string | null {
|
|
|
327
333
|
}
|
|
328
334
|
|
|
329
335
|
export const keychatPlugin: ChannelPlugin<ResolvedKeychatAccount> = {
|
|
330
|
-
id: "keychat",
|
|
336
|
+
id: "keychat-openclaw",
|
|
331
337
|
meta: {
|
|
332
|
-
id: "keychat",
|
|
338
|
+
id: "keychat-openclaw",
|
|
333
339
|
label: "Keychat",
|
|
334
340
|
selectionLabel: "Keychat (E2E Encrypted)",
|
|
335
341
|
docsPath: "/channels/keychat",
|
|
@@ -691,6 +697,18 @@ export const keychatPlugin: ChannelPlugin<ResolvedKeychatAccount> = {
|
|
|
691
697
|
const runtime = getKeychatRuntime();
|
|
692
698
|
const account = ctx.account;
|
|
693
699
|
|
|
700
|
+
// Serialize startAccount calls for the same account — wait for any in-flight
|
|
701
|
+
// startup/cleanup to finish before proceeding (prevents hot-reload race conditions)
|
|
702
|
+
const prevLock = accountStartupLocks.get(account.accountId);
|
|
703
|
+
if (prevLock) {
|
|
704
|
+
ctx.log?.info(`[${account.accountId}] Waiting for previous startup to finish...`);
|
|
705
|
+
await prevLock.catch(() => {}); // ignore errors from previous run
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
let lockResolve: () => void;
|
|
709
|
+
const lock = new Promise<void>((resolve) => { lockResolve = resolve; });
|
|
710
|
+
accountStartupLocks.set(account.accountId, lock);
|
|
711
|
+
|
|
694
712
|
ctx.log?.info(`[${account.accountId}] Starting Keychat channel...`);
|
|
695
713
|
|
|
696
714
|
// Clean up any existing bridge from a previous start
|
|
@@ -1139,6 +1157,10 @@ export const keychatPlugin: ChannelPlugin<ResolvedKeychatAccount> = {
|
|
|
1139
1157
|
bridgeReadyPromises.delete(account.accountId);
|
|
1140
1158
|
}
|
|
1141
1159
|
|
|
1160
|
+
// Release startup lock — initialization is complete, bridge is ready
|
|
1161
|
+
lockResolve!();
|
|
1162
|
+
ctx.log?.info(`[${account.accountId}] Startup lock released — channel fully initialized`);
|
|
1163
|
+
|
|
1142
1164
|
// Keep the channel alive until abortSignal fires (OpenClaw expects startAccount
|
|
1143
1165
|
// to stay pending while the channel is running — resolving triggers auto-restart)
|
|
1144
1166
|
const abortSignal = (ctx as any).abortSignal as AbortSignal | undefined;
|
|
@@ -1152,15 +1174,29 @@ export const keychatPlugin: ChannelPlugin<ResolvedKeychatAccount> = {
|
|
|
1152
1174
|
await new Promise<void>(() => {});
|
|
1153
1175
|
}
|
|
1154
1176
|
|
|
1155
|
-
// Cleanup on abort
|
|
1177
|
+
// Cleanup on abort — clear ALL module-level state for this account
|
|
1178
|
+
// to prevent stale data leaking into the next startAccount call
|
|
1156
1179
|
bridge.disableAutoRestart();
|
|
1157
1180
|
await bridge.disconnect();
|
|
1158
1181
|
await bridge.stop();
|
|
1182
|
+
// Clear peerSubscribedAddresses entries for this account's peers BEFORE deleting peer sessions
|
|
1183
|
+
const peersToClean = peerSessionsByAccount.get(account.accountId);
|
|
1184
|
+
if (peersToClean) {
|
|
1185
|
+
for (const [peerPk] of peersToClean) {
|
|
1186
|
+
peerSubscribedAddresses.delete(peerPk);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
// Now clear all per-account state so next startup reads fresh from DB
|
|
1159
1190
|
activeBridges.delete(account.accountId);
|
|
1160
1191
|
accountInfoCache.delete(account.accountId);
|
|
1161
1192
|
bridgeReadyPromises.delete(account.accountId);
|
|
1162
1193
|
bridgeReadyResolvers.delete(account.accountId);
|
|
1163
|
-
|
|
1194
|
+
accountStartupLocks.delete(account.accountId);
|
|
1195
|
+
peerSessionsByAccount.delete(account.accountId);
|
|
1196
|
+
addressToPeerByAccount.delete(account.accountId);
|
|
1197
|
+
seenEventIdsByAccount.delete(account.accountId);
|
|
1198
|
+
mlsInitialized.delete(account.accountId);
|
|
1199
|
+
ctx.log?.info(`[${account.accountId}] Keychat provider stopped (all state cleared)`);
|
|
1164
1200
|
},
|
|
1165
1201
|
},
|
|
1166
1202
|
};
|