@keychat-io/keychat-openclaw 0.1.15 โ 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/install.sh +7 -0
- package/scripts/postinstall.mjs +22 -6
- 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/install.sh
CHANGED
|
@@ -10,6 +10,13 @@ BINARY="$INSTALL_DIR/bridge/target/release/keychat-openclaw"
|
|
|
10
10
|
echo "๐ Installing Keychat"
|
|
11
11
|
echo ""
|
|
12
12
|
|
|
13
|
+
# โโ Clean up conflicting installs โโ
|
|
14
|
+
NPM_DIR="${OPENCLAW_EXTENSIONS:-$HOME/.openclaw/extensions}/keychat-openclaw"
|
|
15
|
+
if [ -d "$NPM_DIR" ] && [ "$INSTALL_DIR" != "$NPM_DIR" ]; then
|
|
16
|
+
echo "๐งน Removing npm-installed copy ($NPM_DIR)..."
|
|
17
|
+
rm -rf "$NPM_DIR"
|
|
18
|
+
fi
|
|
19
|
+
|
|
13
20
|
# โโ Check OpenClaw โโ
|
|
14
21
|
if ! command -v openclaw &>/dev/null; then
|
|
15
22
|
echo "โ OpenClaw not found. Install it first: https://docs.openclaw.ai"
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Runs automatically after `npm install` / `openclaw plugins install`.
|
|
5
5
|
* Uses native fetch/https โ no child_process dependency.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, mkdirSync, chmodSync, writeFileSync, readFileSync } from "node:fs";
|
|
7
|
+
import { existsSync, mkdirSync, chmodSync, writeFileSync, readFileSync, rmSync } from "node:fs";
|
|
8
8
|
import { join, dirname } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import https from "node:https";
|
|
@@ -25,6 +25,15 @@ const currentVersion = existsSync(versionFile)
|
|
|
25
25
|
? readFileSync(versionFile, "utf-8").trim()
|
|
26
26
|
: null;
|
|
27
27
|
|
|
28
|
+
// Clean up conflicting script-installed copy (extensions/keychat vs extensions/keychat-openclaw)
|
|
29
|
+
const pluginDir = join(__dirname, "..");
|
|
30
|
+
const pluginDirName = pluginDir.split("/").pop();
|
|
31
|
+
const scriptInstallDir = join(pluginDir, "..", "keychat");
|
|
32
|
+
if (pluginDirName === "keychat-openclaw" && existsSync(scriptInstallDir)) {
|
|
33
|
+
console.log(`[keychat] Removing conflicting script-installed copy...`);
|
|
34
|
+
try { rmSync(scriptInstallDir, { recursive: true, force: true }); } catch {}
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
if (existsSync(BINARY_PATH) && currentVersion === pkgVersion) {
|
|
29
38
|
console.log(`[keychat] Binary already exists (v${pkgVersion}), skipping download`);
|
|
30
39
|
process.exit(0);
|
|
@@ -87,7 +96,7 @@ try {
|
|
|
87
96
|
// Don't fail install โ user can build manually
|
|
88
97
|
}
|
|
89
98
|
|
|
90
|
-
// Auto-initialize config if channels
|
|
99
|
+
// Auto-initialize config if channels["keychat-openclaw"] not set
|
|
91
100
|
import { homedir } from "node:os";
|
|
92
101
|
|
|
93
102
|
const configPath = join(homedir(), ".openclaw", "openclaw.json");
|
|
@@ -97,16 +106,23 @@ try {
|
|
|
97
106
|
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
if (config.channels?.keychat) {
|
|
109
|
+
if (config.channels?.["keychat-openclaw"] || config.channels?.keychat) {
|
|
101
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
|
+
}
|
|
102
118
|
} else {
|
|
103
119
|
if (!config.channels) config.channels = {};
|
|
104
|
-
config.channels
|
|
120
|
+
config.channels["keychat-openclaw"] = { enabled: true, dmPolicy: "open" };
|
|
105
121
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
106
|
-
console.log(
|
|
122
|
+
console.log('[keychat] โ
Config initialized (channels.keychat-openclaw.enabled = true)');
|
|
107
123
|
console.log("[keychat] Restart gateway to activate: openclaw gateway restart");
|
|
108
124
|
}
|
|
109
125
|
} catch (err) {
|
|
110
126
|
console.warn(`[keychat] Could not auto-configure: ${err.message}`);
|
|
111
|
-
console.warn(
|
|
127
|
+
console.warn('[keychat] Run manually: openclaw config set channels.keychat-openclaw.enabled true');
|
|
112
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
|
};
|