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 CHANGED
@@ -57,9 +57,7 @@ var index_default = definePluginEntry({
57
57
  );
58
58
  return;
59
59
  }
60
- api.registerService({
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
- stop: () => {
413
- api.logger.info("memory-gateway-sync: 停止");
414
- }
429
+ };
430
+ startService().catch((err) => {
431
+ api.logger.warn(`memory-gateway-sync: 启动失败: ${String(err)}`);
415
432
  });
416
433
  }
417
434
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "memory-gateway-sync",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "name": "Memory Gateway Sync",
5
5
  "description": "监听 MEMORY.md / memory/*.md 文件变化,双写到外部 Gateway",
6
6
  "kind": "generic",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-gateway-sync",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "description": "Memory 双写插件:fs.watch 感知变化后同步到外部 Gateway",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/src/index.ts CHANGED
@@ -73,10 +73,9 @@ export default definePluginEntry({
73
73
  return;
74
74
  }
75
75
 
76
- api.registerService({
77
- id: "memory-gateway-sync",
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
- stop: () => {
515
- api.logger.info("memory-gateway-sync: 停止");
516
- },
543
+ startService().catch((err) => {
544
+ api.logger.warn(`memory-gateway-sync: 启动失败: ${String(err)}`);
517
545
  });
518
546
  },
519
547
  });