@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.
@@ -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="banner" id="banner"></div>
52
- </div>
53
- <div class="hero-actions">
54
- <div class="panel">
55
- <h2 id="activeConfigTitle">Active Config File</h2>
56
- <p class="muted" id="activeConfigText">The page automatically follows the current runtime profile and local config path.</p>
57
- <div class="path-box-row">
58
- <div class="path-box" id="pathBox"></div>
59
- <button class="icon-button" id="copyPathButton" type="button" aria-label="Copy Config Path" title="Copy Config Path">
60
- <span aria-hidden="true">⧉</span>
61
- </button>
62
- </div>
63
- <div class="button-row">
64
- <button class="btn btn-primary" id="saveButton">Save Config</button>
65
- <button class="btn btn-secondary" id="reloadButton">Reload From Disk</button>
66
- </div>
67
- </div>
68
- <div class="panel">
69
- <h2 id="editingModelTitle">Editing Model</h2>
70
- <p class="muted" id="editingModelText">
71
- Known plugin fields are rendered as typed controls. Unknown keys stay in the extra JSON section so
72
- future custom fields are not lost.
73
- </p>
74
- </div>
75
- </div>
76
- </div>
77
- </section>
78
- <main class="layout" id="layout"></main>
79
- <aside class="floating-tools" id="floatingTools">
80
- <div class="floating-nav-card">
81
- <div class="floating-nav-head">
82
- <span id="floatingNavLabel">Sections</span>
83
- <button class="floating-toggle" id="floatingToggleButton" type="button" aria-expanded="true" aria-controls="floatingNav" title="Collapse Navigation">
84
- <span class="floating-toggle-icon" aria-hidden="true">⇤</span>
85
- </button>
86
- </div>
87
- <div class="floating-nav" id="floatingNav"></div>
88
- </div>
89
- </aside>
90
- </div>
91
-
92
- <button class="back-top-button" id="backTopButton" type="button" aria-label="Back To Top" title="Back To Top">
93
- <span aria-hidden="true">↑</span>
94
- </button>
95
-
96
- <div class="overlay" id="overlay">
97
- <div class="overlay-card">
98
- <h4 id="overlayTitle">Restarting gateway</h4>
99
- <p id="overlayText">
100
- Restart requested. Refresh this page in a few seconds if it does not recover automatically.
101
- </p>
102
- <div class="overlay-actions">
103
- <button class="icon-button overlay-refresh-button" id="overlayRefreshButton" type="button" aria-label="Refresh Page" title="Refresh Page">
104
- <span aria-hidden="true">↻</span>
105
- </button>
106
- </div>
107
- </div>
108
- </div>
109
-
110
- <script src="__APP_JS_URL__"></script>
111
- </body>
112
- </html>
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>
@@ -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 { getConfigResolution } from "./memos-cloud-api.js";
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
- configPath: state.configPath,
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 loadAssetTemplate(name) {
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 = { profile, token, bootId, port: 0, url: "", server: null, stateCache: null, heartbeatCache: null };
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/save" && req.method === "POST") {
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)) || "{}");