memory-gateway-sync 0.9.0 → 0.11.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.
Files changed (3) hide show
  1. package/index.js +36 -22
  2. package/package.json +1 -1
  3. package/src/index.ts +42 -20
package/index.js CHANGED
@@ -4,12 +4,18 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import crypto from "node:crypto";
6
6
  import os from "node:os";
7
+ var registered = false;
7
8
  var index_default = definePluginEntry({
8
9
  id: "memory-gateway-sync",
9
10
  name: "Memory Gateway Sync",
10
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",
11
12
  kind: "generic",
12
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;
13
19
  const cfg = api.pluginConfig ?? {};
14
20
  const gatewayUrl = cfg.gatewayUrl || process.env.MEMORY_SYNC_GATEWAY_URL || "";
15
21
  const gatewayToken = cfg.gatewayToken || process.env.MEMORY_SYNC_GATEWAY_TOKEN || "";
@@ -81,6 +87,17 @@ var index_default = definePluginEntry({
81
87
  return;
82
88
  }
83
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
+ }
84
101
  let userId = null;
85
102
  const parts = relativePath.replace(/\\/g, "/").split("/");
86
103
  if (relativePath === "MEMORY.md" || parts[0] === "memory" && parts[1] === "owner") {
@@ -98,9 +115,10 @@ var index_default = definePluginEntry({
98
115
  eventType,
99
116
  syncedAt: (/* @__PURE__ */ new Date()).toISOString()
100
117
  };
118
+ let syncOk = false;
101
119
  try {
102
120
  const controller = new AbortController();
103
- const timeout = setTimeout(() => controller.abort(), 3e4);
121
+ const timeout = setTimeout(() => controller.abort(), 12e4);
104
122
  const res = await fetch(gatewayUrl, {
105
123
  method: "POST",
106
124
  headers: {
@@ -112,31 +130,11 @@ var index_default = definePluginEntry({
112
130
  });
113
131
  clearTimeout(timeout);
114
132
  if (res.ok) {
133
+ syncOk = true;
115
134
  lastSyncedHash.set(absPath, contentHash);
116
135
  api.logger.info?.(
117
136
  `memory-gateway-sync: [${ws.agentId}] \u540C\u6B65\u6210\u529F ${relativePath} (${content.length} chars)`
118
137
  );
119
- if (relativePath === "MEMORY.md" && content.trim().length > 0) {
120
- try {
121
- const backupPath = absPath + ".bak";
122
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
123
- const section = `
124
- <!-- backup ${timestamp} -->
125
- ${content}
126
- `;
127
- await fs.promises.appendFile(backupPath, section, "utf-8");
128
- await fs.promises.writeFile(absPath, "", "utf-8");
129
- const emptyHash = crypto.createHash("md5").update("").digest("hex");
130
- lastSyncedHash.set(absPath, emptyHash);
131
- api.logger.info?.(
132
- `memory-gateway-sync: [${ws.agentId}] MEMORY.md \u5DF2\u5907\u4EFD\u5230 .bak \u5E76\u6E05\u7A7A`
133
- );
134
- } catch (err) {
135
- api.logger.warn(
136
- `memory-gateway-sync: [${ws.agentId}] \u5907\u4EFD/\u6E05\u7A7A MEMORY.md \u5931\u8D25: ${String(err)}`
137
- );
138
- }
139
- }
140
138
  } else {
141
139
  const body = await res.text().catch(() => "");
142
140
  api.logger.warn(
@@ -148,6 +146,21 @@ ${content}
148
146
  `memory-gateway-sync: [${ws.agentId}] POST \u5931\u8D25 ${relativePath}: ${String(err)}`
149
147
  );
150
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
+ }
151
164
  };
152
165
  const scheduleSync = (absPath, eventType) => {
153
166
  const existing = debounceTimers.get(absPath);
@@ -174,6 +187,7 @@ ${content}
174
187
  api.logger.info?.(
175
188
  `memory-gateway-sync: [${wsAgentId}] \u5F00\u59CB\u76D1\u542C MEMORY.md`
176
189
  );
190
+ scheduleSync(memoryMdPath, "startup");
177
191
  } catch (err) {
178
192
  api.logger.warn(
179
193
  `memory-gateway-sync: [${wsAgentId}] \u65E0\u6CD5\u76D1\u542C MEMORY.md: ${String(err)}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-gateway-sync",
3
- "version": "0.9.0",
3
+ "version": "0.11.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,6 +42,11 @@ export default definePluginEntry({
39
42
  kind: "generic",
40
43
 
41
44
  register(api) {
45
+ if (registered) {
46
+ api.logger.info("memory-gateway-sync: 已注册,跳过重复加载");
47
+ return;
48
+ }
49
+ registered = true;
42
50
  const cfg = (api.pluginConfig ?? {}) as Partial<PluginConfig>;
43
51
  const gatewayUrl = cfg.gatewayUrl || process.env.MEMORY_SYNC_GATEWAY_URL || "";
44
52
  const gatewayToken = cfg.gatewayToken || process.env.MEMORY_SYNC_GATEWAY_TOKEN || "";
@@ -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
  // ── 防抖调度 ─────────────────────────────────────────────────────
@@ -252,6 +272,8 @@ export default definePluginEntry({
252
272
  api.logger.info?.(
253
273
  `memory-gateway-sync: [${wsAgentId}] 开始监听 MEMORY.md`
254
274
  );
275
+ // 启动时检查已有内容
276
+ scheduleSync(memoryMdPath, "startup");
255
277
  } catch (err) {
256
278
  api.logger.warn(
257
279
  `memory-gateway-sync: [${wsAgentId}] 无法监听 MEMORY.md: ${String(err)}`