@echomem/echo-memory-cloud-openclaw-plugin 0.2.2 → 0.2.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.
- package/clawdbot.plugin.json +1 -1
- package/index.js +184 -56
- package/moltbot.plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/clawdbot.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "echo-memory-cloud-openclaw-plugin",
|
|
3
3
|
"name": "Echo Memory Cloud OpenClaw Plugin",
|
|
4
4
|
"description": "Sync OpenClaw local markdown memory files to Echo cloud",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.3",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"configSchema": {
|
package/index.js
CHANGED
|
@@ -25,6 +25,25 @@ import {
|
|
|
25
25
|
|
|
26
26
|
const LOCAL_UI_RECONNECT_GRACE_MS = 4000;
|
|
27
27
|
const LOCAL_UI_PRESENCE_GRACE_MS = 75000;
|
|
28
|
+
const COMPAT_STARTUP_DELAY_MS = 1500;
|
|
29
|
+
const PROCESS_STATE_KEY = Symbol.for("echo-memory-cloud-openclaw-plugin.process-state");
|
|
30
|
+
|
|
31
|
+
function getProcessState() {
|
|
32
|
+
const globalState = globalThis;
|
|
33
|
+
if (!globalState[PROCESS_STATE_KEY]) {
|
|
34
|
+
globalState[PROCESS_STATE_KEY] = {
|
|
35
|
+
backgroundActive: false,
|
|
36
|
+
backgroundOwnerId: null,
|
|
37
|
+
backgroundStartPromise: null,
|
|
38
|
+
browserOpenAttempted: false,
|
|
39
|
+
browserOpenPromise: null,
|
|
40
|
+
compatFallbackScheduled: false,
|
|
41
|
+
serviceStartObserved: false,
|
|
42
|
+
stopBackground: null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return globalState[PROCESS_STATE_KEY];
|
|
46
|
+
}
|
|
28
47
|
|
|
29
48
|
function resolveCommandLabel(channel) {
|
|
30
49
|
return channel === "discord" ? "/echomemory" : "/echo-memory";
|
|
@@ -53,6 +72,8 @@ export default {
|
|
|
53
72
|
kind: "lifecycle",
|
|
54
73
|
|
|
55
74
|
register(api) {
|
|
75
|
+
const processState = getProcessState();
|
|
76
|
+
const registrationId = Symbol("echo-memory-cloud-openclaw-plugin.register");
|
|
56
77
|
const cfg = buildConfig(api.pluginConfig);
|
|
57
78
|
const client = createApiClient(cfg);
|
|
58
79
|
const workspaceDir = path.resolve(path.dirname(cfg.memoryDir), "..");
|
|
@@ -66,14 +87,52 @@ export default {
|
|
|
66
87
|
fallbackStateDir: legacyPluginStateDir,
|
|
67
88
|
stableStateDir,
|
|
68
89
|
});
|
|
69
|
-
let startupBrowserOpenAttempted = false;
|
|
70
|
-
let backgroundStarted = false;
|
|
71
|
-
let serviceStartObserved = false;
|
|
72
90
|
|
|
73
91
|
if (!workspaceDir || workspaceDir === "." || workspaceDir === path.sep) {
|
|
74
92
|
api.logger?.warn?.("[echo-memory] workspace resolution looks unusual; compatibility fallback may be limited");
|
|
75
93
|
}
|
|
76
94
|
|
|
95
|
+
async function maybeAutoOpenLocalUi(url, { trigger = "manual" } = {}) {
|
|
96
|
+
const existingPageDetected = trigger === "gateway-start"
|
|
97
|
+
? await hasRecentLocalUiPresence(syncRunner, { maxAgeMs: LOCAL_UI_PRESENCE_GRACE_MS })
|
|
98
|
+
: false;
|
|
99
|
+
const existingClientDetected = existingPageDetected || (
|
|
100
|
+
trigger === "gateway-start"
|
|
101
|
+
? await waitForLocalUiClient({ timeoutMs: LOCAL_UI_RECONNECT_GRACE_MS })
|
|
102
|
+
: false
|
|
103
|
+
);
|
|
104
|
+
return existingClientDetected
|
|
105
|
+
? { opened: false, reason: existingPageDetected ? "existing_page_detected" : "existing_client_reconnected" }
|
|
106
|
+
: openUrlInDefaultBrowser(url, {
|
|
107
|
+
logger: api.logger,
|
|
108
|
+
force: trigger !== "gateway-start",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function maybeAutoOpenStartupBrowser(url) {
|
|
113
|
+
if (!cfg.localUiAutoOpenOnGatewayStart || processState.browserOpenAttempted) {
|
|
114
|
+
return { attempted: false, opened: false, reason: "disabled_or_already_attempted" };
|
|
115
|
+
}
|
|
116
|
+
if (processState.browserOpenPromise) {
|
|
117
|
+
const result = await processState.browserOpenPromise;
|
|
118
|
+
return { attempted: false, ...result };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const openPromise = maybeAutoOpenLocalUi(url, { trigger: "gateway-start" })
|
|
122
|
+
.then((result) => {
|
|
123
|
+
processState.browserOpenAttempted = true;
|
|
124
|
+
return result;
|
|
125
|
+
})
|
|
126
|
+
.finally(() => {
|
|
127
|
+
if (processState.browserOpenPromise === openPromise) {
|
|
128
|
+
processState.browserOpenPromise = null;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
processState.browserOpenPromise = openPromise;
|
|
132
|
+
const result = await openPromise;
|
|
133
|
+
return { attempted: true, ...result };
|
|
134
|
+
}
|
|
135
|
+
|
|
77
136
|
async function ensureLocalUi({ openInBrowser = false, trigger = "manual" } = {}) {
|
|
78
137
|
const url = await startLocalServer(workspaceDir, {
|
|
79
138
|
apiClient: client,
|
|
@@ -86,20 +145,7 @@ export default {
|
|
|
86
145
|
let openedInBrowser = false;
|
|
87
146
|
let openReason = "not_requested";
|
|
88
147
|
if (openInBrowser) {
|
|
89
|
-
const
|
|
90
|
-
? await hasRecentLocalUiPresence(syncRunner, { maxAgeMs: LOCAL_UI_PRESENCE_GRACE_MS })
|
|
91
|
-
: false;
|
|
92
|
-
const existingClientDetected = existingPageDetected || (
|
|
93
|
-
trigger === "gateway-start"
|
|
94
|
-
? await waitForLocalUiClient({ timeoutMs: LOCAL_UI_RECONNECT_GRACE_MS })
|
|
95
|
-
: false
|
|
96
|
-
);
|
|
97
|
-
const openResult = existingClientDetected
|
|
98
|
-
? { opened: false, reason: existingPageDetected ? "existing_page_detected" : "existing_client_reconnected" }
|
|
99
|
-
: await openUrlInDefaultBrowser(url, {
|
|
100
|
-
logger: api.logger,
|
|
101
|
-
force: trigger !== "gateway-start",
|
|
102
|
-
});
|
|
148
|
+
const openResult = await maybeAutoOpenLocalUi(url, { trigger });
|
|
103
149
|
openedInBrowser = openResult.opened;
|
|
104
150
|
openReason = openResult.reason;
|
|
105
151
|
}
|
|
@@ -170,68 +216,150 @@ export default {
|
|
|
170
216
|
}
|
|
171
217
|
|
|
172
218
|
async function startBackgroundFeatures({ stateDir = null, trigger = "service" } = {}) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
219
|
+
const effectiveTrigger = trigger === "service" ? "gateway-start" : trigger;
|
|
220
|
+
|
|
221
|
+
if (processState.backgroundActive) {
|
|
222
|
+
if (effectiveTrigger === "gateway-start") {
|
|
223
|
+
const { attempted, opened, reason } = await maybeAutoOpenStartupBrowser(
|
|
224
|
+
await startLocalServer(workspaceDir, {
|
|
225
|
+
apiClient: client,
|
|
226
|
+
syncRunner,
|
|
227
|
+
cfg,
|
|
228
|
+
logger: api.logger,
|
|
229
|
+
pluginConfig: api.pluginConfig,
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
if (attempted) {
|
|
233
|
+
if (opened) {
|
|
234
|
+
api.logger?.info?.("[echo-memory] Opened local workspace viewer in the default browser");
|
|
235
|
+
} else {
|
|
236
|
+
api.logger?.info?.(`[echo-memory] Skipped browser auto-open (${reason})`);
|
|
237
|
+
}
|
|
192
238
|
}
|
|
193
239
|
}
|
|
194
|
-
|
|
195
|
-
api.logger?.warn?.(`[echo-memory] local server failed: ${String(error?.message ?? error)}`);
|
|
240
|
+
return;
|
|
196
241
|
}
|
|
197
242
|
|
|
198
|
-
if (
|
|
199
|
-
|
|
243
|
+
if (processState.backgroundStartPromise) {
|
|
244
|
+
await processState.backgroundStartPromise;
|
|
245
|
+
if (effectiveTrigger === "gateway-start") {
|
|
246
|
+
const { attempted, opened, reason } = await maybeAutoOpenStartupBrowser(
|
|
247
|
+
await startLocalServer(workspaceDir, {
|
|
248
|
+
apiClient: client,
|
|
249
|
+
syncRunner,
|
|
250
|
+
cfg,
|
|
251
|
+
logger: api.logger,
|
|
252
|
+
pluginConfig: api.pluginConfig,
|
|
253
|
+
}),
|
|
254
|
+
);
|
|
255
|
+
if (attempted) {
|
|
256
|
+
if (opened) {
|
|
257
|
+
api.logger?.info?.("[echo-memory] Opened local workspace viewer in the default browser");
|
|
258
|
+
} else {
|
|
259
|
+
api.logger?.info?.(`[echo-memory] Skipped browser auto-open (${reason})`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
200
263
|
return;
|
|
201
264
|
}
|
|
202
265
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
266
|
+
const startPromise = (async () => {
|
|
267
|
+
processState.backgroundOwnerId = registrationId;
|
|
268
|
+
processState.stopBackground = () => {
|
|
269
|
+
syncRunner.stopInterval();
|
|
270
|
+
stopLocalServer();
|
|
271
|
+
};
|
|
272
|
+
await syncRunner.initialize(stateDir || legacyPluginStateDir);
|
|
273
|
+
|
|
274
|
+
let url = null;
|
|
275
|
+
try {
|
|
276
|
+
const localUi = await ensureLocalUi({
|
|
277
|
+
openInBrowser: false,
|
|
278
|
+
trigger: effectiveTrigger,
|
|
279
|
+
});
|
|
280
|
+
url = localUi.url;
|
|
281
|
+
api.logger?.info?.(`[echo-memory] Local workspace viewer: ${url}`);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
api.logger?.warn?.(`[echo-memory] local server failed: ${String(error?.message ?? error)}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
processState.backgroundActive = true;
|
|
287
|
+
|
|
288
|
+
if (effectiveTrigger === "gateway-start" && url) {
|
|
289
|
+
const { attempted, opened, reason } = await maybeAutoOpenStartupBrowser(url);
|
|
290
|
+
if (attempted) {
|
|
291
|
+
if (opened) {
|
|
292
|
+
api.logger?.info?.("[echo-memory] Opened local workspace viewer in the default browser");
|
|
293
|
+
} else {
|
|
294
|
+
api.logger?.info?.(`[echo-memory] Skipped browser auto-open (${reason})`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!cfg.autoSync) {
|
|
300
|
+
api.logger?.info?.("[echo-memory] autoSync disabled");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
await syncRunner.runSync(trigger === "service" ? "startup" : trigger).catch((error) => {
|
|
305
|
+
api.logger?.warn?.(`[echo-memory] startup sync failed: ${String(error?.message ?? error)}`);
|
|
306
|
+
});
|
|
206
307
|
|
|
207
|
-
|
|
308
|
+
syncRunner.startInterval();
|
|
309
|
+
})()
|
|
310
|
+
.catch((error) => {
|
|
311
|
+
if (processState.backgroundOwnerId === registrationId) {
|
|
312
|
+
processState.backgroundActive = false;
|
|
313
|
+
processState.backgroundOwnerId = null;
|
|
314
|
+
processState.stopBackground = null;
|
|
315
|
+
}
|
|
316
|
+
throw error;
|
|
317
|
+
})
|
|
318
|
+
.finally(() => {
|
|
319
|
+
if (processState.backgroundStartPromise === startPromise) {
|
|
320
|
+
processState.backgroundStartPromise = null;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
processState.backgroundStartPromise = startPromise;
|
|
325
|
+
await startPromise;
|
|
208
326
|
}
|
|
209
327
|
|
|
210
328
|
if (typeof api.registerService === "function") {
|
|
211
329
|
api.registerService({
|
|
212
330
|
id: "echo-memory-cloud-openclaw-sync",
|
|
213
331
|
start: async (ctx) => {
|
|
214
|
-
serviceStartObserved = true;
|
|
332
|
+
processState.serviceStartObserved = true;
|
|
215
333
|
await startBackgroundFeatures({ stateDir: ctx?.stateDir || null, trigger: "service" });
|
|
216
334
|
},
|
|
217
335
|
stop: async () => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
336
|
+
processState.stopBackground?.();
|
|
337
|
+
processState.backgroundActive = false;
|
|
338
|
+
processState.backgroundOwnerId = null;
|
|
339
|
+
processState.backgroundStartPromise = null;
|
|
340
|
+
processState.browserOpenAttempted = false;
|
|
341
|
+
processState.browserOpenPromise = null;
|
|
342
|
+
processState.compatFallbackScheduled = false;
|
|
343
|
+
processState.serviceStartObserved = false;
|
|
344
|
+
processState.stopBackground = null;
|
|
221
345
|
},
|
|
222
346
|
});
|
|
223
347
|
}
|
|
224
348
|
|
|
225
349
|
// Compatibility fallback for older hosts that discover the plugin but do not
|
|
226
350
|
// reliably auto-start registered background services.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
351
|
+
if (!processState.compatFallbackScheduled) {
|
|
352
|
+
processState.compatFallbackScheduled = true;
|
|
353
|
+
setTimeout(() => {
|
|
354
|
+
processState.compatFallbackScheduled = false;
|
|
355
|
+
if (processState.serviceStartObserved || processState.backgroundActive || processState.backgroundStartPromise) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
startBackgroundFeatures({ stateDir: legacyPluginStateDir, trigger: "compat-startup" }).catch((error) => {
|
|
359
|
+
api.logger?.warn?.(`[echo-memory] compatibility startup failed: ${String(error?.message ?? error)}`);
|
|
360
|
+
});
|
|
361
|
+
}, COMPAT_STARTUP_DELAY_MS);
|
|
362
|
+
}
|
|
235
363
|
|
|
236
364
|
if (typeof api.registerCommand === "function") {
|
|
237
365
|
api.registerCommand({
|
package/moltbot.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "echo-memory-cloud-openclaw-plugin",
|
|
3
3
|
"name": "Echo Memory Cloud OpenClaw Plugin",
|
|
4
4
|
"description": "Sync OpenClaw local markdown memory files to Echo cloud",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.3",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"configSchema": {
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "echo-memory-cloud-openclaw-plugin",
|
|
3
3
|
"name": "Echo Memory Cloud OpenClaw Plugin",
|
|
4
4
|
"description": "Sync OpenClaw local markdown memory files to Echo cloud",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.3",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"configSchema": {
|