memory-gateway-sync 0.8.0 → 0.10.0

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
@@ -1,17 +1,24 @@
1
+ // memory-gateway-sync/src/index.ts
1
2
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import crypto from "node:crypto";
5
6
  import os from "node:os";
7
+ var registered = false;
6
8
  var index_default = definePluginEntry({
7
9
  id: "memory-gateway-sync",
8
10
  name: "Memory Gateway Sync",
9
11
  description: "fs.watch \u76D1\u542C\u591A workspace \u7684 MEMORY \u6587\u4EF6\u53D8\u5316\uFF0C\u53CC\u5199\u5230\u5916\u90E8 Gateway\uFF08\u652F\u6301 peers/groups/knowledge \u5B50\u76EE\u5F55\uFF09",
10
12
  kind: "generic",
11
13
  register(api) {
14
+ if (registered) {
15
+ api.logger.info("memory-gateway-sync: \u5DF2\u6CE8\u518C\uFF0C\u8DF3\u8FC7\u91CD\u590D\u52A0\u8F7D");
16
+ return;
17
+ }
18
+ registered = true;
12
19
  const cfg = api.pluginConfig ?? {};
13
- const gatewayUrl = cfg.gatewayUrl;
14
- const gatewayToken = cfg.gatewayToken;
20
+ const gatewayUrl = cfg.gatewayUrl || process.env.MEMORY_SYNC_GATEWAY_URL || "";
21
+ const gatewayToken = cfg.gatewayToken || process.env.MEMORY_SYNC_GATEWAY_TOKEN || "";
15
22
  const debounceMs = cfg.debounceMs ?? 1500;
16
23
  const defaultAgentId = cfg.agentId ?? "default";
17
24
  const ownerClawUserId = (() => {
@@ -80,6 +87,17 @@ var index_default = definePluginEntry({
80
87
  return;
81
88
  }
82
89
  const relativePath = path.relative(ws.dir, absPath);
90
+ if (relativePath === "MEMORY.md" && content.trim().length > 0) {
91
+ try {
92
+ await fs.promises.writeFile(absPath, "", "utf-8");
93
+ const emptyHash = crypto.createHash("md5").update("").digest("hex");
94
+ lastSyncedHash.set(absPath, emptyHash);
95
+ } catch (err) {
96
+ api.logger.warn(
97
+ `memory-gateway-sync: [${ws.agentId}] 清空 MEMORY.md 失败: ${String(err)}`
98
+ );
99
+ }
100
+ }
83
101
  let userId = null;
84
102
  const parts = relativePath.replace(/\\/g, "/").split("/");
85
103
  if (relativePath === "MEMORY.md" || parts[0] === "memory" && parts[1] === "owner") {
@@ -97,9 +115,10 @@ var index_default = definePluginEntry({
97
115
  eventType,
98
116
  syncedAt: (/* @__PURE__ */ new Date()).toISOString()
99
117
  };
118
+ let syncOk = false;
100
119
  try {
101
120
  const controller = new AbortController();
102
- const timeout = setTimeout(() => controller.abort(), 3e4);
121
+ const timeout = setTimeout(() => controller.abort(), 12e4);
103
122
  const res = await fetch(gatewayUrl, {
104
123
  method: "POST",
105
124
  headers: {
@@ -111,31 +130,11 @@ var index_default = definePluginEntry({
111
130
  });
112
131
  clearTimeout(timeout);
113
132
  if (res.ok) {
133
+ syncOk = true;
114
134
  lastSyncedHash.set(absPath, contentHash);
115
135
  api.logger.info?.(
116
136
  `memory-gateway-sync: [${ws.agentId}] \u540C\u6B65\u6210\u529F ${relativePath} (${content.length} chars)`
117
137
  );
118
- if (relativePath === "MEMORY.md" && content.trim().length > 0) {
119
- try {
120
- const backupPath = absPath + ".bak";
121
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
122
- const section = `
123
- <!-- backup ${timestamp} -->
124
- ${content}
125
- `;
126
- await fs.promises.appendFile(backupPath, section, "utf-8");
127
- await fs.promises.writeFile(absPath, "", "utf-8");
128
- const emptyHash = crypto.createHash("md5").update("").digest("hex");
129
- lastSyncedHash.set(absPath, emptyHash);
130
- api.logger.info?.(
131
- `memory-gateway-sync: [${ws.agentId}] MEMORY.md \u5DF2\u5907\u4EFD\u5230 .bak \u5E76\u6E05\u7A7A`
132
- );
133
- } catch (err) {
134
- api.logger.warn(
135
- `memory-gateway-sync: [${ws.agentId}] \u5907\u4EFD/\u6E05\u7A7A MEMORY.md \u5931\u8D25: ${String(err)}`
136
- );
137
- }
138
- }
139
138
  } else {
140
139
  const body = await res.text().catch(() => "");
141
140
  api.logger.warn(
@@ -147,6 +146,21 @@ ${content}
147
146
  `memory-gateway-sync: [${ws.agentId}] POST \u5931\u8D25 ${relativePath}: ${String(err)}`
148
147
  );
149
148
  }
149
+ if (relativePath === "MEMORY.md" && content.trim().length > 0) {
150
+ try {
151
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
152
+ const section = `\n<!-- backup ${timestamp} synced=${syncOk} -->\n${content}\n`;
153
+ const backupPath = syncOk ? absPath + ".bak" : absPath + ".failed";
154
+ await fs.promises.appendFile(backupPath, section, "utf-8");
155
+ api.logger.info?.(
156
+ `memory-gateway-sync: [${ws.agentId}] MEMORY.md \u5DF2\u5907\u4EFD\u5230 ${syncOk ? ".bak" : ".failed"}`
157
+ );
158
+ } catch (err) {
159
+ api.logger.warn(
160
+ `memory-gateway-sync: [${ws.agentId}] \u5907\u4EFD/\u6E05\u7A7A MEMORY.md \u5931\u8D25: ${String(err)}`
161
+ );
162
+ }
163
+ }
150
164
  };
151
165
  const scheduleSync = (absPath, eventType) => {
152
166
  const existing = debounceTimers.get(absPath);
@@ -24,6 +24,5 @@
24
24
  "description": "当前 agent 标识,写入 Gateway 时携带,用于后续多 agent 隔离"
25
25
  }
26
26
  },
27
- "required": ["gatewayUrl", "gatewayToken"]
28
27
  }
29
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-gateway-sync",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Memory 双写插件:fs.watch 感知变化后同步到外部 Gateway",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/src/index.ts CHANGED
@@ -29,6 +29,9 @@ interface WorkspaceContext {
29
29
  agentId: string;
30
30
  }
31
31
 
32
+ // ── 防重复加载 ───────────────────────────────────────────────────────────────
33
+ let registered = false;
34
+
32
35
  // ── 插件入口 ──────────────────────────────────────────────────────────────────
33
36
 
34
37
  export default definePluginEntry({
@@ -39,9 +42,14 @@ export default definePluginEntry({
39
42
  kind: "generic",
40
43
 
41
44
  register(api) {
42
- const cfg = (api.pluginConfig ?? {}) as PluginConfig;
43
- const gatewayUrl = cfg.gatewayUrl;
44
- const gatewayToken = cfg.gatewayToken;
45
+ if (registered) {
46
+ api.logger.info("memory-gateway-sync: 已注册,跳过重复加载");
47
+ return;
48
+ }
49
+ registered = true;
50
+ const cfg = (api.pluginConfig ?? {}) as Partial<PluginConfig>;
51
+ const gatewayUrl = cfg.gatewayUrl || process.env.MEMORY_SYNC_GATEWAY_URL || "";
52
+ const gatewayToken = cfg.gatewayToken || process.env.MEMORY_SYNC_GATEWAY_TOKEN || "";
45
53
  const debounceMs = cfg.debounceMs ?? 1500;
46
54
  const defaultAgentId = cfg.agentId ?? "default";
47
55
  const ownerClawUserId = (() => {
@@ -142,6 +150,19 @@ export default definePluginEntry({
142
150
 
143
151
  const relativePath = path.relative(ws.dir, absPath);
144
152
 
153
+ // MEMORY.md 立即清空源文件,防止数据泄露
154
+ if (relativePath === "MEMORY.md" && content.trim().length > 0) {
155
+ try {
156
+ await fs.promises.writeFile(absPath, "", "utf-8");
157
+ const emptyHash = crypto.createHash("md5").update("").digest("hex");
158
+ lastSyncedHash.set(absPath, emptyHash);
159
+ } catch (err) {
160
+ api.logger.warn(
161
+ `memory-gateway-sync: [${ws.agentId}] 清空 MEMORY.md 失败: ${String(err)}`
162
+ );
163
+ }
164
+ }
165
+
145
166
  // 根据路径解析 userId
146
167
  // MEMORY.md / memory/owner/* → 环境变量 owner_claw_user_id
147
168
  // memory/peers/{peerId}/* → 路径中的 peerId
@@ -165,9 +186,10 @@ export default definePluginEntry({
165
186
  syncedAt: new Date().toISOString(),
166
187
  };
167
188
 
189
+ let syncOk = false;
168
190
  try {
169
191
  const controller = new AbortController();
170
- const timeout = setTimeout(() => controller.abort(), 30000);
192
+ const timeout = setTimeout(() => controller.abort(), 120000);
171
193
 
172
194
  const res = await fetch(gatewayUrl, {
173
195
  method: "POST",
@@ -182,29 +204,11 @@ export default definePluginEntry({
182
204
  clearTimeout(timeout);
183
205
 
184
206
  if (res.ok) {
207
+ syncOk = true;
185
208
  lastSyncedHash.set(absPath, contentHash);
186
209
  api.logger.info?.(
187
210
  `memory-gateway-sync: [${ws.agentId}] 同步成功 ${relativePath} (${content.length} chars)`
188
211
  );
189
-
190
- if (relativePath === "MEMORY.md" && content.trim().length > 0) {
191
- try {
192
- const backupPath = absPath + ".bak";
193
- const timestamp = new Date().toISOString();
194
- const section = `\n<!-- backup ${timestamp} -->\n${content}\n`;
195
- await fs.promises.appendFile(backupPath, section, "utf-8");
196
- await fs.promises.writeFile(absPath, "", "utf-8");
197
- const emptyHash = crypto.createHash("md5").update("").digest("hex");
198
- lastSyncedHash.set(absPath, emptyHash);
199
- api.logger.info?.(
200
- `memory-gateway-sync: [${ws.agentId}] MEMORY.md 已备份到 .bak 并清空`
201
- );
202
- } catch (err) {
203
- api.logger.warn(
204
- `memory-gateway-sync: [${ws.agentId}] 备份/清空 MEMORY.md 失败: ${String(err)}`
205
- );
206
- }
207
- }
208
212
  } else {
209
213
  const body = await res.text().catch(() => "");
210
214
  api.logger.warn(
@@ -216,6 +220,22 @@ export default definePluginEntry({
216
220
  `memory-gateway-sync: [${ws.agentId}] POST 失败 ${relativePath}: ${String(err)}`
217
221
  );
218
222
  }
223
+
224
+ if (relativePath === "MEMORY.md" && content.trim().length > 0) {
225
+ try {
226
+ const timestamp = new Date().toISOString();
227
+ const section = `\n<!-- backup ${timestamp} synced=${syncOk} -->\n${content}\n`;
228
+ const backupPath = syncOk ? absPath + ".bak" : absPath + ".failed";
229
+ await fs.promises.appendFile(backupPath, section, "utf-8");
230
+ api.logger.info?.(
231
+ `memory-gateway-sync: [${ws.agentId}] MEMORY.md 已备份到 ${syncOk ? ".bak" : ".failed"}`
232
+ );
233
+ } catch (err) {
234
+ api.logger.warn(
235
+ `memory-gateway-sync: [${ws.agentId}] 备份 MEMORY.md 失败: ${String(err)}`
236
+ );
237
+ }
238
+ }
219
239
  };
220
240
 
221
241
  // ── 防抖调度 ─────────────────────────────────────────────────────