aicq-openclaw-plugin 1.0.5 → 1.0.6

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.
Files changed (2) hide show
  1. package/dist/index.js +157 -59
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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
@@ -10174,14 +10197,49 @@ function loadPage(page) {
10174
10197
  async function loadDashboard() {
10175
10198
  const el = $('#dashboard-content');
10176
10199
  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; }
10200
+
10201
+ // Load local data first (status + identity), then remote (friends) \u2014 each independent
10202
+ const [statusRes, identityRes] = await Promise.all([safeApi('/status'), safeApi('/identity')]);
10203
+ const status = statusRes.data || {};
10204
+ const identity = identityRes.data || {};
10205
+
10206
+ // Friends is remote \u2014 load independently, don't block
10207
+ const friendsRes = await safeApi('/friends', { _timeout: 10000 });
10208
+ const friendList = (friendsRes.data?.friends) || [];
10209
+ const friendsError = friendsRes.error;
10210
+
10179
10211
  const connCls = status.connected ? 'dot-ok' : 'dot-err';
10180
10212
  const connText = status.connected ? 'Connected' : 'Disconnected';
10181
- const friendList = friends.friends || [];
10182
10213
  const aiFriends = friendList.filter(f => f.friendType === 'ai').length;
10183
10214
  const humanFriends = friendList.filter(f => f.friendType !== 'ai').length;
10184
10215
 
10216
+ // Update header status dot
10217
+ const hDot = $('#header-dot');
10218
+ if (hDot) hDot.className = 'dot ' + (status.connected ? 'dot-ok' : 'dot-err');
10219
+ const hTxt = $('#header-status');
10220
+ if (hTxt) hTxt.textContent = status.connected ? 'Connected' : 'Disconnected';
10221
+
10222
+ // Build friends section
10223
+ let friendsSection = '';
10224
+ if (friendsError) {
10225
+ friendsSection = '<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>' + errorBlock('Friend List Unavailable', friendsError + ' \u2014 Remote AICQ server may be unreachable. <button class="btn btn-sm btn-default" style="margin-top:8px" onclick="loadDashboard()">\u{1F504} Retry</button>') + '</div>';
10226
+ } else {
10227
+ friendsSection = '<div class="card"><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>' + renderMiniFriendList(friendList.slice(0, 5)) + '</div>';
10228
+ }
10229
+
10230
+ // Build identity section
10231
+ let identitySection = '';
10232
+ if (identityRes.error) {
10233
+ identitySection = errorBlock('Identity Unavailable', identityRes.error);
10234
+ } else {
10235
+ identitySection = \\\`
10236
+ <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>
10237
+ <div class="detail-row"><div class="detail-key">Fingerprint</div><div class="detail-val mono">\${escHtml(identity.publicKeyFingerprint || '\u2014')}</div></div>
10238
+ <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>
10239
+ <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>
10240
+ \\\`;
10241
+ }
10242
+
10185
10243
  html(el, \\\`
10186
10244
  <div class="stats-grid">
10187
10245
  <div class="stat-card">
@@ -10190,13 +10248,13 @@ async function loadDashboard() {
10190
10248
  <div class="stat-value" style="font-size:16px;display:flex;align-items:center;gap:8px">
10191
10249
  <span class="dot \${connCls}"></span> \${connText}
10192
10250
  </div>
10193
- <div class="stat-sub">\${escHtml(status.serverUrl)}</div>
10251
+ <div class="stat-sub">\${escHtml(status.serverUrl || '\u2014')}</div>
10194
10252
  </div>
10195
10253
  <div class="stat-card">
10196
10254
  <div class="stat-icon" style="background:var(--ok-bg)">\u{1F465}</div>
10197
10255
  <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>
10256
+ <div class="stat-value">\${friendsError ? '\u2014' : friendList.length}</div>
10257
+ <div class="stat-sub">\${friendsError ? 'unavailable' : aiFriends + ' AI \xB7 ' + humanFriends + ' Human'}</div>
10200
10258
  </div>
10201
10259
  <div class="stat-card">
10202
10260
  <div class="stat-icon" style="background:var(--info-bg)">\u{1F517}</div>
@@ -10207,21 +10265,15 @@ async function loadDashboard() {
10207
10265
  <div class="stat-card">
10208
10266
  <div class="stat-icon" style="background:var(--warn-bg)">\u{1F511}</div>
10209
10267
  <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>
10268
+ <div class="stat-value mono" style="font-size:13px">\${escHtml(status.agentId || '\u2014')}</div>
10269
+ <div class="stat-sub">Fingerprint: \${escHtml(status.fingerprint || '\u2014')}</div>
10212
10270
  </div>
10213
10271
  </div>
10214
10272
  <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>
10273
+ \${friendsSection}
10219
10274
  <div class="card">
10220
10275
  <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>
10276
+ \${identitySection}
10225
10277
  </div>
10226
10278
  </div>
10227
10279
  \\\`);
@@ -10332,12 +10384,17 @@ let friendsFilter = 'all';
10332
10384
  async function loadFriends() {
10333
10385
  const el = $('#friends-content');
10334
10386
  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
10387
 
10337
- // Sub-tabs
10338
- const friendCount = (friends.friends || []).length;
10339
- const reqCount = (requests.requests || []).length;
10340
- const sessCount = (sessions.sessions || []).length;
10388
+ // Load all three independently \u2014 any can fail without blocking others
10389
+ const [friendsRes, requestsRes, sessionsRes] = await Promise.all([
10390
+ safeApi('/friends', { _timeout: 10000 }),
10391
+ safeApi('/friends/requests', { _timeout: 10000 }),
10392
+ safeApi('/sessions', { _timeout: 5000 }),
10393
+ ]);
10394
+
10395
+ const friendCount = (friendsRes.data?.friends || []).length;
10396
+ const reqCount = (requestsRes.data?.requests || []).length;
10397
+ const sessCount = (sessionsRes.data?.sessions || []).length;
10341
10398
 
10342
10399
  html('#friends-tabs', \\\`
10343
10400
  <button class="filter-btn \${friendsSubTab==='friends'?'active':''}" onclick="friendsSubTab='friends';loadFriends()">\u{1F465} Friends (<span id="fc">\${friendCount}</span>)</button>
@@ -10345,13 +10402,29 @@ async function loadFriends() {
10345
10402
  <button class="filter-btn \${friendsSubTab==='sessions'?'active':''}" onclick="friendsSubTab='sessions';loadFriends()">\u{1F517} Sessions (<span id="sc">\${sessCount}</span>)</button>
10346
10403
  \\\`);
10347
10404
 
10348
- window._friendsData = friends;
10349
- window._requestsData = requests;
10350
- window._sessionsData = sessions;
10405
+ window._friendsData = friendsRes.data || {};
10406
+ window._requestsData = requestsRes.data || {};
10407
+ window._sessionsData = sessionsRes.data || {};
10351
10408
 
10352
- if (friendsSubTab === 'friends') renderFriendsList(friends.friends || []);
10353
- else if (friendsSubTab === 'requests') renderRequestsList(requests.requests || []);
10354
- else renderSessionsList(sessions.sessions || []);
10409
+ if (friendsSubTab === 'friends') {
10410
+ if (friendsRes.error) {
10411
+ 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.'));
10412
+ } else {
10413
+ renderFriendsList(friendsRes.data.friends || []);
10414
+ }
10415
+ } else if (friendsSubTab === 'requests') {
10416
+ if (requestsRes.error) {
10417
+ 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));
10418
+ } else {
10419
+ renderRequestsList(requestsRes.data.requests || []);
10420
+ }
10421
+ } else {
10422
+ if (sessionsRes.error) {
10423
+ 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));
10424
+ } else {
10425
+ renderSessionsList(sessionsRes.data.sessions || []);
10426
+ }
10427
+ }
10355
10428
  }
10356
10429
  window.friendsSubTab = 'friends';
10357
10430
 
@@ -10610,41 +10683,66 @@ async function saveModelConfig() {
10610
10683
  async function loadSettings() {
10611
10684
  const el = $('#settings-content');
10612
10685
  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')]);
10686
+ const [statusRes, identityRes, configRes] = await Promise.all([safeApi('/status'), safeApi('/identity'), safeApi('/config')]);
10614
10687
 
10615
- if (config.error) {
10616
- html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>' + escHtml(config.error) + '</p></div>');
10617
- return;
10618
- }
10688
+ let out = '<p class="section-desc">AICQ plugin runtime configuration and system information.</p>';
10619
10689
 
10620
- html(el, \\\`
10621
- <p class="section-desc">AICQ plugin runtime configuration and system information.</p>
10690
+ // Connection section
10691
+ if (statusRes.error) {
10692
+ out += errorBlock('Connection Status Unavailable', statusRes.error);
10693
+ } else {
10694
+ const s = statusRes.data;
10695
+ out += \\\`
10696
+ <div class="card">
10697
+ <div class="card-header"><div class="card-title">\u{1F50C} Connection Settings</div></div>
10698
+ <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>
10699
+ <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>
10700
+ </div>
10701
+ \\\`;
10702
+ }
10622
10703
 
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>
10704
+ // Identity section
10705
+ if (identityRes.error) {
10706
+ out += errorBlock('Agent Identity Unavailable', identityRes.error);
10707
+ } else {
10708
+ const i = identityRes.data;
10709
+ out += \\\`
10710
+ <div class="card">
10711
+ <div class="card-header"><div class="card-title">\u{1F916} Agent Identity</div></div>
10712
+ <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>
10713
+ <div class="detail-row"><div class="detail-key">Public Key Fingerprint</div><div class="detail-val mono">\${escHtml(i.publicKeyFingerprint)}</div></div>
10714
+ </div>
10715
+ \\\`;
10716
+ }
10628
10717
 
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>
10718
+ // Config file section
10719
+ if (configRes.error) {
10720
+ out += errorBlock('Config File Not Found', configRes.error);
10721
+ } else {
10722
+ const c = configRes.data;
10723
+ out += \\\`
10724
+ <div class="card">
10725
+ <div class="card-header"><div class="card-title">\u{1F4C1} Config File</div></div>
10726
+ <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>
10727
+ <div class="detail-row"><div class="detail-key">Config Size</div><div class="detail-val">\${c.configSize || 0} bytes</div></div>
10728
+ </div>
10729
+ \\\`;
10730
+ }
10634
10731
 
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>
10732
+ // Statistics section
10733
+ if (!statusRes.error) {
10734
+ const s = statusRes.data;
10735
+ out += \\\`
10736
+ <div class="card">
10737
+ <div class="card-header"><div class="card-title">\u{1F4CA} Statistics</div></div>
10738
+ <div class="detail-row"><div class="detail-key">Friends Count</div><div class="detail-val">\${s.friendCount || 0}</div></div>
10739
+ <div class="detail-row"><div class="detail-key">Active Sessions</div><div class="detail-val">\${s.sessionCount || 0}</div></div>
10740
+ <div class="detail-row"><div class="detail-key">Plugin Version</div><div class="detail-val">1.0.5</div></div>
10741
+ </div>
10742
+ \\\`;
10743
+ }
10640
10744
 
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
- \\\`);
10745
+ html(el, out);
10648
10746
  }
10649
10747
 
10650
10748
  // \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
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.6",
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",