aicq-openclaw-plugin 1.0.5 → 1.0.7

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/dist/index.js CHANGED
@@ -7826,12 +7826,12 @@ var v4_default = v4;
7826
7826
 
7827
7827
  // dist/config.js
7828
7828
  var __filename = fileURLToPath(import.meta.url);
7829
- var __dirname = path.dirname(__filename);
7829
+ var __dirname2 = path.dirname(__filename);
7830
7830
  var SERVER_URL = process.env.AICQ_SERVER_URL || "https://aicq.online:61018";
7831
7831
  function loadConfig(overrides) {
7832
7832
  let schemaDefaults = {};
7833
7833
  try {
7834
- const manifestPath = path.resolve(__dirname, "..", "openclaw.plugin.json");
7834
+ const manifestPath = path.resolve(__dirname2, "..", "openclaw.plugin.json");
7835
7835
  const manifestRaw = fs.readFileSync(manifestPath, "utf-8");
7836
7836
  const manifest = JSON.parse(manifestRaw);
7837
7837
  const schema = manifest.configSchema;
@@ -10114,14 +10114,37 @@ function createToastContainer() {
10114
10114
  return c;
10115
10115
  }
10116
10116
 
10117
- // \u2500\u2500 API \u2500\u2500
10117
+ // \u2500\u2500 API with timeout \u2500\u2500
10118
10118
  async function api(path, opts = {}) {
10119
+ const timeout = (opts._timeout || 8000);
10120
+ const controller = new AbortController();
10121
+ const timer = setTimeout(() => controller.abort(), timeout);
10119
10122
  try {
10120
- const res = await fetch(API + path, { headers: { 'Content-Type': 'application/json', ...opts.headers }, ...opts });
10123
+ const res = await fetch(API + path, { headers: { 'Content-Type': 'application/json', ...opts.headers }, ...opts, signal: controller.signal });
10124
+ clearTimeout(timer);
10121
10125
  const data = await res.json();
10122
10126
  if (!res.ok && !data.error) data.error = 'HTTP ' + res.status;
10123
10127
  return data;
10124
- } catch (e) { return { error: e.message }; }
10128
+ } catch (e) {
10129
+ clearTimeout(timer);
10130
+ const msg = e.name === 'AbortError' ? 'Request timed out (' + (timeout/1000) + 's)' : e.message;
10131
+ return { error: msg };
10132
+ }
10133
+ }
10134
+
10135
+ // Safe helper: run promise, never throw, return { data, error }
10136
+ async function safeApi(path, opts) {
10137
+ try {
10138
+ const data = await api(path, opts);
10139
+ return { data, error: data.error || null };
10140
+ } catch (e) {
10141
+ return { data: null, error: e.message || 'Unknown error' };
10142
+ }
10143
+ }
10144
+
10145
+ // Render an inline error block
10146
+ function errorBlock(title, msg) {
10147
+ return '<div class="card" style="border-color:var(--danger)"><div style="display:flex;align-items:center;gap:10px"><span style="font-size:20px">\u26A0\uFE0F</span><div><div style="font-weight:600;color:var(--danger)">' + escHtml(title) + '</div><div style="font-size:12px;color:var(--text3);margin-top:2px">' + escHtml(msg) + '</div></div></div></div>';
10125
10148
  }
10126
10149
 
10127
10150
  // \u2500\u2500 Utilities \u2500\u2500
@@ -10169,62 +10192,86 @@ function loadPage(page) {
10169
10192
  }
10170
10193
 
10171
10194
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
10172
- // PAGE: Dashboard
10195
+ // PAGE: Dashboard \u2014 render skeleton immediately, load data async
10173
10196
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
10174
10197
  async function loadDashboard() {
10175
10198
  const el = $('#dashboard-content');
10176
- html(el, '<div class="loading-mask"><div class="spinner"></div>Loading dashboard...</div>');
10177
- const [status, friends, identity] = await Promise.all([api('/status'), api('/friends'), api('/identity')]);
10178
- if (status.error) { html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>Failed to connect to AICQ plugin</p></div>'); return; }
10179
- const connCls = status.connected ? 'dot-ok' : 'dot-err';
10180
- const connText = status.connected ? 'Connected' : 'Disconnected';
10181
- const friendList = friends.friends || [];
10182
- const aiFriends = friendList.filter(f => f.friendType === 'ai').length;
10183
- const humanFriends = friendList.filter(f => f.friendType !== 'ai').length;
10184
-
10199
+ // Render skeleton immediately \u2014 never show spinner that blocks UI
10185
10200
  html(el, \\\`
10186
10201
  <div class="stats-grid">
10187
- <div class="stat-card">
10188
- <div class="stat-icon" style="background:var(--accent-bg)">\u{1F4E1}</div>
10189
- <div class="stat-label">Server Status</div>
10190
- <div class="stat-value" style="font-size:16px;display:flex;align-items:center;gap:8px">
10191
- <span class="dot \${connCls}"></span> \${connText}
10192
- </div>
10193
- <div class="stat-sub">\${escHtml(status.serverUrl)}</div>
10194
- </div>
10195
- <div class="stat-card">
10196
- <div class="stat-icon" style="background:var(--ok-bg)">\u{1F465}</div>
10197
- <div class="stat-label">Total Friends</div>
10198
- <div class="stat-value">\${friendList.length}</div>
10199
- <div class="stat-sub">\${aiFriends} AI \xB7 \${humanFriends} Human</div>
10200
- </div>
10201
- <div class="stat-card">
10202
- <div class="stat-icon" style="background:var(--info-bg)">\u{1F517}</div>
10203
- <div class="stat-label">Active Sessions</div>
10204
- <div class="stat-value">\${status.sessionCount || 0}</div>
10205
- <div class="stat-sub">Encrypted sessions</div>
10206
- </div>
10207
- <div class="stat-card">
10208
- <div class="stat-icon" style="background:var(--warn-bg)">\u{1F511}</div>
10209
- <div class="stat-label">Agent ID</div>
10210
- <div class="stat-value mono" style="font-size:13px">\${escHtml(status.agentId)}</div>
10211
- <div class="stat-sub">Fingerprint: \${escHtml(status.fingerprint)}</div>
10212
- </div>
10202
+ <div class="stat-card"><div class="stat-icon" style="background:var(--accent-bg)">\u{1F4E1}</div><div class="stat-label">Server Status</div><div class="stat-value" style="font-size:16px"><div class="spinner" style="width:16px;height:16px;border-width:2px;margin:0 auto"></div></div><div class="stat-sub" id="dash-server-url">Loading...</div></div>
10203
+ <div class="stat-card"><div class="stat-icon" style="background:var(--ok-bg)">\u{1F465}</div><div class="stat-label">Total Friends</div><div class="stat-value" id="dash-friend-count"><div class="spinner" style="width:16px;height:16px;border-width:2px;margin:0 auto"></div></div><div class="stat-sub" id="dash-friend-sub">loading...</div></div>
10204
+ <div class="stat-card"><div class="stat-icon" style="background:var(--info-bg)">\u{1F517}</div><div class="stat-label">Active Sessions</div><div class="stat-value" id="dash-sessions"><div class="spinner" style="width:16px;height:16px;border-width:2px;margin:0 auto"></div></div><div class="stat-sub">Encrypted sessions</div></div>
10205
+ <div class="stat-card"><div class="stat-icon" style="background:var(--warn-bg)">\u{1F511}</div><div class="stat-label">Agent ID</div><div class="stat-value mono" style="font-size:13px" id="dash-agent-id">\u2014</div><div class="stat-sub" id="dash-fingerprint">\u2014</div></div>
10213
10206
  </div>
10214
10207
  <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
10215
- <div class="card">
10216
- <div class="card-header"><div class="card-title">\u{1F4CB} Recent Friends</div><button class="btn btn-sm btn-ghost" onclick="navigate('friends')">View All \u2192</button></div>
10217
- \${renderMiniFriendList(friendList.slice(0, 5))}
10218
- </div>
10219
- <div class="card">
10220
- <div class="card-header"><div class="card-title">\u{1F916} Identity Info</div></div>
10221
- <div class="detail-row"><div class="detail-key">Agent ID</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${identity.agentId}')">\${escHtml(identity.agentId)} \u{1F4CB}</div></div>
10222
- <div class="detail-row"><div class="detail-key">Fingerprint</div><div class="detail-val mono">\${escHtml(identity.publicKeyFingerprint)}</div></div>
10223
- <div class="detail-row"><div class="detail-key">Server URL</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${identity.serverUrl}')">\${escHtml(identity.serverUrl)} \u{1F4CB}</div></div>
10224
- <div class="detail-row"><div class="detail-key">Connection</div><div class="detail-val"><span class="badge badge-\${identity.connected ? 'ok' : 'danger'}">\${identity.connected ? 'Online' : 'Offline'}</span></div></div>
10225
- </div>
10208
+ <div class="card"><div class="card-header"><div class="card-title">\u{1F4CB} Friends</div><button class="btn btn-sm btn-ghost" onclick="navigate('friends')">View All \u2192</button></div><div id="dash-friends-list"><div class="loading-mask" style="padding:20px"><div class="spinner"></div>Loading friends...</div></div></div>
10209
+ <div class="card"><div class="card-header"><div class="card-title">\u{1F916} Identity Info</div></div><div id="dash-identity"><div class="loading-mask" style="padding:20px"><div class="spinner"></div>Loading...</div></div></div>
10226
10210
  </div>
10227
10211
  \\\`);
10212
+
10213
+ // Load each data source independently \u2014 no await blocking between them
10214
+ const statusRes = await safeApi('/status', { _timeout: 5000 });
10215
+ const status = statusRes.data || {};
10216
+
10217
+ // Update status card immediately
10218
+ const connCls = status.connected ? 'dot-ok' : 'dot-err';
10219
+ const connText = status.connected ? 'Connected' : 'Disconnected';
10220
+ const connEl = $('#dash-server-url');
10221
+ if (connEl) connEl.textContent = status.serverUrl || '\u2014';
10222
+ const statusValEl = el.querySelector('.stat-value');
10223
+ if (statusValEl) statusValEl.innerHTML = '<span class="dot ' + connCls + '" style="display:inline-block;width:8px;height:8px;vertical-align:middle"></span> <span style="font-size:16px">' + connText + '</span>';
10224
+
10225
+ // Update header dot
10226
+ const hDot = $('#header-dot');
10227
+ if (hDot) hDot.className = 'dot ' + connCls;
10228
+ const hTxt = $('#header-status');
10229
+ if (hTxt) hTxt.textContent = connText;
10230
+
10231
+ // Agent ID card
10232
+ const agentEl = $('#dash-agent-id');
10233
+ if (agentEl) agentEl.textContent = status.agentId || '\u2014';
10234
+ const fpEl = $('#dash-fingerprint');
10235
+ if (fpEl) fpEl.textContent = 'Fingerprint: ' + (status.fingerprint || '\u2014');
10236
+ const sessEl = $('#dash-sessions');
10237
+ if (sessEl) sessEl.textContent = status.sessionCount || 0;
10238
+
10239
+ // Identity \u2014 load independently
10240
+ safeApi('/identity', { _timeout: 5000 }).then(identityRes => {
10241
+ const identityEl = $('#dash-identity');
10242
+ if (!identityEl) return;
10243
+ if (identityRes.error) {
10244
+ identityEl.innerHTML = errorBlock('Identity Unavailable', identityRes.error + ' <button class="btn btn-sm btn-default" style="margin-top:8px" onclick="loadDashboard()">\u{1F504} Retry</button>');
10245
+ return;
10246
+ }
10247
+ const identity = identityRes.data || {};
10248
+ identityEl.innerHTML = \\\`
10249
+ <div class="detail-row"><div class="detail-key">Agent ID</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${escHtml(identity.agentId || '')}')">\${escHtml(identity.agentId || '\u2014')} \u{1F4CB}</div></div>
10250
+ <div class="detail-row"><div class="detail-key">Fingerprint</div><div class="detail-val mono">\${escHtml(identity.publicKeyFingerprint || '\u2014')}</div></div>
10251
+ <div class="detail-row"><div class="detail-key">Server URL</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${escHtml(identity.serverUrl || '')}')">\${escHtml(identity.serverUrl || '\u2014')} \u{1F4CB}</div></div>
10252
+ <div class="detail-row"><div class="detail-key">Connection</div><div class="detail-val"><span class="badge badge-\${identity.connected ? 'ok' : 'danger'}">\${identity.connected ? 'Online' : 'Offline'}</span></div></div>
10253
+ \\\`;
10254
+ });
10255
+
10256
+ // Friends \u2014 load independently from remote server
10257
+ safeApi('/friends', { _timeout: 10000 }).then(friendsRes => {
10258
+ const fcEl = $('#dash-friend-count');
10259
+ const fsEl = $('#dash-friend-sub');
10260
+ const flEl = $('#dash-friends-list');
10261
+ if (friendsRes.error) {
10262
+ if (fcEl) fcEl.textContent = '\u2014';
10263
+ if (fsEl) fsEl.textContent = 'unavailable';
10264
+ if (flEl) flEl.innerHTML = errorBlock('Friend List Unavailable', friendsRes.error + ' \u2014 Remote server unreachable. <button class="btn btn-sm btn-default" style="margin-top:8px" onclick="loadDashboard()">\u{1F504} Retry</button>');
10265
+ toast('Cannot load friends: ' + friendsRes.error, 'warn');
10266
+ return;
10267
+ }
10268
+ const friendList = friendsRes.data?.friends || [];
10269
+ const aiFriends = friendList.filter(f => f.friendType === 'ai').length;
10270
+ const humanFriends = friendList.filter(f => f.friendType !== 'ai').length;
10271
+ if (fcEl) fcEl.textContent = friendList.length;
10272
+ if (fsEl) fsEl.textContent = aiFriends + ' AI \xB7 ' + humanFriends + ' Human';
10273
+ if (flEl) flEl.innerHTML = renderMiniFriendList(friendList.slice(0, 5));
10274
+ });
10228
10275
  }
10229
10276
 
10230
10277
  function renderMiniFriendList(friends) {
@@ -10332,12 +10379,17 @@ let friendsFilter = 'all';
10332
10379
  async function loadFriends() {
10333
10380
  const el = $('#friends-content');
10334
10381
  html(el, '<div class="loading-mask"><div class="spinner"></div>Loading friends...</div>');
10335
- const [friends, requests, sessions] = await Promise.all([api('/friends'), api('/friends/requests'), api('/sessions')]);
10336
10382
 
10337
- // Sub-tabs
10338
- const friendCount = (friends.friends || []).length;
10339
- const reqCount = (requests.requests || []).length;
10340
- const sessCount = (sessions.sessions || []).length;
10383
+ // Load all three independently \u2014 any can fail without blocking others
10384
+ const [friendsRes, requestsRes, sessionsRes] = await Promise.all([
10385
+ safeApi('/friends', { _timeout: 10000 }),
10386
+ safeApi('/friends/requests', { _timeout: 10000 }),
10387
+ safeApi('/sessions', { _timeout: 5000 }),
10388
+ ]);
10389
+
10390
+ const friendCount = (friendsRes.data?.friends || []).length;
10391
+ const reqCount = (requestsRes.data?.requests || []).length;
10392
+ const sessCount = (sessionsRes.data?.sessions || []).length;
10341
10393
 
10342
10394
  html('#friends-tabs', \\\`
10343
10395
  <button class="filter-btn \${friendsSubTab==='friends'?'active':''}" onclick="friendsSubTab='friends';loadFriends()">\u{1F465} Friends (<span id="fc">\${friendCount}</span>)</button>
@@ -10345,13 +10397,29 @@ async function loadFriends() {
10345
10397
  <button class="filter-btn \${friendsSubTab==='sessions'?'active':''}" onclick="friendsSubTab='sessions';loadFriends()">\u{1F517} Sessions (<span id="sc">\${sessCount}</span>)</button>
10346
10398
  \\\`);
10347
10399
 
10348
- window._friendsData = friends;
10349
- window._requestsData = requests;
10350
- window._sessionsData = sessions;
10400
+ window._friendsData = friendsRes.data || {};
10401
+ window._requestsData = requestsRes.data || {};
10402
+ window._sessionsData = sessionsRes.data || {};
10351
10403
 
10352
- if (friendsSubTab === 'friends') renderFriendsList(friends.friends || []);
10353
- else if (friendsSubTab === 'requests') renderRequestsList(requests.requests || []);
10354
- else renderSessionsList(sessions.sessions || []);
10404
+ if (friendsSubTab === 'friends') {
10405
+ if (friendsRes.error) {
10406
+ html(el, '<div class="toolbar"><button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504} Retry</button></div>' + errorBlock('Cannot Load Friends', friendsRes.error + ' \u2014 AICQ remote server is unreachable. Check your network or server status.'));
10407
+ } else {
10408
+ renderFriendsList(friendsRes.data.friends || []);
10409
+ }
10410
+ } else if (friendsSubTab === 'requests') {
10411
+ if (requestsRes.error) {
10412
+ html(el, '<div class="toolbar"><button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504} Retry</button></div>' + errorBlock('Cannot Load Requests', requestsRes.error));
10413
+ } else {
10414
+ renderRequestsList(requestsRes.data.requests || []);
10415
+ }
10416
+ } else {
10417
+ if (sessionsRes.error) {
10418
+ html(el, '<div class="toolbar"><button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504} Retry</button></div>' + errorBlock('Cannot Load Sessions', sessionsRes.error));
10419
+ } else {
10420
+ renderSessionsList(sessionsRes.data.sessions || []);
10421
+ }
10422
+ }
10355
10423
  }
10356
10424
  window.friendsSubTab = 'friends';
10357
10425
 
@@ -10605,46 +10673,178 @@ async function saveModelConfig() {
10605
10673
  }
10606
10674
 
10607
10675
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
10608
- // PAGE: Settings
10676
+ // PAGE: Settings \u2014 with Update button
10609
10677
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
10678
+ let _updating = false;
10679
+
10610
10680
  async function loadSettings() {
10611
10681
  const el = $('#settings-content');
10612
- html(el, '<div class="loading-mask"><div class="spinner"></div>Loading settings...</div>');
10613
- const [status, identity, config] = await Promise.all([api('/status'), api('/identity'), api('/config')]);
10682
+ // Render skeleton immediately
10683
+ html(el, \\\`
10684
+ <p class="section-desc">AICQ plugin runtime configuration and system information.</p>
10685
+ <div class="card" id="settings-update-card"><div class="loading-mask" style="padding:20px"><div class="spinner"></div>Loading...</div></div>
10686
+ <div id="settings-rest"></div>
10687
+ \\\`);
10614
10688
 
10615
- if (config.error) {
10616
- html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>' + escHtml(config.error) + '</p></div>');
10617
- return;
10689
+ // Load update check, status, identity, config \u2014 all independently
10690
+ const [updateRes, statusRes, identityRes, configRes, pluginRes] = await Promise.all([
10691
+ safeApi('/update/check', { _timeout: 15000 }),
10692
+ safeApi('/status', { _timeout: 5000 }),
10693
+ safeApi('/identity', { _timeout: 5000 }),
10694
+ safeApi('/config', { _timeout: 5000 }),
10695
+ safeApi('/plugin-info', { _timeout: 5000 }),
10696
+ ]);
10697
+
10698
+ // \u2500\u2500 Update Card \u2500\u2500
10699
+ const updateCard = $('#settings-update-card');
10700
+ if (updateCard) {
10701
+ const upd = updateRes.data || {};
10702
+ const pInfo = pluginRes.data || {};
10703
+ const currentVer = pInfo.version || upd.currentVersion || 'unknown';
10704
+ const latestVer = upd.latestVersion || currentVer;
10705
+ const hasUpdate = upd.updateAvailable;
10706
+
10707
+ updateCard.innerHTML = \\\`
10708
+ <div class="card-header">
10709
+ <div class="card-title">\u{1F504} Plugin Update</div>
10710
+ <div style="display:flex;gap:6px;align-items:center">
10711
+ <span class="badge badge-\${hasUpdate ? 'warn' : 'ok'}">\${hasUpdate ? 'Update Available!' : 'Up to date'}</span>
10712
+ </div>
10713
+ </div>
10714
+ <div class="detail-row"><div class="detail-key">Current Version</div><div class="detail-val mono">\${escHtml(currentVer)}</div></div>
10715
+ <div class="detail-row"><div class="detail-key">Latest Version</div><div class="detail-val mono">\${escHtml(latestVer)}</div></div>
10716
+ <div class="detail-row"><div class="detail-key">Package</div><div class="detail-val mono">aicq-openclaw-plugin</div></div>
10717
+ \${pluginRes.error ? '' : \\\`
10718
+ <div class="detail-row"><div class="detail-key">Node.js</div><div class="detail-val mono">\${escHtml(pInfo.nodeVersion || '\u2014')}</div></div>
10719
+ <div class="detail-row"><div class="detail-key">Uptime</div><div class="detail-val">\${pInfo.uptime ? Math.floor(pInfo.uptime / 3600) + 'h ' + Math.floor((pInfo.uptime % 3600) / 60) + 'm' : '\u2014'}</div></div>
10720
+ \\\`}
10721
+ <div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap">
10722
+ <button class="btn btn-primary" id="btn-check-update" onclick="checkForUpdates()">\u{1F50D} Check for Updates</button>
10723
+ <button class="btn btn-ok \${hasUpdate ? '' : 'btn-ghost'}" id="btn-do-update" onclick="doUpdate(false)">\u{1F4E5} \${hasUpdate ? 'Update to v' + escHtml(latestVer) : 'Incremental Update'}</button>
10724
+ <button class="btn btn-warn" id="btn-force-update" onclick="doUpdate(true)">\u{1F503} Force Reinstall</button>
10725
+ </div>
10726
+ <div id="update-output" style="display:none;margin-top:12px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:12px;max-height:300px;overflow-y:auto">
10727
+ <pre style="font-size:12px;color:var(--text2);white-space:pre-wrap;margin:0" id="update-log"></pre>
10728
+ </div>
10729
+ \\\`;
10618
10730
  }
10619
10731
 
10620
- html(el, \\\`
10621
- <p class="section-desc">AICQ plugin runtime configuration and system information.</p>
10732
+ // \u2500\u2500 Rest of settings \u2500\u2500
10733
+ const restEl = $('#settings-rest');
10734
+ if (!restEl) return;
10735
+ let out = '';
10622
10736
 
10623
- <div class="card">
10624
- <div class="card-header"><div class="card-title">\u{1F50C} Connection Settings</div></div>
10625
- <div class="detail-row"><div class="detail-key">Server URL</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${escHtml(status.serverUrl)}')">\${escHtml(status.serverUrl)} \u{1F4CB}</div></div>
10626
- <div class="detail-row"><div class="detail-key">WebSocket Status</div><div class="detail-val"><span class="badge badge-\${status.connected ? 'ok' : 'danger'}">\${status.connected ? 'Connected' : 'Disconnected'}</span></div></div>
10627
- </div>
10737
+ // Connection section
10738
+ if (statusRes.error) {
10739
+ out += errorBlock('Connection Status Unavailable', statusRes.error);
10740
+ } else {
10741
+ const s = statusRes.data;
10742
+ out += \\\`
10743
+ <div class="card">
10744
+ <div class="card-header"><div class="card-title">\u{1F50C} Connection Settings</div></div>
10745
+ <div class="detail-row"><div class="detail-key">Server URL</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${escHtml(s.serverUrl)}')">\${escHtml(s.serverUrl)} \u{1F4CB}</div></div>
10746
+ <div class="detail-row"><div class="detail-key">WebSocket Status</div><div class="detail-val"><span class="badge badge-\${s.connected ? 'ok' : 'danger'}">\${s.connected ? 'Connected' : 'Disconnected'}</span></div></div>
10747
+ </div>
10748
+ \\\`;
10749
+ }
10628
10750
 
10629
- <div class="card">
10630
- <div class="card-header"><div class="card-title">\u{1F916} Agent Identity</div></div>
10631
- <div class="detail-row"><div class="detail-key">Agent ID</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${escHtml(identity.agentId)}')">\${escHtml(identity.agentId)} \u{1F4CB}</div></div>
10632
- <div class="detail-row"><div class="detail-key">Public Key Fingerprint</div><div class="detail-val mono">\${escHtml(identity.publicKeyFingerprint)}</div></div>
10633
- </div>
10751
+ // Identity section
10752
+ if (identityRes.error) {
10753
+ out += errorBlock('Agent Identity Unavailable', identityRes.error);
10754
+ } else {
10755
+ const i = identityRes.data;
10756
+ out += \\\`
10757
+ <div class="card">
10758
+ <div class="card-header"><div class="card-title">\u{1F916} Agent Identity</div></div>
10759
+ <div class="detail-row"><div class="detail-key">Agent ID</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${escHtml(i.agentId)}')">\${escHtml(i.agentId)} \u{1F4CB}</div></div>
10760
+ <div class="detail-row"><div class="detail-key">Public Key Fingerprint</div><div class="detail-val mono">\${escHtml(i.publicKeyFingerprint)}</div></div>
10761
+ </div>
10762
+ \\\`;
10763
+ }
10634
10764
 
10635
- <div class="card">
10636
- <div class="card-header"><div class="card-title">\u{1F4C1} Config File</div></div>
10637
- <div class="detail-row"><div class="detail-key">Source</div><div class="detail-val" style="cursor:pointer" onclick="copyText('\${escHtml(config.configPath || '')}')">\${escHtml(config.configPath || 'Not found')} \u{1F4CB}</div></div>
10638
- <div class="detail-row"><div class="detail-key">Config Size</div><div class="detail-val">\${config.configSize || 0} bytes</div></div>
10639
- </div>
10765
+ // Config file section
10766
+ if (configRes.error) {
10767
+ out += errorBlock('Config File Not Found', configRes.error);
10768
+ } else {
10769
+ const c = configRes.data;
10770
+ out += \\\`
10771
+ <div class="card">
10772
+ <div class="card-header"><div class="card-title">\u{1F4C1} Config File</div></div>
10773
+ <div class="detail-row"><div class="detail-key">Source</div><div class="detail-val" style="cursor:pointer" onclick="copyText('\${escHtml(c.configPath || '')}')">\${escHtml(c.configPath || 'Not found')} \u{1F4CB}</div></div>
10774
+ <div class="detail-row"><div class="detail-key">Config Size</div><div class="detail-val">\${c.configSize || 0} bytes</div></div>
10775
+ </div>
10776
+ \\\`;
10777
+ }
10640
10778
 
10641
- <div class="card">
10642
- <div class="card-header"><div class="card-title">\u{1F4CA} Statistics</div></div>
10643
- <div class="detail-row"><div class="detail-key">Friends Count</div><div class="detail-val">\${status.friendCount || 0}</div></div>
10644
- <div class="detail-row"><div class="detail-key">Active Sessions</div><div class="detail-val">\${status.sessionCount || 0}</div></div>
10645
- <div class="detail-row"><div class="detail-key">Plugin Version</div><div class="detail-val">1.0.4</div></div>
10646
- </div>
10647
- \\\`);
10779
+ // Statistics section
10780
+ if (!statusRes.error) {
10781
+ const s = statusRes.data;
10782
+ out += \\\`
10783
+ <div class="card">
10784
+ <div class="card-header"><div class="card-title">\u{1F4CA} Statistics</div></div>
10785
+ <div class="detail-row"><div class="detail-key">Friends Count</div><div class="detail-val">\${s.friendCount || 0}</div></div>
10786
+ <div class="detail-row"><div class="detail-key">Active Sessions</div><div class="detail-val">\${s.sessionCount || 0}</div></div>
10787
+ <div class="detail-row"><div class="detail-key">Plugin Version</div><div class="detail-val">\${escHtml(pluginRes.data?.version || 'unknown')}</div></div>
10788
+ </div>
10789
+ \\\`;
10790
+ }
10791
+
10792
+ restEl.innerHTML = out;
10793
+ }
10794
+
10795
+ async function checkForUpdates() {
10796
+ const btn = $('#btn-check-update');
10797
+ if (btn) { btn.disabled = true; btn.textContent = '\u{1F50D} Checking...'; }
10798
+ toast('Checking for updates...', 'info');
10799
+ const data = await api('/update/check', { _timeout: 15000 });
10800
+ if (data.error) { toast('Check failed: ' + data.error, 'err'); }
10801
+ else if (data.updateAvailable) { toast('New version available: v' + data.latestVersion, 'ok'); }
10802
+ else { toast('Plugin is up to date (v' + data.currentVersion + ')', 'ok'); }
10803
+ if (btn) { btn.disabled = false; btn.textContent = '\u{1F50D} Check for Updates'; }
10804
+ loadSettings();
10805
+ }
10806
+
10807
+ async function doUpdate(force) {
10808
+ if (_updating) { toast('Update already in progress...', 'warn'); return; }
10809
+ const label = force ? 'Force reinstall' : 'Incremental update';
10810
+ if (!confirm(label + ' aicq-openclaw-plugin? This will download and install the latest version from npm.')) return;
10811
+
10812
+ _updating = true;
10813
+ const btnDo = $('#btn-do-update');
10814
+ const btnForce = $('#btn-force-update');
10815
+ if (btnDo) btnDo.disabled = true;
10816
+ if (btnForce) btnForce.disabled = true;
10817
+
10818
+ const outputEl = $('#update-output');
10819
+ const logEl = $('#update-log');
10820
+ if (outputEl) outputEl.style.display = 'block';
10821
+ if (logEl) logEl.textContent = 'Starting ' + label.toLowerCase() + '...
10822
+ ';
10823
+ toast(label + ' starting...', 'info');
10824
+
10825
+ const r = await api('/update', {
10826
+ method: 'POST',
10827
+ body: JSON.stringify({ force }),
10828
+ _timeout: 120000,
10829
+ });
10830
+
10831
+ if (logEl) logEl.textContent = r.output || r.message || (r.success ? 'Done!' : 'Failed.');
10832
+ if (r.success) {
10833
+ toast('Update successful! Restart the service to apply.', 'ok');
10834
+ if (logEl) logEl.textContent += '
10835
+
10836
+ \u2705 ' + (r.message || 'Done! Please restart the service.');
10837
+ } else {
10838
+ toast('Update failed: ' + (r.message || 'Unknown error'), 'err');
10839
+ if (logEl) logEl.textContent += '
10840
+
10841
+ \u274C ' + (r.message || 'Failed.');
10842
+ }
10843
+
10844
+ _updating = false;
10845
+ if (btnDo) btnDo.disabled = false;
10846
+ if (btnForce) btnForce.disabled = false;
10847
+ loadSettings();
10648
10848
  }
10649
10849
 
10650
10850
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
@@ -10725,7 +10925,7 @@ var HTML = `<!DOCTYPE html>
10725
10925
  <div class="main-content">
10726
10926
 
10727
10927
  <!-- Dashboard -->
10728
- <div class="page active" id="page-dashboard"><div id="dashboard-content"><div class="loading-mask"><div class="spinner"></div>Loading...</div></div></div>
10928
+ <div class="page active" id="page-dashboard"><div id="dashboard-content"></div></div>
10729
10929
 
10730
10930
  <!-- Agents -->
10731
10931
  <div class="page" id="page-agents"><div id="agents-content"></div></div>
@@ -10830,6 +11030,7 @@ function getManagementHTML() {
10830
11030
  import * as fs5 from "fs";
10831
11031
  import * as path5 from "path";
10832
11032
  import * as os from "os";
11033
+ import { execSync, exec } from "child_process";
10833
11034
  var MODEL_PROVIDERS = [
10834
11035
  { id: "openai", name: "OpenAI", description: "GPT-4o, GPT-4, GPT-3.5, o1, o3", apiKeyHint: "sk-...", modelHint: "gpt-4o", baseUrlHint: "https://api.openai.com/v1", configKey: "openai" },
10835
11036
  { id: "anthropic", name: "Anthropic", description: "Claude 4, Claude 3.5 Sonnet, Haiku, Opus", apiKeyHint: "sk-ant-...", modelHint: "claude-sonnet-4-20250514", baseUrlHint: "https://api.anthropic.com", configKey: "anthropic" },
@@ -11314,6 +11515,146 @@ function createManagementHandler(ctx) {
11314
11515
  logger.info("[API] Model config saved for provider: " + providerId);
11315
11516
  return json(res, { success: true, message: "Model configuration saved for " + provider.name });
11316
11517
  }
11518
+ if (apiPath === "/update/check" && method === "GET") {
11519
+ try {
11520
+ const pkgPath = path5.join(__dirname, "..", "package.json");
11521
+ let currentVersion = "unknown";
11522
+ try {
11523
+ const pkgRaw = fs5.readFileSync(pkgPath, "utf-8");
11524
+ const pkg = JSON.parse(pkgRaw);
11525
+ currentVersion = pkg.version || "unknown";
11526
+ } catch {
11527
+ }
11528
+ if (currentVersion === "unknown") {
11529
+ const altPkgPath = path5.join(process.cwd(), "plugin", "package.json");
11530
+ try {
11531
+ const pkg = JSON.parse(fs5.readFileSync(altPkgPath, "utf-8"));
11532
+ currentVersion = pkg.version || "unknown";
11533
+ } catch {
11534
+ }
11535
+ }
11536
+ let latestVersion = currentVersion;
11537
+ let updateAvailable = false;
11538
+ try {
11539
+ const npmView = execSync("npm view aicq-openclaw-plugin version --registry https://registry.npmjs.org 2>/dev/null", {
11540
+ timeout: 15e3,
11541
+ encoding: "utf-8"
11542
+ }).trim();
11543
+ if (npmView) {
11544
+ latestVersion = npmView.replace(/^\^|~|\s+.*$/g, "").trim();
11545
+ updateAvailable = latestVersion !== currentVersion;
11546
+ }
11547
+ } catch (npmErr) {
11548
+ logger.warn("[API] npm view failed: " + (npmErr instanceof Error ? npmErr.message : String(npmErr)));
11549
+ }
11550
+ return json(res, {
11551
+ currentVersion,
11552
+ latestVersion,
11553
+ updateAvailable,
11554
+ packageName: "aicq-openclaw-plugin"
11555
+ });
11556
+ } catch (err) {
11557
+ const msg = err instanceof Error ? err.message : String(err);
11558
+ return json(res, { error: "Failed to check for updates: " + msg }, 500);
11559
+ }
11560
+ }
11561
+ if (apiPath === "/update" && method === "POST") {
11562
+ const body = await readBody(req);
11563
+ const force = body.force;
11564
+ try {
11565
+ logger.info("[API] Starting npm update for aicq-openclaw-plugin...");
11566
+ const pluginDir = __dirname;
11567
+ let npmRoot = pluginDir;
11568
+ for (let i = 0; i < 5; i++) {
11569
+ if (npmRoot.endsWith("node_modules"))
11570
+ break;
11571
+ npmRoot = path5.dirname(npmRoot);
11572
+ }
11573
+ const parentDir = path5.dirname(npmRoot);
11574
+ let installDir = parentDir;
11575
+ const candidates = [
11576
+ parentDir,
11577
+ path5.join(os.homedir(), ".openclaw", "plugins"),
11578
+ path5.join(os.homedir(), ".config", "openclaw", "plugins")
11579
+ ];
11580
+ let updateCmd;
11581
+ if (force) {
11582
+ updateCmd = `npm install aicq-openclaw-plugin@latest --dangerously-force-unsafe-install --registry https://registry.npmjs.org`;
11583
+ } else {
11584
+ updateCmd = `npm update aicq-openclaw-plugin --dangerously-force-unsafe-install --registry https://registry.npmjs.org`;
11585
+ }
11586
+ let installPath = parentDir;
11587
+ try {
11588
+ const pkgJsonPath = path5.join(pluginDir, "package.json");
11589
+ const realPkgDir = fs5.realpathSync(path5.dirname(pkgJsonPath));
11590
+ if (realPkgDir.includes("node_modules")) {
11591
+ const nmIdx = realPkgDir.lastIndexOf("node_modules");
11592
+ installPath = realPkgDir.substring(0, nmIdx + 13);
11593
+ }
11594
+ } catch {
11595
+ }
11596
+ logger.info("[API] Running: " + updateCmd + " in " + installPath);
11597
+ const result = await new Promise((resolve3) => {
11598
+ const child = exec(updateCmd, {
11599
+ cwd: installPath,
11600
+ timeout: 12e4,
11601
+ encoding: "utf-8",
11602
+ env: { ...process.env, NODE_NO_WARNINGS: "1" }
11603
+ }, (error, stdout, stderr) => {
11604
+ if (error) {
11605
+ resolve3("ERROR: " + (error.message || String(error)) + "\n\n" + (stderr || ""));
11606
+ } else {
11607
+ resolve3(stdout || stderr || "Update completed (no output)");
11608
+ }
11609
+ });
11610
+ child.stdout?.on("data", (d) => {
11611
+ });
11612
+ child.stderr?.on("data", (d) => {
11613
+ });
11614
+ });
11615
+ let newVersion = "unknown";
11616
+ try {
11617
+ const pkgRaw = fs5.readFileSync(path5.join(__dirname, "..", "package.json"), "utf-8");
11618
+ newVersion = JSON.parse(pkgRaw).version || "unknown";
11619
+ } catch {
11620
+ }
11621
+ const success = !result.startsWith("ERROR");
11622
+ logger.info("[API] Update " + (success ? "succeeded" : "failed") + ": " + result.substring(0, 200));
11623
+ return json(res, {
11624
+ success,
11625
+ message: success ? "Plugin updated successfully! Restart the service to apply changes." : "Update failed.",
11626
+ output: result.substring(0, 2e3),
11627
+ newVersion,
11628
+ needsRestart: success
11629
+ });
11630
+ } catch (err) {
11631
+ const msg = err instanceof Error ? err.message : String(err);
11632
+ logger.error("[API] Update error: " + msg);
11633
+ return json(res, { success: false, message: "Update failed: " + msg }, 500);
11634
+ }
11635
+ }
11636
+ if (apiPath === "/plugin-info" && method === "GET") {
11637
+ let pkgVersion = "unknown";
11638
+ try {
11639
+ const pkgPath = path5.join(__dirname, "..", "package.json");
11640
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
11641
+ pkgVersion = pkg.version || "unknown";
11642
+ } catch {
11643
+ const altPkgPath = path5.join(process.cwd(), "plugin", "package.json");
11644
+ try {
11645
+ pkgVersion = JSON.parse(fs5.readFileSync(altPkgPath, "utf-8")).version || "unknown";
11646
+ } catch {
11647
+ }
11648
+ }
11649
+ return json(res, {
11650
+ version: pkgVersion,
11651
+ name: "aicq-openclaw-plugin",
11652
+ uptime: process.uptime(),
11653
+ nodeVersion: process.version,
11654
+ platform: process.platform,
11655
+ pid: process.pid
11656
+ });
11657
+ }
11317
11658
  res.writeHead(404, { "Content-Type": "application/json" });
11318
11659
  res.end(JSON.stringify({ error: "Not found: " + apiPath }));
11319
11660
  } catch (err) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "aicq-chat",
3
3
  "name": "AICQ Encrypted Chat",
4
- "version": "1.0.4",
4
+ "version": "1.0.7",
5
5
  "description": "End-to-end encrypted chat plugin supporting AI-AI, Human-AI communication with P2P messaging via Noise-XK handshake, Ed25519/X25519/AES-256-GCM encryption",
6
6
  "enabledByDefault": false,
7
7
  "channels": ["encrypted-chat"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicq-openclaw-plugin",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "AICQ OpenClaw plugin - end-to-end encrypted P2P chat for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",