@memtensor/memos-cloud-openclaw-plugin 0.1.17-beta.0 → 0.1.18-beta.1
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/LICENSE +201 -201
- package/README.md +316 -316
- package/README_ZH.md +321 -321
- package/clawdbot.plugin.json +1 -1
- package/index.js +844 -844
- package/lib/arms-reporter.js +116 -116
- package/lib/check-update.js +202 -187
- package/lib/config-resolution-schema.js +50 -50
- package/lib/config-ui/app.css +1022 -920
- package/lib/config-ui/app.js +1454 -1290
- package/lib/config-ui/index.html +113 -112
- package/lib/config-ui-server.js +71 -19
- package/lib/memos-cloud-api.js +882 -882
- package/moltbot.plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +46 -46
- package/scripts/sync-version.js +45 -45
- package/test/direct-session-user-id.test.mjs +125 -125
- package/test/query-strip.test.mjs +381 -381
- package/update.md +9 -0
package/lib/config-ui/index.html
CHANGED
|
@@ -1,112 +1,113 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
<title>MemOS Plugin Config</title>
|
|
7
|
-
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
|
8
|
-
<link rel="stylesheet" href="/app.css" />
|
|
9
|
-
</head>
|
|
10
|
-
<body>
|
|
11
|
-
<div class="shell">
|
|
12
|
-
<section class="hero">
|
|
13
|
-
<div class="hero-grid">
|
|
14
|
-
<div>
|
|
15
|
-
<div class="hero-top">
|
|
16
|
-
<span class="eyebrow" id="eyebrowText">
|
|
17
|
-
<img class="eyebrow-icon" src="/icon.svg" alt="" aria-hidden="true" />
|
|
18
|
-
<span>MemOS Plugin Config UI</span>
|
|
19
|
-
</span>
|
|
20
|
-
<div class="lang-switch">
|
|
21
|
-
<span class="lang-label" id="langLabel">Language</span>
|
|
22
|
-
<div class="lang-dropdown" id="languageDropdown">
|
|
23
|
-
<button
|
|
24
|
-
class="lang-select"
|
|
25
|
-
id="languageSelectButton"
|
|
26
|
-
type="button"
|
|
27
|
-
aria-haspopup="listbox"
|
|
28
|
-
aria-expanded="false"
|
|
29
|
-
aria-labelledby="langLabel languageSelectButton"
|
|
30
|
-
>
|
|
31
|
-
<span class="lang-select-text" id="languageSelectText">Auto</span>
|
|
32
|
-
</button>
|
|
33
|
-
<div class="lang-menu" id="languageMenu" role="listbox" aria-labelledby="langLabel">
|
|
34
|
-
<button class="lang-option" id="languageOptionAuto" type="button" role="option" data-language="auto">Auto</button>
|
|
35
|
-
<button class="lang-option" id="languageOptionEn" type="button" role="option" data-language="en">EN</button>
|
|
36
|
-
<button class="lang-option" id="languageOptionZh" type="button" role="option" data-language="zh">中文</button>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
<div class="hero-title-row">
|
|
42
|
-
<img class="hero-logo" src="/icon.svg" alt="MemOS Cloud OpenClaw Plugin icon" />
|
|
43
|
-
<h1 id="heroTitle">MemOS Cloud OpenClaw Plugin Config</h1>
|
|
44
|
-
</div>
|
|
45
|
-
<p class="subtitle" id="heroSubtitle">
|
|
46
|
-
This page reads and writes <code>plugins.entries.__PLUGIN_ID__.config</code> from the active gateway
|
|
47
|
-
config file, and keeps the form in sync with on-disk changes.
|
|
48
|
-
</p>
|
|
49
|
-
<div class="status-row" id="statusRow"></div>
|
|
50
|
-
<div class="meta-row" id="metaRow"></div>
|
|
51
|
-
<div class="
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<button class="btn btn-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
</
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>MemOS Plugin Config</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
|
8
|
+
<link rel="stylesheet" href="/app.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div class="shell">
|
|
12
|
+
<section class="hero">
|
|
13
|
+
<div class="hero-grid">
|
|
14
|
+
<div>
|
|
15
|
+
<div class="hero-top">
|
|
16
|
+
<span class="eyebrow" id="eyebrowText">
|
|
17
|
+
<img class="eyebrow-icon" src="/icon.svg" alt="" aria-hidden="true" />
|
|
18
|
+
<span>MemOS Plugin Config UI</span>
|
|
19
|
+
</span>
|
|
20
|
+
<div class="lang-switch">
|
|
21
|
+
<span class="lang-label" id="langLabel">Language</span>
|
|
22
|
+
<div class="lang-dropdown" id="languageDropdown">
|
|
23
|
+
<button
|
|
24
|
+
class="lang-select"
|
|
25
|
+
id="languageSelectButton"
|
|
26
|
+
type="button"
|
|
27
|
+
aria-haspopup="listbox"
|
|
28
|
+
aria-expanded="false"
|
|
29
|
+
aria-labelledby="langLabel languageSelectButton"
|
|
30
|
+
>
|
|
31
|
+
<span class="lang-select-text" id="languageSelectText">Auto</span>
|
|
32
|
+
</button>
|
|
33
|
+
<div class="lang-menu" id="languageMenu" role="listbox" aria-labelledby="langLabel">
|
|
34
|
+
<button class="lang-option" id="languageOptionAuto" type="button" role="option" data-language="auto">Auto</button>
|
|
35
|
+
<button class="lang-option" id="languageOptionEn" type="button" role="option" data-language="en">EN</button>
|
|
36
|
+
<button class="lang-option" id="languageOptionZh" type="button" role="option" data-language="zh">中文</button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="hero-title-row">
|
|
42
|
+
<img class="hero-logo" src="/icon.svg" alt="MemOS Cloud OpenClaw Plugin icon" />
|
|
43
|
+
<h1 id="heroTitle">MemOS Cloud OpenClaw Plugin Config</h1>
|
|
44
|
+
</div>
|
|
45
|
+
<p class="subtitle" id="heroSubtitle">
|
|
46
|
+
This page reads and writes <code>plugins.entries.__PLUGIN_ID__.config</code> from the active gateway
|
|
47
|
+
config file, and keeps the form in sync with on-disk changes.
|
|
48
|
+
</p>
|
|
49
|
+
<div class="status-row" id="statusRow"></div>
|
|
50
|
+
<div class="meta-row" id="metaRow"></div>
|
|
51
|
+
<div class="update-notice" id="updateNotice" hidden></div>
|
|
52
|
+
<div class="banner" id="banner"></div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="hero-actions">
|
|
55
|
+
<div class="panel">
|
|
56
|
+
<h2 id="activeConfigTitle">Active Config File</h2>
|
|
57
|
+
<p class="muted" id="activeConfigText">The page automatically follows the current runtime profile and local config path.</p>
|
|
58
|
+
<div class="path-box-row">
|
|
59
|
+
<div class="path-box" id="pathBox"></div>
|
|
60
|
+
<button class="icon-button" id="copyPathButton" type="button" aria-label="Copy Config Path" title="Copy Config Path">
|
|
61
|
+
<span aria-hidden="true">⧉</span>
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="button-row">
|
|
65
|
+
<button class="btn btn-primary" id="saveButton">Save Config</button>
|
|
66
|
+
<button class="btn btn-secondary" id="reloadButton">Reload From Disk</button>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="panel">
|
|
70
|
+
<h2 id="editingModelTitle">Editing Model</h2>
|
|
71
|
+
<p class="muted" id="editingModelText">
|
|
72
|
+
Known plugin fields are rendered as typed controls. Unknown keys stay in the extra JSON section so
|
|
73
|
+
future custom fields are not lost.
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</section>
|
|
79
|
+
<main class="layout" id="layout"></main>
|
|
80
|
+
<aside class="floating-tools" id="floatingTools">
|
|
81
|
+
<div class="floating-nav-card">
|
|
82
|
+
<div class="floating-nav-head">
|
|
83
|
+
<span id="floatingNavLabel">Sections</span>
|
|
84
|
+
<button class="floating-toggle" id="floatingToggleButton" type="button" aria-expanded="true" aria-controls="floatingNav" title="Collapse Navigation">
|
|
85
|
+
<span class="floating-toggle-icon" aria-hidden="true">⇤</span>
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="floating-nav" id="floatingNav"></div>
|
|
89
|
+
</div>
|
|
90
|
+
</aside>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<button class="back-top-button" id="backTopButton" type="button" aria-label="Back To Top" title="Back To Top">
|
|
94
|
+
<span aria-hidden="true">↑</span>
|
|
95
|
+
</button>
|
|
96
|
+
|
|
97
|
+
<div class="overlay" id="overlay">
|
|
98
|
+
<div class="overlay-card">
|
|
99
|
+
<h4 id="overlayTitle">Restarting gateway</h4>
|
|
100
|
+
<p id="overlayText">
|
|
101
|
+
Restart requested. Refresh this page in a few seconds if it does not recover automatically.
|
|
102
|
+
</p>
|
|
103
|
+
<div class="overlay-actions">
|
|
104
|
+
<button class="icon-button overlay-refresh-button" id="overlayRefreshButton" type="button" aria-label="Refresh Page" title="Refresh Page">
|
|
105
|
+
<span aria-hidden="true">↻</span>
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<script src="__APP_JS_URL__"></script>
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
package/lib/config-ui-server.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
|
|
2
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
-
import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { createServer } from "node:http";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { Script } from "node:vm";
|
|
9
|
-
import {
|
|
8
|
+
import { Script } from "node:vm";
|
|
9
|
+
import { checkForPluginUpdate, getPackageVersion } from "./check-update.js";
|
|
10
|
+
import { getConfigResolution } from "./memos-cloud-api.js";
|
|
10
11
|
|
|
11
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
|
|
@@ -21,7 +22,8 @@ const ANSI_CYAN = "\x1b[36m";
|
|
|
21
22
|
const ANSI_GREEN = "\x1b[32m";
|
|
22
23
|
const ANSI_YELLOW = "\x1b[33m";
|
|
23
24
|
const ANSI_RESET = "\x1b[0m";
|
|
24
|
-
const DEFAULT_GATEWAY_READY_PORT = 18789;
|
|
25
|
+
const DEFAULT_GATEWAY_READY_PORT = 18789;
|
|
26
|
+
const UPDATE_STATUS_CACHE_MS = 30 * 60 * 1000;
|
|
25
27
|
|
|
26
28
|
// Hook policies that the gateway requires non-bundled plugins to opt into.
|
|
27
29
|
// Without these, the gateway blocks `agent_end` (and other conversation-access
|
|
@@ -517,9 +519,10 @@ function buildStatePayload(service) {
|
|
|
517
519
|
const state = readGatewayConfig(service.profile);
|
|
518
520
|
const resolution = getConfigResolution(state.config);
|
|
519
521
|
return {
|
|
520
|
-
runtime: state.profile.id,
|
|
521
|
-
runtimeDisplayName: state.profile.displayName,
|
|
522
|
-
|
|
522
|
+
runtime: state.profile.id,
|
|
523
|
+
runtimeDisplayName: state.profile.displayName,
|
|
524
|
+
pluginVersion: getPackageVersion(),
|
|
525
|
+
configPath: state.configPath,
|
|
523
526
|
entryExists: state.entryExists,
|
|
524
527
|
fileExists: state.fileExists,
|
|
525
528
|
enabled: state.enabled,
|
|
@@ -548,7 +551,7 @@ function getCachedStatePayload(service, maxAgeMs = 1200) {
|
|
|
548
551
|
return payload;
|
|
549
552
|
}
|
|
550
553
|
|
|
551
|
-
function getCachedHeartbeatPayload(service, maxAgeMs = 1200) {
|
|
554
|
+
function getCachedHeartbeatPayload(service, maxAgeMs = 1200) {
|
|
552
555
|
const now = Date.now();
|
|
553
556
|
if (service.heartbeatCache && now - service.heartbeatCache.createdAt < maxAgeMs) {
|
|
554
557
|
return {
|
|
@@ -570,9 +573,41 @@ function getCachedHeartbeatPayload(service, maxAgeMs = 1200) {
|
|
|
570
573
|
...payload,
|
|
571
574
|
timestamp: now,
|
|
572
575
|
};
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async function getCachedUpdateStatusPayload(service, maxAgeMs = UPDATE_STATUS_CACHE_MS, options = {}) {
|
|
579
|
+
const now = Date.now();
|
|
580
|
+
if (!options.force && service.updateStatusCache && now - service.updateStatusCache.createdAt < maxAgeMs) {
|
|
581
|
+
return {
|
|
582
|
+
...service.updateStatusCache.payload,
|
|
583
|
+
cached: true,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
try {
|
|
588
|
+
const status = await checkForPluginUpdate();
|
|
589
|
+
const payload = {
|
|
590
|
+
ok: true,
|
|
591
|
+
...status,
|
|
592
|
+
cached: false,
|
|
593
|
+
};
|
|
594
|
+
service.updateStatusCache = {
|
|
595
|
+
createdAt: now,
|
|
596
|
+
payload,
|
|
597
|
+
};
|
|
598
|
+
return payload;
|
|
599
|
+
} catch (error) {
|
|
600
|
+
return {
|
|
601
|
+
ok: false,
|
|
602
|
+
currentVersion: getPackageVersion(),
|
|
603
|
+
error: String(error?.message || error),
|
|
604
|
+
checkedAt: new Date().toISOString(),
|
|
605
|
+
cached: false,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function loadAssetTemplate(name) {
|
|
576
611
|
return readFileSync(join(ASSET_DIR, name), "utf8");
|
|
577
612
|
}
|
|
578
613
|
|
|
@@ -827,7 +862,17 @@ async function createService(log) {
|
|
|
827
862
|
const token = randomBytes(24).toString("hex");
|
|
828
863
|
const bootId = randomBytes(10).toString("hex");
|
|
829
864
|
|
|
830
|
-
const service = {
|
|
865
|
+
const service = {
|
|
866
|
+
profile,
|
|
867
|
+
token,
|
|
868
|
+
bootId,
|
|
869
|
+
port: 0,
|
|
870
|
+
url: "",
|
|
871
|
+
server: null,
|
|
872
|
+
stateCache: null,
|
|
873
|
+
heartbeatCache: null,
|
|
874
|
+
updateStatusCache: null,
|
|
875
|
+
};
|
|
831
876
|
|
|
832
877
|
const server = createServer(async (req, res) => {
|
|
833
878
|
try {
|
|
@@ -872,12 +917,19 @@ async function createService(log) {
|
|
|
872
917
|
return;
|
|
873
918
|
}
|
|
874
919
|
|
|
875
|
-
if (requestUrl.pathname === "/api/state" && req.method === "GET") {
|
|
876
|
-
sendJson(res, 200, getCachedStatePayload(service));
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
if (requestUrl.pathname === "/api/
|
|
920
|
+
if (requestUrl.pathname === "/api/state" && req.method === "GET") {
|
|
921
|
+
sendJson(res, 200, getCachedStatePayload(service));
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (requestUrl.pathname === "/api/update-status" && req.method === "GET") {
|
|
926
|
+
sendJson(res, 200, await getCachedUpdateStatusPayload(service, UPDATE_STATUS_CACHE_MS, {
|
|
927
|
+
force: requestUrl.searchParams.get("force") === "1",
|
|
928
|
+
}));
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (requestUrl.pathname === "/api/save" && req.method === "POST") {
|
|
881
933
|
let parsed = {};
|
|
882
934
|
try {
|
|
883
935
|
parsed = JSON.parse((await readRequestBody(req)) || "{}");
|