memory-gateway-sync 0.5.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 +38 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-gateway-sync",
3
- "version": "0.5.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,6 +70,16 @@ export default definePluginEntry({
68
70
  ReturnType<typeof setTimeout>
69
71
  >();
70
72
 
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
+ };
82
+
71
83
  // 已注册的 workspace 集合(避免重复注册)
72
84
  const registeredWorkspaces = new Map<string, WorkspaceContext>();
73
85
 
@@ -118,6 +130,11 @@ export default definePluginEntry({
118
130
  return;
119
131
  }
120
132
 
133
+ const contentHash = crypto.createHash("md5").update(content).digest("hex");
134
+ if (lastSyncedHash.get(absPath) === contentHash) {
135
+ return;
136
+ }
137
+
121
138
  const relativePath = path.relative(ws.dir, absPath);
122
139
 
123
140
  // 根据路径解析 userId
@@ -135,6 +152,7 @@ export default definePluginEntry({
135
152
  const payload: IngestPayload = {
136
153
  path: relativePath,
137
154
  content,
155
+ release_name: parseReleaseName(content),
138
156
  agentId: ws.agentId,
139
157
  userId,
140
158
  workspaceDir: ws.dir,
@@ -159,9 +177,29 @@ export default definePluginEntry({
159
177
  clearTimeout(timeout);
160
178
 
161
179
  if (res.ok) {
180
+ lastSyncedHash.set(absPath, contentHash);
162
181
  api.logger.info?.(
163
182
  `memory-gateway-sync: [${ws.agentId}] 同步成功 ${relativePath} (${content.length} chars)`
164
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
+ }
165
203
  } else {
166
204
  const body = await res.text().catch(() => "");
167
205
  api.logger.warn(