openclaw-memory-alibaba-local 0.1.3 → 0.1.5

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/API.md CHANGED
@@ -99,11 +99,17 @@
99
99
  | `memory.admin.delete` | 批量删除 | `items`: `{ agentId, id }[]` | 同原 `POST …/api/delete` |
100
100
  | `memory.admin.add` | 手工插入 | `agentId`, `text`, `category` | 同原 `POST …/api/add` |
101
101
 
102
- ### 1.3 废弃的 HTTP JSON
102
+ ### 1.3 HTTP 同源 RPC(公网 / 非本机,免 WS `operator.admin`)
103
103
 
104
104
  | 路径 | 行为 |
105
105
  |------|------|
106
- | `GET/POST {GATEWAY}/plugins/memory/api/*` | 若配置了 gateway token,仍校验 `?token=` `Authorization: Bearer`;校验通过后返回 **`410 Gone`**,body 提示改用 WebSocket `memory.admin.*`。 |
106
+ | `POST {GATEWAY}/plugins/memory/api/v1/call` | Body:`{"method":"memory.admin.list","params":{...}}`(`method` 与上表 WebSocket 方法名一致)。若配置了 `gateway.auth.token`,须 `Authorization: Bearer <token>` 或 URL `?token=`。成功时 HTTP 200,body 为与 WS `payload` 相同的 JSON;失败时 HTTP 状态码与 body 内 `error` 等与运维操作一致。 |
107
+
108
+ ### 1.4 废弃的旧版 HTTP JSON 路径
109
+
110
+ | 路径 | 行为 |
111
+ |------|------|
112
+ | `GET/POST {GATEWAY}/plugins/memory/api/config` 等旧路径(非 `api/v1/call`) | 若配置了 gateway token,仍校验 `?token=` 或 `Authorization: Bearer`;未命中 `v1/call` 时返回 **`404`**,请使用上表 **`POST …/api/v1/call`** 或 WebSocket `memory.admin.*`。 |
107
113
 
108
114
  ---
109
115
 
@@ -113,7 +119,7 @@
113
119
 
114
120
  | 项目 | 说明 |
115
121
  |------|------|
116
- | **功能** | 返回记忆管理端单页 HTML(壳页面;数据由 `/api/*` 拉取) |
122
+ | **功能** | 返回记忆管理端单页 HTML(壳页面;本机数据可走 WebSocket `memory.admin.*`,**非本机 hostname** 时面板走 **`POST /plugins/memory/api/v1/call`**) |
117
123
  | **URI** | `{GATEWAY}/plugins/memory` |
118
124
 
119
125
  **入参**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-memory-alibaba-local",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "OpenClaw memory plugin: local LanceDB + DashScope-compatible embeddings",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,6 +18,7 @@ import {
18
18
  opMemoryAdminDelete,
19
19
  opMemoryAdminFacets,
20
20
  opMemoryAdminList,
21
+ type AdminOpResult,
21
22
  type MemoryAdminOpsContext,
22
23
  type MemoryAdminOpsOpts,
23
24
  } from "./memory-admin-ops.js";
@@ -83,7 +84,7 @@ function sendHtml(res: ServerResponse, html: string): void {
83
84
  res.end(html);
84
85
  }
85
86
 
