memory-gateway-sync 0.4.0 → 0.6.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +37 -85
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-gateway-sync",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Memory 双写插件:fs.watch 感知变化后同步到外部 Gateway",
5
5
  "type": "module",
6
6
  "openclaw": {
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import crypto from "node:crypto";
4
5
  import os from "node:os";
5
6
 
6
7
  // ── 类型 ──────────────────────────────────��───────────────────────────────────
@@ -15,6 +16,7 @@ interface PluginConfig {
15
16
  interface IngestPayload {
16
17
  path: string;
17
18
  content: string;
19
+ release_name: string | null;
18
20
  agentId: string;
19
21
  userId: string | null;
20
22
  workspaceDir: string;
@@ -68,8 +70,15 @@ export default definePluginEntry({
68
70
  ReturnType<typeof setTimeout>
69
71
  >();
70
72
 
71
- // 已注册 watcher 的目录集合(避免重复监听)
72
- const watchedDirs = new Set<string>();
73
+ // 内容哈希 Map:key=文件绝对路径,value=上次同步内容的 MD5,用于去重
74
+ const lastSyncedHash = new Map<string, string>();
75
+
76
+ const parseReleaseName = (content: string): string | null => {
77
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
78
+ if (!match) return null;
79
+ const nameMatch = match[1].match(/^name:\s*(.+)$/m);
80
+ return nameMatch ? nameMatch[1].trim() : null;
81
+ };
73
82
 
74
83
  // 已注册的 workspace 集合(避免重复注册)
75
84
  const registeredWorkspaces = new Map<string, WorkspaceContext>();
@@ -121,6 +130,11 @@ export default definePluginEntry({
121
130
  return;
122
131
  }
123
132
 
133
+ const contentHash = crypto.createHash("md5").update(content).digest("hex");
134
+ if (lastSyncedHash.get(absPath) === contentHash) {
135
+ return;
136
+ }
137
+
124
138
  const relativePath = path.relative(ws.dir, absPath);
125
139
 
126
140
  // 根据路径解析 userId
@@ -138,6 +152,7 @@ export default definePluginEntry({
138
152
  const payload: IngestPayload = {
139
153
  path: relativePath,
140
154
  content,
155
+ release_name: parseReleaseName(content),
141
156
  agentId: ws.agentId,
142
157
  userId,
143
158
  workspaceDir: ws.dir,
@@ -162,9 +177,29 @@ export default definePluginEntry({
162
177
  clearTimeout(timeout);
163
178
 
164
179
  if (res.ok) {
180
+ lastSyncedHash.set(absPath, contentHash);
165
181
  api.logger.info?.(
166
182
  `memory-gateway-sync: [${ws.agentId}] 同步成功 ${relativePath} (${content.length} chars)`
167
183
  );
184
+
185
+ if (relativePath === "MEMORY.md" && content.trim().length > 0) {
186
+ try {
187
+ const backupPath = absPath + ".bak";
188
+ const timestamp = new Date().toISOString();
189
+ const section = `\n<!-- backup ${timestamp} -->\n${content}\n`;
190
+ await fs.promises.appendFile(backupPath, section, "utf-8");
191
+ await fs.promises.writeFile(absPath, "", "utf-8");
192
+ const emptyHash = crypto.createHash("md5").update("").digest("hex");
193
+ lastSyncedHash.set(absPath, emptyHash);
194
+ api.logger.info?.(
195
+ `memory-gateway-sync: [${ws.agentId}] MEMORY.md 已备份到 .bak 并清空`
196
+ );
197
+ } catch (err) {
198
+ api.logger.warn(
199
+ `memory-gateway-sync: [${ws.agentId}] 备份/清空 MEMORY.md 失败: ${String(err)}`
200
+ );
201
+ }
202
+ }
168
203
  } else {
169
204
  const body = await res.text().catch(() => "");
170
205
  api.logger.warn(
@@ -193,85 +228,6 @@ export default definePluginEntry({
193
228
  );
194
229
  };
195
230
 
196
- // ── 递归监听目录中的 .md 文件 ────────────────────────────────────
197
-
198
- const watchDirectory = (dirPath: string, wsDir: string): void => {
199
- if (watchedDirs.has(dirPath)) return;
200
- watchedDirs.add(dirPath);
201
-
202
- try {
203
- fs.watch(dirPath, async (eventType, filename) => {
204
- if (!filename) return;
205
-
206
- const absFilePath = path.join(dirPath, filename);
207
-
208
- try {
209
- const stat = await fs.promises.stat(absFilePath);
210
-
211
- if (stat.isDirectory()) {
212
- scanAndWatch(absFilePath, wsDir, true);
213
- return;
214
- }
215
-
216
- if (stat.isFile() && filename.endsWith(".md")) {
217
- scheduleSync(absFilePath, eventType ?? "change");
218
- }
219
- } catch {
220
- // stat 失败说明文件已删除,跳过
221
- }
222
- });
223
-
224
- const ws = registeredWorkspaces.get(wsDir);
225
- const label = ws ? ws.agentId : "?";
226
- api.logger.info?.(
227
- `memory-gateway-sync: [${label}] 监听目录 ${path.relative(wsDir, dirPath) || "memory/"}`
228
- );
229
- } catch (err) {
230
- api.logger.warn(
231
- `memory-gateway-sync: 无法监听目录 ${dirPath}: ${String(err)}`
232
- );
233
- }
234
- };
235
-
236
- // ── 递归扫描已存在的子目录并注册 watcher ─────────────────────────
237
-
238
- const scanAndWatch = async (
239
- dirPath: string,
240
- wsDir: string,
241
- syncExisting = false
242
- ): Promise<void> => {
243
- try {
244
- await fs.promises.mkdir(dirPath, { recursive: true });
245
- } catch {
246
- // ignore
247
- }
248
-
249
- watchDirectory(dirPath, wsDir);
250
-
251
- try {
252
- const entries = await fs.promises.readdir(dirPath, {
253
- withFileTypes: true,
254
- });
255
- for (const entry of entries) {
256
- if (entry.isDirectory()) {
257
- await scanAndWatch(
258
- path.join(dirPath, entry.name),
259
- wsDir,
260
- syncExisting
261
- );
262
- } else if (
263
- syncExisting &&
264
- entry.isFile() &&
265
- entry.name.endsWith(".md")
266
- ) {
267
- scheduleSync(path.join(dirPath, entry.name), "change");
268
- }
269
- }
270
- } catch {
271
- // ignore
272
- }
273
- };
274
-
275
231
  // ── 注册单个 workspace ───────────────────────────────────────────
276
232
 
277
233
  const registerWorkspace = async (wsDir: string, wsAgentId: string): Promise<void> => {
@@ -295,10 +251,6 @@ export default definePluginEntry({
295
251
  );
296
252
  }
297
253
 
298
- // 递归监听 memory/ 目录
299
- const memoryDirPath = path.join(wsDir, "memory");
300
- await scanAndWatch(memoryDirPath, wsDir);
301
-
302
254
  api.logger.info(
303
255
  `memory-gateway-sync: [${wsAgentId}] workspace 注册完成 → ${wsDir}`
304
256
  );