memory-gateway-sync 0.5.0 → 0.7.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 +41 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-gateway-sync",
3
- "version": "0.5.0",
3
+ "version": "0.7.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;
@@ -44,11 +46,12 @@ export default definePluginEntry({
44
46
  const defaultAgentId = cfg.agentId ?? "default";
45
47
  const ownerClawUserId = (() => {
46
48
  const token = process.env.OPENCLAW_GATEWAY_TOKEN ?? "";
47
- // 格式: oc-user-{userId},用 - 切开取最后一段
48
49
  const parts = token.split("-");
49
50
  return parts.length >= 3 ? parts[parts.length - 1] : null;
50
51
  })();
51
52
 
53
+ const podReleaseName = process.env.botID ?? null;
54
+
52
55
  if (!gatewayUrl || !gatewayToken) {
53
56
  api.logger.warn(
54
57
  "memory-gateway-sync: gatewayUrl 或 gatewayToken 未配置,插件不启动"
@@ -68,6 +71,16 @@ export default definePluginEntry({
68
71
  ReturnType<typeof setTimeout>
69
72
  >();
70
73
 
74
+ // 内容哈希 Map:key=文件绝对路径,value=上次同步内容的 MD5,用于去重
75
+ const lastSyncedHash = new Map<string, string>();
76
+
77
+ const parseReleaseName = (content: string): string | null => {
78
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
79
+ if (!match) return null;
80
+ const nameMatch = match[1].match(/^name:\s*(.+)$/m);
81
+ return nameMatch ? nameMatch[1].trim() : null;
82
+ };
83
+
71
84
  // 已注册的 workspace 集合(避免重复注册)
72
85
  const registeredWorkspaces = new Map<string, WorkspaceContext>();
73
86
 
@@ -118,6 +131,11 @@ export default definePluginEntry({
118
131
  return;
119
132
  }
120
133
 
134
+ const contentHash = crypto.createHash("md5").update(content).digest("hex");
135
+ if (lastSyncedHash.get(absPath) === contentHash) {
136
+ return;
137
+ }
138
+
121
139
  const relativePath = path.relative(ws.dir, absPath);
122
140
 
123
141
  // 根据路径解析 userId
@@ -135,6 +153,7 @@ export default definePluginEntry({
135
153
  const payload: IngestPayload = {
136
154
  path: relativePath,
137
155
  content,
156
+ release_name: parseReleaseName(content) ?? podReleaseName,
138
157
  agentId: ws.agentId,
139
158
  userId,
140
159
  workspaceDir: ws.dir,
@@ -144,7 +163,7 @@ export default definePluginEntry({
144
163
 
145
164
  try {
146
165
  const controller = new AbortController();
147
- const timeout = setTimeout(() => controller.abort(), 5000);
166
+ const timeout = setTimeout(() => controller.abort(), 30000);
148
167
 
149
168
  const res = await fetch(gatewayUrl, {
150
169
  method: "POST",
@@ -159,9 +178,29 @@ export default definePluginEntry({
159
178
  clearTimeout(timeout);
160
179
 
161
180
  if (res.ok) {
181
+ lastSyncedHash.set(absPath, contentHash);
162
182
  api.logger.info?.(
163
183
  `memory-gateway-sync: [${ws.agentId}] 同步成功 ${relativePath} (${content.length} chars)`
164
184
  );
185
+
186
+ if (relativePath === "MEMORY.md" && content.trim().length > 0) {
187
+ try {
188
+ const backupPath = absPath + ".bak";
189
+ const timestamp = new Date().toISOString();
190
+ const section = `\n<!-- backup ${timestamp} -->\n${content}\n`;
191
+ await fs.promises.appendFile(backupPath, section, "utf-8");
192
+ await fs.promises.writeFile(absPath, "", "utf-8");
193
+ const emptyHash = crypto.createHash("md5").update("").digest("hex");
194
+ lastSyncedHash.set(absPath, emptyHash);
195
+ api.logger.info?.(
196
+ `memory-gateway-sync: [${ws.agentId}] MEMORY.md 已备份到 .bak 并清空`
197
+ );
198
+ } catch (err) {
199
+ api.logger.warn(
200
+ `memory-gateway-sync: [${ws.agentId}] 备份/清空 MEMORY.md 失败: ${String(err)}`
201
+ );
202
+ }
203
+ }
165
204
  } else {
166
205
  const body = await res.text().catch(() => "");
167
206
  api.logger.warn(