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 +435 -94
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
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
|
|
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(
|
|
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) {
|
|
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
|
-
|
|
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
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
10338
|
-
const
|
|
10339
|
-
|
|
10340
|
-
|
|
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 =
|
|
10349
|
-
window._requestsData =
|
|
10350
|
-
window._sessionsData =
|
|
10400
|
+
window._friendsData = friendsRes.data || {};
|
|
10401
|
+
window._requestsData = requestsRes.data || {};
|
|
10402
|
+
window._sessionsData = sessionsRes.data || {};
|
|
10351
10403
|
|
|
10352
|
-
if (friendsSubTab === 'friends')
|
|
10353
|
-
|
|
10354
|
-
|
|
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
|
-
|
|
10613
|
-
|
|
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
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
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
|
-
|
|
10621
|
-
|
|
10732
|
+
// \u2500\u2500 Rest of settings \u2500\u2500
|
|
10733
|
+
const restEl = $('#settings-rest');
|
|
10734
|
+
if (!restEl) return;
|
|
10735
|
+
let out = '';
|
|
10622
10736
|
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
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
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
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
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
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
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
<div class="
|
|
10646
|
-
|
|
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"
|
|
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) {
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "aicq-chat",
|
|
3
3
|
"name": "AICQ Encrypted Chat",
|
|
4
|
-
"version": "1.0.
|
|
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"],
|