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.
- package/package.json +1 -1
- package/src/index.ts +41 -2
package/package.json
CHANGED
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(),
|
|
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(
|