memory-gateway-sync 0.14.1 → 0.14.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/index.js +61 -44
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/index.ts +78 -50
package/index.js
CHANGED
|
@@ -57,9 +57,7 @@ var index_default = definePluginEntry({
|
|
|
57
57
|
);
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
id: "memory-gateway-sync",
|
|
62
|
-
start: async () => {
|
|
60
|
+
const startService = async () => {
|
|
63
61
|
const openclawDir = path.join(os.homedir(), ".openclaw");
|
|
64
62
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
65
63
|
const lastSyncedHash = /* @__PURE__ */ new Map();
|
|
@@ -225,6 +223,42 @@ var index_default = definePluginEntry({
|
|
|
225
223
|
`memory-gateway-sync: [${wsAgentId}] workspace 注册完成 → ${wsDir}`
|
|
226
224
|
);
|
|
227
225
|
};
|
|
226
|
+
const scanTenantWorkspaces = async (tenantDir, userId) => {
|
|
227
|
+
try {
|
|
228
|
+
const entries = await fs.promises.readdir(tenantDir, { withFileTypes: true });
|
|
229
|
+
for (const entry of entries) {
|
|
230
|
+
if (!entry.isDirectory()) continue;
|
|
231
|
+
const name = entry.name;
|
|
232
|
+
if (name !== "workspace" && !name.startsWith("workspace-")) continue;
|
|
233
|
+
const wsDir = path.join(tenantDir, name);
|
|
234
|
+
const wsAgentId = resolveAgentId(name);
|
|
235
|
+
await registerWorkspace(wsDir, wsAgentId, userId);
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
const watchTenantWorkspacesDir = (tenantDir, userId) => {
|
|
241
|
+
try {
|
|
242
|
+
fs.watch(tenantDir, async (_eventType, filename) => {
|
|
243
|
+
if (!filename) return;
|
|
244
|
+
if (filename !== "workspace" && !filename.startsWith("workspace-")) return;
|
|
245
|
+
const wsDir = path.join(tenantDir, filename);
|
|
246
|
+
try {
|
|
247
|
+
const stat = await fs.promises.stat(wsDir);
|
|
248
|
+
if (!stat.isDirectory()) return;
|
|
249
|
+
const realDir = await fs.promises.realpath(wsDir).catch(() => wsDir);
|
|
250
|
+
if (registeredRealPaths.has(realDir)) return;
|
|
251
|
+
const wsAgentId = resolveAgentId(filename);
|
|
252
|
+
api.logger.info(
|
|
253
|
+
`memory-gateway-sync: [tenants] 发现新 workspace → ${userId}/${filename} (agentId=${wsAgentId})`
|
|
254
|
+
);
|
|
255
|
+
await registerWorkspace(wsDir, wsAgentId, userId);
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
};
|
|
228
262
|
const scanWorkspaces = async () => {
|
|
229
263
|
try {
|
|
230
264
|
const entries = await fs.promises.readdir(openclawDir, {
|
|
@@ -233,6 +267,13 @@ var index_default = definePluginEntry({
|
|
|
233
267
|
for (const entry of entries) {
|
|
234
268
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
235
269
|
const name = entry.name;
|
|
270
|
+
if (name.startsWith("user-")) {
|
|
271
|
+
const tenantDir = path.join(openclawDir, name);
|
|
272
|
+
const userId = name;
|
|
273
|
+
await scanTenantWorkspaces(tenantDir, userId);
|
|
274
|
+
watchTenantWorkspacesDir(tenantDir, userId);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
236
277
|
if (name !== "workspace" && !name.startsWith("workspace-")) continue;
|
|
237
278
|
const wsDir = path.join(openclawDir, name);
|
|
238
279
|
if (entry.isSymbolicLink()) {
|
|
@@ -278,6 +319,20 @@ var index_default = definePluginEntry({
|
|
|
278
319
|
try {
|
|
279
320
|
fs.watch(dir, async (eventType, filename) => {
|
|
280
321
|
if (!filename) return;
|
|
322
|
+
if (dir === openclawDir && filename.startsWith("user-")) {
|
|
323
|
+
const tenantDir = path.join(openclawDir, filename);
|
|
324
|
+
try {
|
|
325
|
+
const stat = await fs.promises.stat(tenantDir);
|
|
326
|
+
if (!stat.isDirectory()) return;
|
|
327
|
+
api.logger.info(
|
|
328
|
+
`memory-gateway-sync: 发现新 tenant → ${filename}`
|
|
329
|
+
);
|
|
330
|
+
await scanTenantWorkspaces(tenantDir, filename);
|
|
331
|
+
watchTenantWorkspacesDir(tenantDir, filename);
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
281
336
|
if (filename !== "workspace" && !filename.startsWith("workspace-")) {
|
|
282
337
|
return;
|
|
283
338
|
}
|
|
@@ -306,43 +361,6 @@ var index_default = definePluginEntry({
|
|
|
306
361
|
);
|
|
307
362
|
}
|
|
308
363
|
};
|
|
309
|
-
|
|
310
|
-
// ── Tenants 模式 ──
|
|
311
|
-
|
|
312
|
-
const scanTenantWorkspaces = async (tenantDir, userId) => {
|
|
313
|
-
const workspacesDir = path.join(tenantDir, "workspaces");
|
|
314
|
-
try {
|
|
315
|
-
const entries = await fs.promises.readdir(workspacesDir, { withFileTypes: true });
|
|
316
|
-
for (const entry of entries) {
|
|
317
|
-
if (!entry.isDirectory()) continue;
|
|
318
|
-
const agentId = entry.name;
|
|
319
|
-
const wsDir = path.join(workspacesDir, agentId);
|
|
320
|
-
await registerWorkspace(wsDir, agentId, userId);
|
|
321
|
-
}
|
|
322
|
-
} catch {
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
const watchTenantWorkspacesDir = (tenantDir, userId) => {
|
|
326
|
-
const workspacesDir = path.join(tenantDir, "workspaces");
|
|
327
|
-
try {
|
|
328
|
-
fs.watch(workspacesDir, async (_eventType, filename) => {
|
|
329
|
-
if (!filename) return;
|
|
330
|
-
const wsDir = path.join(workspacesDir, filename);
|
|
331
|
-
try {
|
|
332
|
-
const stat = await fs.promises.stat(wsDir);
|
|
333
|
-
if (!stat.isDirectory()) return;
|
|
334
|
-
const realDir = await fs.promises.realpath(wsDir).catch(() => wsDir);
|
|
335
|
-
if (registeredRealPaths.has(realDir)) return;
|
|
336
|
-
api.logger.info(
|
|
337
|
-
`memory-gateway-sync: [tenants] 发现新 workspace → ${userId}/${filename}`
|
|
338
|
-
);
|
|
339
|
-
await registerWorkspace(wsDir, filename, userId);
|
|
340
|
-
} catch {
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
} catch {
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
364
|
const scanTenants = async () => {
|
|
347
365
|
try {
|
|
348
366
|
const entries = await fs.promises.readdir(tenantsDir, { withFileTypes: true });
|
|
@@ -408,10 +426,9 @@ var index_default = definePluginEntry({
|
|
|
408
426
|
api.logger.info(
|
|
409
427
|
`memory-gateway-sync: 启动完成 → ${gatewayUrl} (防抖 ${debounceMs}ms, ${wsCount} 个 workspace: ${wsIds})`
|
|
410
428
|
);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
429
|
+
};
|
|
430
|
+
startService().catch((err) => {
|
|
431
|
+
api.logger.warn(`memory-gateway-sync: 启动失败: ${String(err)}`);
|
|
415
432
|
});
|
|
416
433
|
}
|
|
417
434
|
});
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -73,10 +73,9 @@ export default definePluginEntry({
|
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
start: async () => {
|
|
76
|
+
// 2026.6.1 延迟加载插件,startPluginServices 在插件加载前就已执行完毕,
|
|
77
|
+
// 因此直接在 register() 中异步启动,不再依赖 service lifecycle。
|
|
78
|
+
const startService = async () => {
|
|
80
79
|
const openclawDir = path.join(os.homedir(), ".openclaw");
|
|
81
80
|
|
|
82
81
|
// 防抖 Map:key=文件绝对路径,value=setTimeout 句柄
|
|
@@ -295,10 +294,53 @@ export default definePluginEntry({
|
|
|
295
294
|
);
|
|
296
295
|
};
|
|
297
296
|
|
|
297
|
+
// ── Tenants 模式:扫描 {tenantsDir}/{userId}/workspace-{name}/ ──
|
|
298
|
+
|
|
299
|
+
const scanTenantWorkspaces = async (tenantDir: string, userId: string): Promise<void> => {
|
|
300
|
+
try {
|
|
301
|
+
const entries = await fs.promises.readdir(tenantDir, { withFileTypes: true });
|
|
302
|
+
for (const entry of entries) {
|
|
303
|
+
if (!entry.isDirectory()) continue;
|
|
304
|
+
const name = entry.name;
|
|
305
|
+
if (name !== "workspace" && !name.startsWith("workspace-")) continue;
|
|
306
|
+
const wsDir = path.join(tenantDir, name);
|
|
307
|
+
const wsAgentId = resolveAgentId(name);
|
|
308
|
+
await registerWorkspace(wsDir, wsAgentId, userId);
|
|
309
|
+
}
|
|
310
|
+
} catch {
|
|
311
|
+
// tenant 目录不存在或不可读,跳过
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const watchTenantWorkspacesDir = (tenantDir: string, userId: string): void => {
|
|
316
|
+
try {
|
|
317
|
+
fs.watch(tenantDir, async (_eventType, filename) => {
|
|
318
|
+
if (!filename) return;
|
|
319
|
+
if (filename !== "workspace" && !filename.startsWith("workspace-")) return;
|
|
320
|
+
const wsDir = path.join(tenantDir, filename);
|
|
321
|
+
try {
|
|
322
|
+
const stat = await fs.promises.stat(wsDir);
|
|
323
|
+
if (!stat.isDirectory()) return;
|
|
324
|
+
const realDir = await fs.promises.realpath(wsDir).catch(() => wsDir);
|
|
325
|
+
if (registeredRealPaths.has(realDir)) return;
|
|
326
|
+
const wsAgentId = resolveAgentId(filename);
|
|
327
|
+
api.logger.info(
|
|
328
|
+
`memory-gateway-sync: [tenants] 发现新 workspace → ${userId}/${filename} (agentId=${wsAgentId})`
|
|
329
|
+
);
|
|
330
|
+
await registerWorkspace(wsDir, wsAgentId, userId);
|
|
331
|
+
} catch {
|
|
332
|
+
// 目录不存在或已删除
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
} catch {
|
|
336
|
+
// tenant 目录无法监听
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
298
340
|
// ── 扫描所有 workspace 目录 ──────────────────────────────────────
|
|
299
341
|
|
|
300
342
|
const scanWorkspaces = async (): Promise<void> => {
|
|
301
|
-
// 1. 扫描 openclawDir(处理 symlink
|
|
343
|
+
// 1. 扫描 openclawDir(处理 symlink、物理目录、user-* 子目录)
|
|
302
344
|
try {
|
|
303
345
|
const entries = await fs.promises.readdir(openclawDir, {
|
|
304
346
|
withFileTypes: true,
|
|
@@ -306,6 +348,16 @@ export default definePluginEntry({
|
|
|
306
348
|
for (const entry of entries) {
|
|
307
349
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
308
350
|
const name = entry.name;
|
|
351
|
+
|
|
352
|
+
// 自动发现 user-* 子目录,作为 tenant 处理
|
|
353
|
+
if (name.startsWith("user-")) {
|
|
354
|
+
const tenantDir = path.join(openclawDir, name);
|
|
355
|
+
const userId = name;
|
|
356
|
+
await scanTenantWorkspaces(tenantDir, userId);
|
|
357
|
+
watchTenantWorkspacesDir(tenantDir, userId);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
309
361
|
if (name !== "workspace" && !name.startsWith("workspace-")) continue;
|
|
310
362
|
|
|
311
363
|
const wsDir = path.join(openclawDir, name);
|
|
@@ -360,6 +412,24 @@ export default definePluginEntry({
|
|
|
360
412
|
try {
|
|
361
413
|
fs.watch(dir, async (eventType, filename) => {
|
|
362
414
|
if (!filename) return;
|
|
415
|
+
|
|
416
|
+
// 动态感知新 user-* 子目录
|
|
417
|
+
if (dir === openclawDir && filename.startsWith("user-")) {
|
|
418
|
+
const tenantDir = path.join(openclawDir, filename);
|
|
419
|
+
try {
|
|
420
|
+
const stat = await fs.promises.stat(tenantDir);
|
|
421
|
+
if (!stat.isDirectory()) return;
|
|
422
|
+
api.logger.info(
|
|
423
|
+
`memory-gateway-sync: 发现新 tenant → ${filename}`
|
|
424
|
+
);
|
|
425
|
+
await scanTenantWorkspaces(tenantDir, filename);
|
|
426
|
+
watchTenantWorkspacesDir(tenantDir, filename);
|
|
427
|
+
} catch {
|
|
428
|
+
// 目录不存在或已删除
|
|
429
|
+
}
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
363
433
|
if (
|
|
364
434
|
filename !== "workspace" &&
|
|
365
435
|
!filename.startsWith("workspace-")
|
|
@@ -399,47 +469,6 @@ export default definePluginEntry({
|
|
|
399
469
|
}
|
|
400
470
|
};
|
|
401
471
|
|
|
402
|
-
// ── Tenants 模式:扫描 {tenantsDir}/{userId}/workspaces/{agentId}/ ──
|
|
403
|
-
|
|
404
|
-
const scanTenantWorkspaces = async (tenantDir: string, userId: string): Promise<void> => {
|
|
405
|
-
const workspacesDir = path.join(tenantDir, "workspaces");
|
|
406
|
-
try {
|
|
407
|
-
const entries = await fs.promises.readdir(workspacesDir, { withFileTypes: true });
|
|
408
|
-
for (const entry of entries) {
|
|
409
|
-
if (!entry.isDirectory()) continue;
|
|
410
|
-
const agentId = entry.name;
|
|
411
|
-
const wsDir = path.join(workspacesDir, agentId);
|
|
412
|
-
await registerWorkspace(wsDir, agentId, userId);
|
|
413
|
-
}
|
|
414
|
-
} catch {
|
|
415
|
-
// workspaces 目录不存在,跳过
|
|
416
|
-
}
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
const watchTenantWorkspacesDir = (tenantDir: string, userId: string): void => {
|
|
420
|
-
const workspacesDir = path.join(tenantDir, "workspaces");
|
|
421
|
-
try {
|
|
422
|
-
fs.watch(workspacesDir, async (_eventType, filename) => {
|
|
423
|
-
if (!filename) return;
|
|
424
|
-
const wsDir = path.join(workspacesDir, filename);
|
|
425
|
-
try {
|
|
426
|
-
const stat = await fs.promises.stat(wsDir);
|
|
427
|
-
if (!stat.isDirectory()) return;
|
|
428
|
-
const realDir = await fs.promises.realpath(wsDir).catch(() => wsDir);
|
|
429
|
-
if (registeredRealPaths.has(realDir)) return;
|
|
430
|
-
api.logger.info(
|
|
431
|
-
`memory-gateway-sync: [tenants] 发现新 workspace → ${userId}/${filename}`
|
|
432
|
-
);
|
|
433
|
-
await registerWorkspace(wsDir, filename, userId);
|
|
434
|
-
} catch {
|
|
435
|
-
// 目录不存在或已删除
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
} catch {
|
|
439
|
-
// workspaces 目录不存在,无法监听
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
|
|
443
472
|
const scanTenants = async (): Promise<void> => {
|
|
444
473
|
try {
|
|
445
474
|
const entries = await fs.promises.readdir(tenantsDir, { withFileTypes: true });
|
|
@@ -509,11 +538,10 @@ export default definePluginEntry({
|
|
|
509
538
|
api.logger.info(
|
|
510
539
|
`memory-gateway-sync: 启动完成 → ${gatewayUrl} (防抖 ${debounceMs}ms, ${wsCount} 个 workspace: ${wsIds})`
|
|
511
540
|
);
|
|
512
|
-
|
|
541
|
+
};
|
|
513
542
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
},
|
|
543
|
+
startService().catch((err) => {
|
|
544
|
+
api.logger.warn(`memory-gateway-sync: 启动失败: ${String(err)}`);
|
|
517
545
|
});
|
|
518
546
|
},
|
|
519
547
|
});
|