86
- function applyAdminOpResult(respond: GatewayRequestHandlerOptions["respond"], result: import("./memory-admin-ops.js").AdminOpResult): void {
87
+ function applyAdminOpResult(respond: GatewayRequestHandlerOptions["respond"], result: AdminOpResult): void {
87
88
  if (result.ok) {
88
89
  respond(true, result.data);
89
90
  return;
@@ -168,8 +169,49 @@ export function registerMemoryAdminGatewayMethods(
168
169
  logger.info("[openclaw-memory-alibaba-local] Memory admin Gateway methods: memory.admin.* (config, facets, dashboard, list, delete, add)");
169
170
  }
170
171
 
172
+ async function readJsonBody(req: IncomingMessage, maxBytes = 2 * 1024 * 1024): Promise<string> {
173
+ return new Promise((resolve, reject) => {
174
+ const chunks: Buffer[] = [];
175
+ let n = 0;
176
+ req.on("data", (chunk: Buffer) => {
177
+ n += chunk.length;
178
+ if (n > maxBytes) {
179
+ req.destroy();
180
+ reject(new Error("body too large"));
181
+ return;
182
+ }
183
+ chunks.push(chunk);
184
+ });
185
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
186
+ req.on("error", reject);
187
+ });
188
+ }
189
+
190
+ async function dispatchMemoryAdminRpc(
191
+ ctx: MemoryAdminOpsContext,
192
+ method: string,
193
+ params: unknown,
194
+ ): Promise<AdminOpResult> {
195
+ switch (method) {
196
+ case "memory.admin.config":
197
+ return opMemoryAdminConfig(ctx);
198
+ case "memory.admin.facets":
199
+ return opMemoryAdminFacets(ctx);
200
+ case "memory.admin.dashboard":
201
+ return opMemoryAdminDashboard(ctx, (params as Record<string, unknown>) ?? {});
202
+ case "memory.admin.list":
203
+ return opMemoryAdminList(ctx, (params as Record<string, unknown>) ?? {});
204
+ case "memory.admin.delete":
205
+ return opMemoryAdminDelete(ctx, (params as Record<string, unknown>) ?? {});
206
+ case "memory.admin.add":
207
+ return opMemoryAdminAdd(ctx, (params as Record<string, unknown>) ?? {});
208
+ default:
209
+ return { ok: false, status: 404, body: { error: "unknown method", method } };
210
+ }
211
+ }
212
+
171
213
  /**
172
- * HTTP: serves HTML at /plugins/memory; legacy /plugins/memory/api/* returns 410 with migration hint.
214
+ * HTTP: HTML at /plugins/memory;同源 HTTP POST /plugins/memory/api/v1/call 供非本机访问(仅需 gateway token,不经 WS operator scope)。
173
215
  */
174
216
  export function registerMemoryPanelRoutes(
175
217
  registerHttpRoute: RegisterHttpRoute,
@@ -178,9 +220,11 @@ export function registerMemoryPanelRoutes(
178
220
  logger: PluginLogger,
179
221
  opts?: MemoryPanelRoutesOpts | null,
180
222
  ): void {
181
- void db;
182
- void cfg;
183
- void opts;
223
+ const ctxBase: MemoryAdminOpsContext = {
224
+ db,
225
+ cfg,
226
+ opts: opts ?? undefined,
227
+ };
184
228
  const requiredToken = resolveGatewayToken();
185
229
  const token = typeof requiredToken === "string" && requiredToken.length > 0 ? requiredToken : undefined;
186
230
 
@@ -204,9 +248,44 @@ export function registerMemoryPanelRoutes(
204
248
  return true;
205
249
  }
206
250
  }
207
- sendJson(res, 410, {
208
- error: "HTTP JSON API removed",
209
- detail: "Use Gateway WebSocket RPC methods: memory.admin.config, memory.admin.facets, memory.admin.dashboard, memory.admin.list, memory.admin.delete, memory.admin.add",
251
+
252
+ if (p === "/plugins/memory/api/v1/call" && req.method === "POST") {
253
+ let raw: string;
254
+ try {
255
+ raw = await readJsonBody(req);
256
+ } catch {
257
+ sendJson(res, 400, { error: "invalid body" });
258
+ return true;
259
+ }
260
+ let body: { method?: string; params?: unknown };
261
+ try {
262
+ body = JSON.parse(raw || "{}") as { method?: string; params?: unknown };
263
+ } catch {
264
+ sendJson(res, 400, { error: "invalid json" });
265
+ return true;
266
+ }
267
+ const method = typeof body.method === "string" ? body.method.trim() : "";
268
+ if (!method) {
269
+ sendJson(res, 400, { error: "missing method" });
270
+ return true;
271
+ }
272
+ try {
273
+ const result = await dispatchMemoryAdminRpc(ctxBase, method, body.params ?? {});
274
+ if (result.ok) {
275
+ sendJson(res, 200, result.data as unknown);
276
+ } else {
277
+ sendJson(res, result.status, { ...result.body, status: result.status });
278
+ }
279
+ } catch (e) {
280
+ logger.warn(`memory api/v1/call ${method}: ${String(e)}`);
281
+ sendJson(res, 500, { error: String(e) });
282
+ }
283
+ return true;
284
+ }
285
+
286
+ sendJson(res, 404, {
287
+ error: "not found",
288
+ detail: "POST /plugins/memory/api/v1/call with JSON { method, params } (same as memory.admin.* WebSocket RPC)",
210
289
  });
211
290
  return true;
212
291
  }
@@ -221,5 +300,7 @@ export function registerMemoryPanelRoutes(
221
300
  },
222
301
  });
223
302
 
224
- logger.info("[openclaw-memory-alibaba-local] Memory admin UI at /plugins/memory/ (data via WebSocket memory.admin.*)");
303
+ logger.info(
304
+ "[openclaw-memory-alibaba-local] Memory admin UI at /plugins/memory/ (WS memory.admin.* on loopback; HTTP POST .../api/v1/call for remote)",
305
+ );
225
306
  }
package/web/memory-ui.ts CHANGED
@@ -797,6 +797,20 @@ function buildClientScript(): string {
797
797
  // 外层是模板字符串反引号:内层不要用 \ 转义双引号,否则生成到浏览器里的 JS 会断串(例如 class="mono")。
798
798
  const s = `
799
799
  (function () {
800
+ // 与 rds-claw-observability-plugin 一致:127.0.0.1 与 localhost 在网关 Control UI / WS 策略上不等价,统一跳到 localhost。
801
+ try {
802
+ if (window.location.hostname === "127.0.0.1") {
803
+ var _ocRedir =
804
+ window.location.protocol +
805
+ "//localhost" +
806
+ (window.location.port ? ":" + window.location.port : "") +
807
+ window.location.pathname +
808
+ window.location.search +
809
+ window.location.hash;
810
+ window.location.replace(_ocRedir);
811
+ }
812
+ } catch (_ocRedirErr) {}
813
+
800
814
  var LS_TOKEN_KEY = "openclaw_memory_gateway_token";
801
815
 
802
816
  function readTokenFromQueryString(qs) {
@@ -807,12 +821,60 @@ function buildClientScript(): string {
807
821
  return t && String(t).trim() ? String(t).trim() : "";
808
822
  }
809
823
 
824
+ /** 与观测插件一致:支持 JSON 包一层 token / gatewayToken / settings.* */
825
+ function tryTokenFromJsonString(raw) {
826
+ if (!raw || typeof raw !== "string") {
827
+ return "";
828
+ }
829
+ var st = raw.trim();
830
+ if (!st) {
831
+ return "";
832
+ }
833
+ if (st.charAt(0) === "{") {
834
+ try {
835
+ var obj = JSON.parse(st);
836
+ if (obj && typeof obj === "object") {
837
+ if (typeof obj.token === "string" && obj.token.trim()) {
838
+ return obj.token.trim();
839
+ }
840
+ if (typeof obj.gatewayToken === "string" && obj.gatewayToken.trim()) {
841
+ return obj.gatewayToken.trim();
842
+ }
843
+ if (obj.settings && typeof obj.settings === "object") {
844
+ if (typeof obj.settings.token === "string" && obj.settings.token.trim()) {
845
+ return obj.settings.token.trim();
846
+ }
847
+ if (typeof obj.settings.gatewayToken === "string" && obj.settings.gatewayToken.trim()) {
848
+ return obj.settings.gatewayToken.trim();
849
+ }
850
+ }
851
+ }
852
+ } catch (e) {}
853
+ }
854
+ return st;
855
+ }
856
+
857
+ function readTokenFromStorage(storage, key) {
858
+ try {
859
+ if (!storage || typeof storage.getItem !== "function") {
860
+ return "";
861
+ }
862
+ return tryTokenFromJsonString(storage.getItem(key) || "");
863
+ } catch (e) {
864
+ return "";
865
+ }
866
+ }
867
+
868
+ function persistToken(t) {
869
+ try {
870
+ localStorage.setItem(LS_TOKEN_KEY, t);
871
+ } catch (e) {}
872
+ }
873
+
810
874
  function getGatewayToken() {
811
875
  var fromSearch = readTokenFromQueryString(window.location.search);
812
876
  if (fromSearch) {
813
- try {
814
- localStorage.setItem(LS_TOKEN_KEY, fromSearch);
815
- } catch (e) {}
877
+ persistToken(fromSearch);
816
878
  return fromSearch;
817
879
  }
818
880
  var hash = window.location.hash || "";
@@ -820,15 +882,35 @@ function buildClientScript(): string {
820
882
  if (qm >= 0) {
821
883
  var fromHash = readTokenFromQueryString(hash.slice(qm));
822
884
  if (fromHash) {
823
- try {
824
- localStorage.setItem(LS_TOKEN_KEY, fromHash);
825
- } catch (e) {}
885
+ persistToken(fromHash);
826
886
  return fromHash;
827
887
  }
828
888
  }
889
+ var ocKeys = [
890
+ "openclaw.control.token.v1",
891
+ "openclaw.control.settings.v1",
892
+ "openclaw.gateway.token",
893
+ "gateway.token",
894
+ "gatewayToken",
895
+ "token"
896
+ ];
897
+ var i;
898
+ for (i = 0; i < ocKeys.length; i++) {
899
+ var tk = readTokenFromStorage(window.localStorage, ocKeys[i]);
900
+ if (tk) {
901
+ persistToken(tk);
902
+ return tk;
903
+ }
904
+ tk = readTokenFromStorage(window.sessionStorage, ocKeys[i]);
905
+ if (tk) {
906
+ persistToken(tk);
907
+ return tk;
908
+ }
909
+ }
829
910
  try {
830
- var s = localStorage.getItem(LS_TOKEN_KEY);
831
- return s && String(s).trim() ? String(s).trim() : "";
911
+ var legacy = localStorage.getItem(LS_TOKEN_KEY);
912
+ legacy = legacy ? tryTokenFromJsonString(legacy) : "";
913
+ return legacy && String(legacy).trim() ? String(legacy).trim() : "";
832
914
  } catch (e) {
833
915
  return "";
834
916
  }
@@ -855,7 +937,7 @@ function buildClientScript(): string {
855
937
  if (isMemoryPanelLoopbackHost()) {
856
938
  return (
857
939
  " 解决办法:编辑 ~/.openclaw/openclaw.json,在 gateway.controlUi 中加入 \\"allowInsecureAuth\\": true,保存后执行 openclaw gateway restart,再刷新本页。" +
858
- " 请用 http://127.0.0.1 http://localhost 打开面板(不要用局域网 IP,除非按下文配置)。"
940
+ " 请用 http://localhost 打开本机面板(从 127.0.0.1 访问会自动跳转);不要用局域网 IP,除非按下文配置。"
859
941
  );
860
942
  }
861
943
  return (
@@ -1020,10 +1102,76 @@ function buildClientScript(): string {
1020
1102
  });
1021
1103
  }
1022
1104
 
1105
+ /**
1106
+ * 非本机 hostname(公网 IP / 域名)时 Gateway WebSocket 会剥离 operator.admin;同源 HTTP 仅需 gateway token(与观测插件一致)。
1107
+ */
1108
+ async function fetchMemoryApiHttp(method, params) {
1109
+ var tok = getGatewayToken();
1110
+ var headers = { "Content-Type": "application/json" };
1111
+ if (tok) {
1112
+ headers["Authorization"] = "Bearer " + tok;
1113
+ }
1114
+ var resp;
1115
+ try {
1116
+ resp = await fetch("/plugins/memory/api/v1/call", {
1117
+ method: "POST",
1118
+ headers: headers,
1119
+ body: JSON.stringify({ method: method, params: params || {} })
1120
+ });
1121
+ } catch (e) {
1122
+ return {
1123
+ ok: false,
1124
+ status: 0,
1125
+ json: function () {
1126
+ return Promise.resolve({ error: String(e) });
1127
+ },
1128
+ text: function () {
1129
+ return Promise.resolve(String(e));
1130
+ }
1131
+ };
1132
+ }
1133
+ var j;
1134
+ try {
1135
+ j = await resp.json();
1136
+ } catch (e2) {
1137
+ j = { error: "invalid JSON response" };
1138
+ }
1139
+ if (resp.ok) {
1140
+ return {
1141
+ ok: true,
1142
+ status: 200,
1143
+ json: function () {
1144
+ return Promise.resolve(j);
1145
+ },
1146
+ text: function () {
1147
+ return Promise.resolve("");
1148
+ }
1149
+ };
1150
+ }
1151
+ var st = resp.status;
1152
+ if (j && typeof j.status === "number" && Number.isFinite(j.status)) {
1153
+ st = j.status;
1154
+ }
1155
+ return {
1156
+ ok: false,
1157
+ status: st,
1158
+ json: function () {
1159
+ return Promise.resolve(j);
1160
+ },
1161
+ text: function () {
1162
+ return Promise.resolve(String((j && j.error) || resp.status));
1163
+ }
1164
+ };
1165
+ }
1166
+
1023
1167
  /** 与原先 fetch 类似的接口:ok / status / json() / text() */
1024
1168
  async function fetchMemoryApi(method, params) {
1169
+ var p = params || {};
1170
+ if (!isMemoryPanelLoopbackHost()) {
1171
+ return fetchMemoryApiHttp(method, p);
1172
+ }
1025
1173
  try {
1026
- var res = await gatewayRpc(method, params || {});
1174
+ var res = await gatewayRpc(method, p);
1027
1175
  if (res.ok) {
1028
1176
  return {
1029
1177
  ok: true,
@@ -1036,6 +1184,12 @@ function buildClientScript(): string {
1036
1184
  }
1037
1185
  };
1038
1186
  }
1187
+ if (res.unauthorized) {
1188
+ var httpRes = await fetchMemoryApiHttp(method, p);
1189
+ if (httpRes.ok) {
1190
+ return httpRes;
1191
+ }
1192
+ }
1039
1193
  var st = res.payload && res.payload.status ? parseInt(String(res.payload.status), 10) : 0;
1040
1194
  if (!Number.isFinite(st) || st < 400) {
1041
1195
  st = res.unauthorized ? 403 : 502;