@echomem/echo-memory-cloud-openclaw-plugin 0.2.0 → 0.2.2
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/README.md +11 -2
- package/clawdbot.plugin.json +2 -2
- package/index.js +44 -12
- package/lib/config.js +35 -8
- package/lib/local-server.js +331 -138
- package/lib/local-ui/dist/assets/index-CKT0_swv.css +1 -0
- package/lib/local-ui/dist/assets/index-D9Bbjf7A.js +54 -0
- package/lib/local-ui/dist/index.html +2 -2
- package/lib/onboarding.js +2 -1
- package/lib/state.js +59 -4
- package/lib/sync.js +42 -15
- package/moltbot.plugin.json +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/lib/local-ui/dist/assets/index-BoyzFR9Q.css +0 -1
- package/lib/local-ui/dist/assets/index-CJrdHn7-.js +0 -54
package/README.md
CHANGED
|
@@ -59,8 +59,11 @@ Path resolution order for `memoryDir`:
|
|
|
59
59
|
Supported runtime `.env` locations:
|
|
60
60
|
|
|
61
61
|
- `~/.openclaw/.env`
|
|
62
|
-
|
|
63
|
-
-
|
|
62
|
+
|
|
63
|
+
Legacy one-release migration bridge:
|
|
64
|
+
|
|
65
|
+
- if `~/.openclaw/.env` is missing, EchoMemory can still read `~/.moltbot/.env` or `~/.clawdbot/.env`
|
|
66
|
+
- new saves and updated setup should go to `~/.openclaw/.env`
|
|
64
67
|
|
|
65
68
|
Supported environment variables:
|
|
66
69
|
|
|
@@ -132,6 +135,11 @@ Older configs may still include `baseUrl` or `webBaseUrl`. Those keys are deprec
|
|
|
132
135
|
|
|
133
136
|
### Install from a local path
|
|
134
137
|
|
|
138
|
+
Version note:
|
|
139
|
+
|
|
140
|
+
- on OpenClaw `2026.3.22+`, avoid bare plugin names during install because plugin source precedence changed
|
|
141
|
+
- use an exact local path, `--link`, or the exact scoped npm package
|
|
142
|
+
|
|
135
143
|
On Windows, quote the path if your username or folders contain spaces:
|
|
136
144
|
|
|
137
145
|
```powershell
|
|
@@ -229,6 +237,7 @@ The plugin starts a localhost workspace UI during gateway startup and can auto-o
|
|
|
229
237
|
|
|
230
238
|
- first run can automatically trigger `npm install` and `npm run build` under `lib/local-ui`
|
|
231
239
|
- browser auto-open is skipped automatically for SSH, CI, and headless Linux sessions
|
|
240
|
+
- when the gateway restarts, an already-open local UI tab reconnects and refreshes itself instead of spawning a redundant new tab
|
|
232
241
|
- `/echo-memory view` returns the current localhost URL for the local markdown workspace UI and also tries to open the browser
|
|
233
242
|
- natural-language requests can use the `echo_memory_local_ui` tool to get the exact live URL instead of guessing the port
|
|
234
243
|
- the local markdown archive stays fully browsable even when no Echo Cloud API key is configured
|
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.2",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"configSchema": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"memoryDir": {
|
|
24
24
|
"type": "string",
|
|
25
|
-
"description": "Absolute path to the markdown memory directory. Falls back to ECHOMEM_MEMORY_DIR, then ~/.
|
|
25
|
+
"description": "Absolute path to the markdown memory directory. Falls back to ECHOMEM_MEMORY_DIR, then ~/.openclaw/workspace/memory."
|
|
26
26
|
},
|
|
27
27
|
"localOnlyMode": {
|
|
28
28
|
"type": "boolean",
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { buildConfig, getEnvFileStatus } from "./lib/config.js";
|
|
3
|
+
import { buildConfig, getEnvFileStatus, getOpenClawHome } from "./lib/config.js";
|
|
4
4
|
import { createApiClient } from "./lib/api-client.js";
|
|
5
5
|
import { formatSearchResultsText } from "./lib/echo-memory-search.js";
|
|
6
6
|
import {
|
|
@@ -15,7 +15,16 @@ import {
|
|
|
15
15
|
import { buildOnboardingText } from "./lib/onboarding.js";
|
|
16
16
|
import { createSyncRunner, formatStatusText } from "./lib/sync.js";
|
|
17
17
|
import { readLastSyncState } from "./lib/state.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
hasRecentLocalUiPresence,
|
|
20
|
+
openUrlInDefaultBrowser,
|
|
21
|
+
startLocalServer,
|
|
22
|
+
stopLocalServer,
|
|
23
|
+
waitForLocalUiClient,
|
|
24
|
+
} from "./lib/local-server.js";
|
|
25
|
+
|
|
26
|
+
const LOCAL_UI_RECONNECT_GRACE_MS = 4000;
|
|
27
|
+
const LOCAL_UI_PRESENCE_GRACE_MS = 75000;
|
|
19
28
|
|
|
20
29
|
function resolveCommandLabel(channel) {
|
|
21
30
|
return channel === "discord" ? "/echomemory" : "/echo-memory";
|
|
@@ -47,13 +56,15 @@ export default {
|
|
|
47
56
|
const cfg = buildConfig(api.pluginConfig);
|
|
48
57
|
const client = createApiClient(cfg);
|
|
49
58
|
const workspaceDir = path.resolve(path.dirname(cfg.memoryDir), "..");
|
|
50
|
-
const openclawHome =
|
|
51
|
-
const
|
|
59
|
+
const openclawHome = getOpenClawHome();
|
|
60
|
+
const legacyPluginStateDir = path.join(openclawHome, "state", "plugins", "echo-memory-cloud-openclaw-plugin");
|
|
61
|
+
const stableStateDir = path.join(openclawHome, "state", "echo-memory-cloud-openclaw-plugin");
|
|
52
62
|
const syncRunner = createSyncRunner({
|
|
53
63
|
api,
|
|
54
64
|
cfg,
|
|
55
65
|
client,
|
|
56
|
-
fallbackStateDir,
|
|
66
|
+
fallbackStateDir: legacyPluginStateDir,
|
|
67
|
+
stableStateDir,
|
|
57
68
|
});
|
|
58
69
|
let startupBrowserOpenAttempted = false;
|
|
59
70
|
let backgroundStarted = false;
|
|
@@ -75,10 +86,20 @@ export default {
|
|
|
75
86
|
let openedInBrowser = false;
|
|
76
87
|
let openReason = "not_requested";
|
|
77
88
|
if (openInBrowser) {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
const existingPageDetected = trigger === "gateway-start"
|
|
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
|
+
});
|
|
82
103
|
openedInBrowser = openResult.opened;
|
|
83
104
|
openReason = openResult.reason;
|
|
84
105
|
}
|
|
@@ -130,7 +151,18 @@ export default {
|
|
|
130
151
|
});
|
|
131
152
|
}
|
|
132
153
|
|
|
133
|
-
const envStatus = getEnvFileStatus();
|
|
154
|
+
const envStatus = getEnvFileStatus();
|
|
155
|
+
if (envStatus.usingLegacyBridge) {
|
|
156
|
+
api.logger?.warn?.(
|
|
157
|
+
`[echo-memory] Legacy env file detected (${envStatus.legacyPaths.join(", ")}). ` +
|
|
158
|
+
`EchoMemory is using a one-release migration bridge and will write future changes to ${envStatus.primaryPath}.`,
|
|
159
|
+
);
|
|
160
|
+
} else if (envStatus.legacyPaths.length > 0) {
|
|
161
|
+
api.logger?.info?.(
|
|
162
|
+
`[echo-memory] Legacy env file still present (${envStatus.legacyPaths.join(", ")}). ` +
|
|
163
|
+
`Current settings should be kept in ${envStatus.primaryPath}.`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
134
166
|
if (!envStatus.found) {
|
|
135
167
|
api.logger?.warn?.(
|
|
136
168
|
`[echo-memory] No .env file found in ${envStatus.searchPaths.join(", ")}. Using plugin config or process env.`,
|
|
@@ -142,7 +174,7 @@ export default {
|
|
|
142
174
|
return;
|
|
143
175
|
}
|
|
144
176
|
backgroundStarted = true;
|
|
145
|
-
await syncRunner.initialize(stateDir ||
|
|
177
|
+
await syncRunner.initialize(stateDir || legacyPluginStateDir);
|
|
146
178
|
|
|
147
179
|
try {
|
|
148
180
|
const shouldOpenBrowser = cfg.localUiAutoOpenOnGatewayStart && !startupBrowserOpenAttempted;
|
|
@@ -196,7 +228,7 @@ export default {
|
|
|
196
228
|
if (serviceStartObserved) {
|
|
197
229
|
return;
|
|
198
230
|
}
|
|
199
|
-
startBackgroundFeatures({ stateDir:
|
|
231
|
+
startBackgroundFeatures({ stateDir: legacyPluginStateDir, trigger: "compat-startup" }).catch((error) => {
|
|
200
232
|
api.logger?.warn?.(`[echo-memory] compatibility startup failed: ${String(error?.message ?? error)}`);
|
|
201
233
|
});
|
|
202
234
|
});
|
package/lib/config.js
CHANGED
|
@@ -4,12 +4,25 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
|
|
5
5
|
const DEFAULT_BASE_URL = "https://echo-mem-chrome.vercel.app";
|
|
6
6
|
const DEFAULT_WEB_BASE_URL = "https://www.iditor.com";
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
7
|
+
function resolveOpenClawHome() {
|
|
8
|
+
const configPath = process.env.OPENCLAW_CONFIG_PATH?.trim();
|
|
9
|
+
if (configPath) {
|
|
10
|
+
return dirname(configPath);
|
|
11
|
+
}
|
|
12
|
+
return join(homedir(), ".openclaw");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const OPENCLAW_HOME = resolveOpenClawHome();
|
|
16
|
+
const PRIMARY_ENV_PATH = join(OPENCLAW_HOME, ".env");
|
|
17
|
+
const LEGACY_ENV_SOURCES = [
|
|
10
18
|
join(homedir(), ".moltbot", ".env"),
|
|
11
19
|
join(homedir(), ".clawdbot", ".env"),
|
|
12
20
|
];
|
|
21
|
+
const DEFAULT_CONFIG_PATH = process.env.OPENCLAW_CONFIG_PATH || join(OPENCLAW_HOME, "openclaw.json");
|
|
22
|
+
const ENV_SOURCES = [
|
|
23
|
+
PRIMARY_ENV_PATH,
|
|
24
|
+
...LEGACY_ENV_SOURCES,
|
|
25
|
+
];
|
|
13
26
|
|
|
14
27
|
let cachedEnv = null;
|
|
15
28
|
|
|
@@ -97,13 +110,22 @@ function parseInteger(value, fallback, { min = Number.NEGATIVE_INFINITY, max = N
|
|
|
97
110
|
|
|
98
111
|
export function getEnvFileStatus() {
|
|
99
112
|
const env = loadEnvFiles();
|
|
113
|
+
const legacyPaths = env.foundPaths.filter((envPath) => LEGACY_ENV_SOURCES.includes(envPath));
|
|
100
114
|
return {
|
|
101
115
|
found: env.foundPaths.length > 0,
|
|
102
116
|
paths: env.foundPaths,
|
|
103
117
|
searchPaths: env.searchPaths,
|
|
118
|
+
primaryPath: PRIMARY_ENV_PATH,
|
|
119
|
+
foundPrimary: env.foundPaths.includes(PRIMARY_ENV_PATH),
|
|
120
|
+
legacyPaths,
|
|
121
|
+
usingLegacyBridge: !env.foundPaths.includes(PRIMARY_ENV_PATH) && legacyPaths.length > 0,
|
|
104
122
|
};
|
|
105
123
|
}
|
|
106
124
|
|
|
125
|
+
export function getOpenClawHome() {
|
|
126
|
+
return OPENCLAW_HOME;
|
|
127
|
+
}
|
|
128
|
+
|
|
107
129
|
function resolveConfigValue(pluginConfig, configKey, envKey, fallback) {
|
|
108
130
|
if (pluginConfig?.[configKey] !== undefined && pluginConfig?.[configKey] !== null && pluginConfig?.[configKey] !== "") {
|
|
109
131
|
return {
|
|
@@ -142,7 +164,6 @@ function maskValue(value, { keepStart = 2, keepEnd = 2 } = {}) {
|
|
|
142
164
|
export function getLocalUiSetupState(pluginConfig = {}, cfg = null) {
|
|
143
165
|
const runtimeCfg = cfg ?? buildConfig(pluginConfig);
|
|
144
166
|
const envStatus = getEnvFileStatus();
|
|
145
|
-
const targetEnvPath = envStatus.paths[0] || envStatus.searchPaths[0];
|
|
146
167
|
const localOnlyMode = resolveConfigValue(
|
|
147
168
|
pluginConfig,
|
|
148
169
|
"localOnlyMode",
|
|
@@ -162,9 +183,12 @@ export function getLocalUiSetupState(pluginConfig = {}, cfg = null) {
|
|
|
162
183
|
targetPath: DEFAULT_CONFIG_PATH,
|
|
163
184
|
},
|
|
164
185
|
envFile: {
|
|
165
|
-
targetPath:
|
|
186
|
+
targetPath: envStatus.primaryPath,
|
|
187
|
+
activePath: envStatus.paths[0] || null,
|
|
166
188
|
foundPaths: envStatus.paths,
|
|
167
189
|
searchPaths: envStatus.searchPaths,
|
|
190
|
+
legacyPaths: envStatus.legacyPaths,
|
|
191
|
+
usingLegacyBridge: envStatus.usingLegacyBridge,
|
|
168
192
|
},
|
|
169
193
|
localOnlyMode: {
|
|
170
194
|
enabled: Boolean(runtimeCfg.localOnlyMode),
|
|
@@ -190,12 +214,13 @@ export function getLocalUiSetupState(pluginConfig = {}, cfg = null) {
|
|
|
190
214
|
|
|
191
215
|
export function saveLocalUiSetup(values = {}) {
|
|
192
216
|
const envStatus = getEnvFileStatus();
|
|
193
|
-
const targetPath = envStatus.
|
|
217
|
+
const targetPath = envStatus.primaryPath;
|
|
218
|
+
const sourcePath = envStatus.foundPrimary ? envStatus.primaryPath : (envStatus.paths[0] || envStatus.primaryPath);
|
|
194
219
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
195
220
|
|
|
196
221
|
let lines = [];
|
|
197
222
|
try {
|
|
198
|
-
lines = readFileSync(
|
|
223
|
+
lines = readFileSync(sourcePath, "utf8").split(/\r?\n/);
|
|
199
224
|
} catch {
|
|
200
225
|
lines = [];
|
|
201
226
|
}
|
|
@@ -237,6 +262,8 @@ export function saveLocalUiSetup(values = {}) {
|
|
|
237
262
|
invalidateEnvCache();
|
|
238
263
|
return {
|
|
239
264
|
targetPath,
|
|
265
|
+
sourcePath,
|
|
266
|
+
migratedFrom: sourcePath !== targetPath ? sourcePath : null,
|
|
240
267
|
savedKeys: [...managedKeys],
|
|
241
268
|
};
|
|
242
269
|
}
|
|
@@ -275,7 +302,7 @@ export function buildConfig(pluginConfig = {}) {
|
|
|
275
302
|
memoryDir: String(
|
|
276
303
|
cfg.memoryDir
|
|
277
304
|
|| loadEnvVar("ECHOMEM_MEMORY_DIR")
|
|
278
|
-
|| join(
|
|
305
|
+
|| join(OPENCLAW_HOME, "workspace", "memory"),
|
|
279
306
|
).trim(),
|
|
280
307
|
};
|
|
281
308
|
}
|