coding-tool-x 3.9.5-261014 → 3.9.6-261014
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/web/assets/{AgentsDrawer-K97azdxi.js → AgentsDrawer-D-1A92Ig.js} +1 -1
- package/dist/web/assets/{Analytics-CNPDGLNe.js → Analytics-B2O5txNK.js} +1 -1
- package/dist/web/assets/{AssetPathField-BzwSZryS.js → AssetPathField-DuohRI-K.js} +1 -1
- package/dist/web/assets/{ChatHistoryDrawer-BVbfqK-u.js → ChatHistoryDrawer-CjyehFJ5.js} +1 -1
- package/dist/web/assets/{CollapseItem-DgBQWlsJ.js → CollapseItem--khwfagQ.js} +1 -1
- package/dist/web/assets/{CommandsDrawer-885BtWjs.js → CommandsDrawer-gZsBcvyW.js} +1 -1
- package/dist/web/assets/{ConfigExportDrawer-jn00iZWm.js → ConfigExportDrawer-CJnljMGE.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DoUG3mOo.js → ConfigTemplates-wkwjB2C5.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesDrawer-BQ8h43E6.js → ConfigTemplatesDrawer-hAGvUnSt.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesPanel-VCghYqMx.js → ConfigTemplatesPanel-BfF8qmL_.js} +1 -1
- package/dist/web/assets/{FavoritesDrawer-CKjlAe4O.js → FavoritesDrawer-YeAnYTuA.js} +1 -1
- package/dist/web/assets/{GatewayConvertDrawer-Cin2yotJ.js → GatewayConvertDrawer-D1znBVev.js} +1 -1
- package/dist/web/assets/{Home-ui0qANow.js → Home-CSsVyr4n.js} +1 -1
- package/dist/web/assets/{MarkdownEditor-LiZaVnJu.js → MarkdownEditor-CeLdWA-c.js} +1 -1
- package/dist/web/assets/{McpDrawer-ClaSJOLK.js → McpDrawer-z-veNi2j.js} +1 -1
- package/dist/web/assets/{McpFormDrawer-w1EJUIsa.js → McpFormDrawer-DgDBnMOk.js} +1 -1
- package/dist/web/assets/{PluginManager-C_Yr9wyj.js → PluginManager-DFYt_Xmd.js} +1 -1
- package/dist/web/assets/{PluginsDrawer-CUnoai6T.js → PluginsDrawer-Dqpb2UsR.js} +1 -1
- package/dist/web/assets/{PluginsPanel-CNShDbDF.js → PluginsPanel-CvcT0Up3.js} +1 -1
- package/dist/web/assets/{ProjectList-CZ2ZwKRn.js → ProjectList-DjwDDvVo.js} +1 -1
- package/dist/web/assets/{PromptsDrawer-BhB_q-KZ.js → PromptsDrawer-B2vI4UfM.js} +1 -1
- package/dist/web/assets/{RecentSessionsDrawer-BZ8YKqwq.js → RecentSessionsDrawer-DTbikshr.js} +1 -1
- package/dist/web/assets/{SessionCard-CEFt-CNZ.js → SessionCard-AKJF0KLk.js} +1 -1
- package/dist/web/assets/{SessionList-CTO4NiMY.js → SessionList-C2kxSHyw.js} +1 -1
- package/dist/web/assets/{SettingsDrawer-BiOXu9lZ.js → SettingsDrawer-CYNU3Scm.js} +1 -1
- package/dist/web/assets/{SkillManager-Bf87paMe.js → SkillManager-MMDMnZuA.js} +1 -1
- package/dist/web/assets/{SkillsDrawer-BwkyaeRk.js → SkillsDrawer-Bg6QLvgm.js} +1 -1
- package/dist/web/assets/{SkillsPanel-obdDEThm.js → SkillsPanel-CDUSYnfc.js} +1 -1
- package/dist/web/assets/{Upload-DdjJgjTW.js → Upload-DMr2S_D5.js} +1 -1
- package/dist/web/assets/{WorkspaceDrawer-UWKpEUXI.js → WorkspaceDrawer-VrUPX-Pa.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-rKzSKzKC.js → WorkspaceManager-q76RE4Ss.js} +1 -1
- package/dist/web/assets/{WorkspacePanel-BCMcITVD.js → WorkspacePanel-CSDGZMrU.js} +1 -1
- package/dist/web/assets/{index-aW6itxZg.js → index-BXFl1mzC.js} +2 -2
- package/dist/web/assets/index-CcIxENSq.css +1 -0
- package/dist/web/assets/{useResponsiveDrawer-Cn6gH3VG.js → useResponsiveDrawer-DXmsFFe1.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/platforms/drivers/omp/channels.js +27 -0
- package/src/platforms/drivers/omp/gateway-routing.js +123 -33
- package/src/platforms/drivers/omp/gateway.js +102 -40
- package/src/server/services/memory-pressure.js +101 -0
- package/src/shared/channel-driver.js +12 -0
- package/dist/web/assets/index-DvoMGWQv.css +0 -1
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { memoryPressureMiddleware, rejectUpgradeUnderPressure } = require('../../../server/services/memory-pressure');
|
|
4
|
+
|
|
3
5
|
const crypto = require('crypto');
|
|
4
6
|
const { downstreamHeaders } = require('../../../shared/proxy-transport');
|
|
5
7
|
const http = require('http');
|
|
6
8
|
const https = require('https');
|
|
7
9
|
const httpProxy = require('http-proxy');
|
|
8
10
|
const { URL } = require('url');
|
|
9
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
allocateChannel,
|
|
13
|
+
releaseChannel,
|
|
14
|
+
getSchedulerState
|
|
15
|
+
} = require('../../../server/services/channel-scheduler');
|
|
10
16
|
const { recordSuccess, recordFailure } = require('../../../server/services/channel-health');
|
|
11
|
-
const {
|
|
12
|
-
const {
|
|
17
|
+
const { broadcastSchedulerState } = require('../../../server/websocket-server');
|
|
18
|
+
const { attachServerShutdownHandling, expediteServerShutdown, PROXY_IDLE_TIMEOUT_MS } = require('../../../server/services/server-shutdown');
|
|
19
|
+
const {
|
|
20
|
+
resolveOmpGatewayRoute,
|
|
21
|
+
channelSupportsOmpModel,
|
|
22
|
+
normalizeOmpModelId
|
|
23
|
+
} = require('./gateway-routing');
|
|
13
24
|
const { persistProxyRequestSnapshot } = require('../../../server/services/request-logger');
|
|
14
25
|
|
|
15
26
|
const RETRYABLE_STATUS = new Set([401, 403, 429, 502, 503, 504]);
|
|
@@ -115,6 +126,7 @@ function rewriteCredential(headers, requestUrl, route, channel, options = {}) {
|
|
|
115
126
|
}
|
|
116
127
|
|
|
117
128
|
function readRequestBody(req) {
|
|
129
|
+
if (req.aborted || req.destroyed) return Promise.reject(createAbortError());
|
|
118
130
|
return new Promise((resolve, reject) => {
|
|
119
131
|
const chunks = [];
|
|
120
132
|
let size = 0;
|
|
@@ -157,35 +169,40 @@ function readRequestBody(req) {
|
|
|
157
169
|
});
|
|
158
170
|
}
|
|
159
171
|
|
|
160
|
-
function extractModelId(body, contentType = '') {
|
|
161
|
-
if (
|
|
172
|
+
function extractModelId(body, contentType = '', requestUrl = '') {
|
|
173
|
+
if (body?.length && String(contentType).includes('json')) {
|
|
174
|
+
try {
|
|
175
|
+
const parsed = JSON.parse(body.toString('utf8'));
|
|
176
|
+
const bodyModel = typeof parsed.model === 'string'
|
|
177
|
+
? parsed.model.trim()
|
|
178
|
+
: String(parsed.model?.id || parsed.model?.name || parsed.modelId || parsed.model_id || '').trim();
|
|
179
|
+
if (bodyModel) return bodyModel;
|
|
180
|
+
} catch {
|
|
181
|
+
// Some provider endpoints carry the model in the URL instead of JSON.
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
162
185
|
try {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
186
|
+
const url = new URL(String(requestUrl || '/'), 'http://127.0.0.1');
|
|
187
|
+
const queryModel = url.searchParams.get('model')
|
|
188
|
+
|| url.searchParams.get('model_id')
|
|
189
|
+
|| url.searchParams.get('modelId');
|
|
190
|
+
if (queryModel?.trim()) return queryModel.trim();
|
|
191
|
+
|
|
192
|
+
// Gemini and Vertex encode the model in paths such as
|
|
193
|
+
// /v1beta/models/gemini-2.5-pro:generateContent.
|
|
194
|
+
const match = url.pathname.match(/(?:^|\/)models\/([^/:]+):[^/]+$/i);
|
|
195
|
+
if (match?.[1]) {
|
|
196
|
+
try {
|
|
197
|
+
return decodeURIComponent(match[1]).trim();
|
|
198
|
+
} catch {
|
|
199
|
+
return match[1].trim();
|
|
200
|
+
}
|
|
167
201
|
}
|
|
168
|
-
return String(parsed.modelId || parsed.model_id || '').trim();
|
|
169
202
|
} catch {
|
|
170
203
|
return '';
|
|
171
204
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
function normalizeModelId(value) {
|
|
175
|
-
return String(value || '').trim().replace(/:(minimal|low|medium|high|xhigh|max|off)$/i, '');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function channelSupportsModel(channel, modelId) {
|
|
179
|
-
const requested = normalizeModelId(modelId).toLowerCase();
|
|
180
|
-
if (!requested) return true;
|
|
181
|
-
const declared = [];
|
|
182
|
-
(Array.isArray(channel.allowedModels) ? channel.allowedModels : []).forEach(value => declared.push(value));
|
|
183
|
-
(Array.isArray(channel.models) ? channel.models : []).forEach((model) => {
|
|
184
|
-
declared.push(typeof model === 'string' ? model : (model?.id || model?.name));
|
|
185
|
-
});
|
|
186
|
-
if (channel.model) declared.push(channel.model);
|
|
187
|
-
const normalized = [...new Set(declared.map(normalizeModelId).filter(Boolean).map(value => value.toLowerCase()))];
|
|
188
|
-
return normalized.length === 0 || normalized.includes(requested);
|
|
205
|
+
return '';
|
|
189
206
|
}
|
|
190
207
|
|
|
191
208
|
function writeJson(res, statusCode, payload) {
|
|
@@ -253,6 +270,8 @@ function createOmpGateway(options = {}) {
|
|
|
253
270
|
const getChannels = options.getChannels;
|
|
254
271
|
const allocate = options.allocateChannel || allocateChannel;
|
|
255
272
|
const release = options.releaseChannel || releaseChannel;
|
|
273
|
+
const readSchedulerState = options.getSchedulerState || getSchedulerState;
|
|
274
|
+
const publishSchedulerState = options.broadcastSchedulerState || broadcastSchedulerState;
|
|
256
275
|
const onSuccess = options.recordSuccess || recordSuccess;
|
|
257
276
|
const onFailure = options.recordFailure || recordFailure;
|
|
258
277
|
const websocketProxy = httpProxy.createProxyServer({
|
|
@@ -269,6 +288,22 @@ function createOmpGateway(options = {}) {
|
|
|
269
288
|
let inflightRequests = 0;
|
|
270
289
|
const activeUpstreams = new Set();
|
|
271
290
|
|
|
291
|
+
function broadcastCurrentSchedulerState() {
|
|
292
|
+
try {
|
|
293
|
+
publishSchedulerState('omp', readSchedulerState('omp'));
|
|
294
|
+
} catch {
|
|
295
|
+
// Scheduler broadcasts are observational and must not interrupt a request.
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function releaseAndBroadcast(channelId) {
|
|
300
|
+
try {
|
|
301
|
+
release(channelId, 'omp');
|
|
302
|
+
} finally {
|
|
303
|
+
broadcastCurrentSchedulerState();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
272
307
|
function gatewayDescriptor() {
|
|
273
308
|
return { host, port, secret };
|
|
274
309
|
}
|
|
@@ -282,7 +317,7 @@ function createOmpGateway(options = {}) {
|
|
|
282
317
|
const channels = currentChannels();
|
|
283
318
|
const candidateIds = route.channelIds.filter((channelId) => {
|
|
284
319
|
const channel = channels.find(item => item.id === channelId);
|
|
285
|
-
return channel &&
|
|
320
|
+
return channel && channelSupportsOmpModel(channel, modelId);
|
|
286
321
|
});
|
|
287
322
|
if (candidateIds.length === 0) {
|
|
288
323
|
throw Object.assign(
|
|
@@ -290,7 +325,7 @@ function createOmpGateway(options = {}) {
|
|
|
290
325
|
{ statusCode: 422 }
|
|
291
326
|
);
|
|
292
327
|
}
|
|
293
|
-
|
|
328
|
+
const channel = await allocate({
|
|
294
329
|
source: 'omp',
|
|
295
330
|
enableSessionBinding: false,
|
|
296
331
|
candidateIds,
|
|
@@ -298,9 +333,11 @@ function createOmpGateway(options = {}) {
|
|
|
298
333
|
routingGroup: route.routingGroup,
|
|
299
334
|
providerKey: route.providerKey,
|
|
300
335
|
providerApi: route.providerApi,
|
|
301
|
-
modelId:
|
|
336
|
+
modelId: normalizeOmpModelId(modelId),
|
|
302
337
|
signal
|
|
303
338
|
});
|
|
339
|
+
if (channel) broadcastCurrentSchedulerState();
|
|
340
|
+
return channel;
|
|
304
341
|
}
|
|
305
342
|
|
|
306
343
|
function forwardAttempt(req, route, channel, body, signal) {
|
|
@@ -309,7 +346,7 @@ function createOmpGateway(options = {}) {
|
|
|
309
346
|
const releaseOnce = () => {
|
|
310
347
|
if (released) return;
|
|
311
348
|
released = true;
|
|
312
|
-
|
|
349
|
+
releaseAndBroadcast(channel.id);
|
|
313
350
|
};
|
|
314
351
|
if (signal?.aborted) {
|
|
315
352
|
releaseOnce();
|
|
@@ -363,6 +400,9 @@ function createOmpGateway(options = {}) {
|
|
|
363
400
|
});
|
|
364
401
|
|
|
365
402
|
activeUpstreams.add(upstreamRequest);
|
|
403
|
+
upstreamRequest.setTimeout(PROXY_IDLE_TIMEOUT_MS, () => {
|
|
404
|
+
upstreamRequest.destroy(new Error('OMP upstream request timeout'));
|
|
405
|
+
});
|
|
366
406
|
const abortUpstream = () => {
|
|
367
407
|
const error = createAbortError();
|
|
368
408
|
if (upstreamResponse) {
|
|
@@ -385,7 +425,7 @@ function createOmpGateway(options = {}) {
|
|
|
385
425
|
async function handleProxyRequest(req, res, route, signal) {
|
|
386
426
|
const body = await readRequestBody(req);
|
|
387
427
|
if (signal?.aborted) throw createAbortError();
|
|
388
|
-
const modelId = extractModelId(body, req.headers['content-type']);
|
|
428
|
+
const modelId = extractModelId(body, req.headers['content-type'], route.upstreamPath);
|
|
389
429
|
const attemptedIds = [];
|
|
390
430
|
let lastError = null;
|
|
391
431
|
|
|
@@ -398,7 +438,7 @@ function createOmpGateway(options = {}) {
|
|
|
398
438
|
break;
|
|
399
439
|
}
|
|
400
440
|
if (signal?.aborted) {
|
|
401
|
-
|
|
441
|
+
releaseAndBroadcast(channel.id);
|
|
402
442
|
lastError = createAbortError();
|
|
403
443
|
break;
|
|
404
444
|
}
|
|
@@ -409,7 +449,7 @@ function createOmpGateway(options = {}) {
|
|
|
409
449
|
if (attempt === 0 && RETRYABLE_STATUS.has(result.statusCode)) {
|
|
410
450
|
result.response.destroy();
|
|
411
451
|
result.release();
|
|
412
|
-
onFailure(channel.id, 'omp');
|
|
452
|
+
if (!signal?.aborted) onFailure(channel.id, 'omp');
|
|
413
453
|
lastError = Object.assign(new Error(`OMP upstream returned HTTP ${result.statusCode}`), {
|
|
414
454
|
statusCode: result.statusCode
|
|
415
455
|
});
|
|
@@ -427,7 +467,7 @@ function createOmpGateway(options = {}) {
|
|
|
427
467
|
if (result.statusCode < 400 && !error) {
|
|
428
468
|
onSuccess(channel.id, 'omp');
|
|
429
469
|
} else {
|
|
430
|
-
onFailure(channel.id, 'omp');
|
|
470
|
+
if (!signal?.aborted) onFailure(channel.id, 'omp');
|
|
431
471
|
}
|
|
432
472
|
resolve();
|
|
433
473
|
};
|
|
@@ -455,7 +495,7 @@ function createOmpGateway(options = {}) {
|
|
|
455
495
|
if (error?.name === 'AbortError') {
|
|
456
496
|
break;
|
|
457
497
|
}
|
|
458
|
-
onFailure(channel.id, 'omp');
|
|
498
|
+
if (!signal?.aborted) onFailure(channel.id, 'omp');
|
|
459
499
|
}
|
|
460
500
|
}
|
|
461
501
|
|
|
@@ -514,6 +554,11 @@ function createOmpGateway(options = {}) {
|
|
|
514
554
|
}
|
|
515
555
|
});
|
|
516
556
|
|
|
557
|
+
req.proxyPlatform = 'omp';
|
|
558
|
+
let admitted = false;
|
|
559
|
+
memoryPressureMiddleware(req, res, () => { admitted = true; });
|
|
560
|
+
if (!admitted) return;
|
|
561
|
+
|
|
517
562
|
inflightRequests++;
|
|
518
563
|
const abortController = new AbortController();
|
|
519
564
|
const abortDownstream = () => abortController.abort();
|
|
@@ -541,6 +586,7 @@ function createOmpGateway(options = {}) {
|
|
|
541
586
|
const statusText = {
|
|
542
587
|
401: 'Unauthorized',
|
|
543
588
|
404: 'Not Found',
|
|
589
|
+
422: 'Unprocessable Entity',
|
|
544
590
|
503: 'Service Unavailable'
|
|
545
591
|
}[statusCode] || 'Bad Gateway';
|
|
546
592
|
socket.end([
|
|
@@ -554,6 +600,8 @@ function createOmpGateway(options = {}) {
|
|
|
554
600
|
}
|
|
555
601
|
|
|
556
602
|
async function handleUpgrade(req, socket, head) {
|
|
603
|
+
req.proxyPlatform = 'omp';
|
|
604
|
+
if (rejectUpgradeUnderPressure(req, socket)) return;
|
|
557
605
|
if (draining) {
|
|
558
606
|
rejectUpgrade(socket, 503, 'OMP gateway is draining');
|
|
559
607
|
return;
|
|
@@ -583,10 +631,22 @@ function createOmpGateway(options = {}) {
|
|
|
583
631
|
route.upstreamPath = `${route.upstreamPath}${parsed.search}`;
|
|
584
632
|
|
|
585
633
|
let channel;
|
|
634
|
+
const controller = new AbortController();
|
|
635
|
+
const abortWaiting = () => controller.abort();
|
|
636
|
+
socket.once('close', abortWaiting);
|
|
637
|
+
socket.once('error', abortWaiting);
|
|
586
638
|
try {
|
|
587
|
-
|
|
639
|
+
const modelId = extractModelId(null, '', route.upstreamPath);
|
|
640
|
+
channel = await allocateForRoute(route, [], modelId, controller.signal);
|
|
588
641
|
} catch (error) {
|
|
589
|
-
rejectUpgrade(socket, 503, error.message || 'No compatible OMP channel is available');
|
|
642
|
+
rejectUpgrade(socket, error.statusCode || 503, error.message || 'No compatible OMP channel is available');
|
|
643
|
+
return;
|
|
644
|
+
} finally {
|
|
645
|
+
socket.removeListener('close', abortWaiting);
|
|
646
|
+
socket.removeListener('error', abortWaiting);
|
|
647
|
+
}
|
|
648
|
+
if (controller.signal.aborted || socket.destroyed) {
|
|
649
|
+
releaseAndBroadcast(channel.id);
|
|
590
650
|
return;
|
|
591
651
|
}
|
|
592
652
|
|
|
@@ -607,7 +667,7 @@ function createOmpGateway(options = {}) {
|
|
|
607
667
|
if (!failed) {
|
|
608
668
|
onSuccess(channel.id, 'omp');
|
|
609
669
|
}
|
|
610
|
-
|
|
670
|
+
releaseAndBroadcast(channel.id);
|
|
611
671
|
};
|
|
612
672
|
socket.once('close', releaseOnce);
|
|
613
673
|
socket.once('error', (error) => {
|
|
@@ -631,7 +691,9 @@ function createOmpGateway(options = {}) {
|
|
|
631
691
|
websocketProxy.ws(req, socket, head, {
|
|
632
692
|
target: upstream.origin,
|
|
633
693
|
changeOrigin: true,
|
|
634
|
-
secure: upstream.protocol === 'https:'
|
|
694
|
+
secure: upstream.protocol === 'https:',
|
|
695
|
+
proxyTimeout: PROXY_IDLE_TIMEOUT_MS,
|
|
696
|
+
timeout: 0
|
|
635
697
|
});
|
|
636
698
|
} catch (error) {
|
|
637
699
|
markFailedOnce(error);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MIB = 1024 * 1024;
|
|
4
|
+
const PAUSE_RSS_BYTES = 380 * MIB;
|
|
5
|
+
const RESUME_RSS_BYTES = 330 * MIB;
|
|
6
|
+
const CHECK_INTERVAL_MS = 5_000;
|
|
7
|
+
let timer = null;
|
|
8
|
+
let paused = false;
|
|
9
|
+
let lowChecks = 0;
|
|
10
|
+
let rejected = 0;
|
|
11
|
+
let latestRss = 0;
|
|
12
|
+
let persistenceBlocked = false;
|
|
13
|
+
|
|
14
|
+
function readPersistencePressure() {
|
|
15
|
+
// Admission must not initialize storage or enter the statistics/driver cycle.
|
|
16
|
+
const statistics = require.cache[require.resolve('./statistics-service')]?.exports;
|
|
17
|
+
const nativeLogs = require.cache[require.resolve('./native-log-observer')]?.exports;
|
|
18
|
+
return statistics?.getStatisticsQueueDiagnostics?.().blocked === true
|
|
19
|
+
|| nativeLogs?.getNativeLogDiagnostics?.().persistenceBlocked === true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function checkMemoryPressure() {
|
|
23
|
+
latestRss = process.memoryUsage().rss;
|
|
24
|
+
persistenceBlocked = readPersistencePressure();
|
|
25
|
+
if (latestRss >= PAUSE_RSS_BYTES || persistenceBlocked) {
|
|
26
|
+
paused = true;
|
|
27
|
+
lowChecks = 0;
|
|
28
|
+
} else if (paused) {
|
|
29
|
+
lowChecks = latestRss < RESUME_RSS_BYTES ? lowChecks + 1 : 0;
|
|
30
|
+
if (lowChecks >= 2) {
|
|
31
|
+
paused = false;
|
|
32
|
+
lowChecks = 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return getMemoryPressureDiagnostics();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function startMemoryPressureMonitor() {
|
|
39
|
+
if (timer) return false;
|
|
40
|
+
checkMemoryPressure();
|
|
41
|
+
timer = setInterval(checkMemoryPressure, CHECK_INTERVAL_MS);
|
|
42
|
+
timer.unref?.();
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function stopMemoryPressureMonitor() {
|
|
47
|
+
if (timer) clearInterval(timer);
|
|
48
|
+
timer = null;
|
|
49
|
+
paused = false;
|
|
50
|
+
lowChecks = 0;
|
|
51
|
+
rejected = 0;
|
|
52
|
+
persistenceBlocked = false;
|
|
53
|
+
latestRss = 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isUnderMemoryPressure() {
|
|
57
|
+
startMemoryPressureMonitor();
|
|
58
|
+
// Admission can trip the guard immediately, but only periodic checks recover it.
|
|
59
|
+
latestRss = process.memoryUsage().rss;
|
|
60
|
+
persistenceBlocked = readPersistencePressure();
|
|
61
|
+
if (latestRss >= PAUSE_RSS_BYTES || persistenceBlocked) {
|
|
62
|
+
paused = true;
|
|
63
|
+
lowChecks = 0;
|
|
64
|
+
}
|
|
65
|
+
return paused;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function errorBody(platform) {
|
|
69
|
+
const message = 'Server memory pressure; retry in 5 seconds.';
|
|
70
|
+
if (platform === 'claude') return { type: 'error', error: { type: 'overloaded_error', message } };
|
|
71
|
+
if (platform === 'gemini') return { error: { code: 503, status: 'UNAVAILABLE', message } };
|
|
72
|
+
return { error: { type: 'server_error', code: 'memory_pressure', message } };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function memoryPressureMiddleware(req, res, next) {
|
|
76
|
+
if (!isUnderMemoryPressure()) return next();
|
|
77
|
+
rejected += 1;
|
|
78
|
+
res.statusCode = 503;
|
|
79
|
+
res.setHeader('Retry-After', '5');
|
|
80
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
81
|
+
res.end(JSON.stringify(errorBody(req.proxyPlatform)));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function rejectUpgradeUnderPressure(req, socket) {
|
|
85
|
+
if (!isUnderMemoryPressure()) return false;
|
|
86
|
+
rejected += 1;
|
|
87
|
+
const body = JSON.stringify(errorBody(req.proxyPlatform));
|
|
88
|
+
socket.end(`HTTP/1.1 503 Service Unavailable\r\nRetry-After: 5\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r\n\r\n${body}`);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getMemoryPressureDiagnostics() {
|
|
93
|
+
return { paused, rss: latestRss, lowChecks, rejected, persistenceBlocked };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
memoryPressureMiddleware, rejectUpgradeUnderPressure,
|
|
98
|
+
startMemoryPressureMonitor, stopMemoryPressureMonitor,
|
|
99
|
+
getMemoryPressureDiagnostics,
|
|
100
|
+
_test: { checkMemoryPressure }
|
|
101
|
+
};
|
|
@@ -92,6 +92,7 @@ function createChannelDriver({
|
|
|
92
92
|
formatCliChannelDetails,
|
|
93
93
|
dashboardChannelShape = 'object',
|
|
94
94
|
modelListType,
|
|
95
|
+
schedulerStateProvider,
|
|
95
96
|
...context
|
|
96
97
|
} = {}) {
|
|
97
98
|
let service;
|
|
@@ -245,6 +246,17 @@ function createChannelDriver({
|
|
|
245
246
|
? call('markRecentlyUsed', 'markChannelAsRecentlyUsed', [id], sanitizeChannel)
|
|
246
247
|
: invalid(platform, capability, 'markRecentlyUsed', new Error('Channel id is required'));
|
|
247
248
|
driver.saveOrder = order => call('saveOrder', 'saveChannelOrder', [order]);
|
|
249
|
+
driver.poolStatus = () => {
|
|
250
|
+
try {
|
|
251
|
+
const readSchedulerState = schedulerStateProvider
|
|
252
|
+
|| require('../server/services/channel-scheduler').getSchedulerState;
|
|
253
|
+
return ok(platform, capability, 'poolStatus', {
|
|
254
|
+
scheduler: readSchedulerState(platform)
|
|
255
|
+
});
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return failed(platform, capability, 'poolStatus', error);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
248
260
|
|
|
249
261
|
return driver;
|
|
250
262
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.header-button[data-v-b515354d]{border:1px solid var(--border-primary);background:var(--bg-elevated);cursor:pointer;border-radius:999px;outline:none;justify-content:center;align-items:center;width:40px;height:40px;padding:0;transition:all .2s;display:inline-flex;box-shadow:0 10px 24px #0f171d14}.header-button[data-v-b515354d] .n-icon{color:var(--text-tertiary);transition:all .2s}.header-button[data-v-b515354d]:hover:not(.disabled){background:color-mix(in srgb, var(--bg-elevated) 90%, var(--primary-color) 10%);border-color:#18a0583d;box-shadow:0 14px 30px #0f171d1f}.header-button[data-v-b515354d]:hover:not(.disabled) .n-icon{color:var(--primary-color,#18a058)}.header-button[data-v-b515354d]:active:not(.disabled){transform:scale(.95)}.header-button.active[data-v-b515354d]{background:linear-gradient(135deg,#18a05824,#18a0580f);border-color:#18a05852}.header-button.active[data-v-b515354d] .n-icon{color:#18a058!important}.header-button.disabled[data-v-b515354d]{cursor:not-allowed;opacity:.5}[data-theme=dark] .header-button[data-v-b515354d]{box-shadow:none;background:#0d171eeb;border-color:#364c56e6}[data-theme=dark] .header-button[data-v-b515354d]:hover:not(.disabled){background:#14222afa;border-color:#18a05847;box-shadow:0 0 0 1px #18a05814}.modal-header[data-v-7ea71ef7]{align-items:center;gap:10px;display:flex}.warning-icon[data-v-7ea71ef7]{color:#f59e0b}.header-title[data-v-7ea71ef7]{color:var(--text-primary);font-size:16px;font-weight:600}.modal-content[data-v-7ea71ef7]{padding:4px 0}.desc-text[data-v-7ea71ef7]{color:var(--text-secondary);margin:0 0 16px;font-size:13px;line-height:1.6}.conflict-list[data-v-7ea71ef7]{background:var(--bg-tertiary);border-radius:6px;padding:12px}.list-header[data-v-7ea71ef7]{border-bottom:1px solid var(--border-primary);align-items:center;gap:12px;margin-bottom:10px;padding-bottom:10px;display:flex}.selected-hint[data-v-7ea71ef7]{color:var(--text-tertiary);font-size:12px}.conflict-items[data-v-7ea71ef7]{flex-direction:column;gap:8px;display:flex}.conflict-item[data-v-7ea71ef7]{background:var(--bg-secondary);cursor:pointer;border:1px solid #0000;border-radius:6px;align-items:flex-start;gap:10px;padding:10px 12px;transition:all .15s;display:flex}.conflict-item[data-v-7ea71ef7]:hover{background:#18a05805;border-color:#18a05866}.conflict-item.selected[data-v-7ea71ef7]{background:#18a0580f;border-color:#18a058}.item-content[data-v-7ea71ef7]{flex:1;min-width:0}.item-header[data-v-7ea71ef7]{align-items:center;gap:8px;margin-bottom:4px;display:flex}.var-name[data-v-7ea71ef7]{color:var(--text-primary);background:var(--bg-tertiary);border-radius:4px;padding:2px 6px;font-size:13px;font-weight:600}.var-value[data-v-7ea71ef7]{color:var(--text-secondary);word-break:break-all;margin-bottom:4px;font-family:SF Mono,Monaco,monospace;font-size:12px}.var-source[data-v-7ea71ef7]{color:var(--text-tertiary);font-size:11px}.source-label[data-v-7ea71ef7]{color:var(--text-quaternary)}.modal-footer[data-v-7ea71ef7]{justify-content:flex-end;gap:8px;display:flex}.process-env-alert[data-v-7ea71ef7]{margin-bottom:16px}.alert-content[data-v-7ea71ef7]{font-size:12px;line-height:1.6}.alert-title[data-v-7ea71ef7]{margin:0 0 8px;font-weight:500}.alert-list[data-v-7ea71ef7]{margin:0;padding-left:20px}.alert-list li[data-v-7ea71ef7]{margin:4px 0}.alert-list code[data-v-7ea71ef7]{background:var(--bg-tertiary);color:#18a058;border-radius:4px;padding:2px 6px;font-family:SF Mono,Monaco,monospace;font-size:11px}.alert-hint[data-v-7ea71ef7]{color:var(--text-tertiary);margin:8px 0 0;font-size:11px}.channel-card[data-v-beb1848e]{border:1px solid var(--border-primary);background:var(--bg-primary);border-radius:8px;transition:all .2s}.channel-card[data-v-beb1848e]:hover{border-color:var(--border-secondary)}.channel-header[data-v-beb1848e]{border-bottom:1px solid var(--border-primary);justify-content:space-between;align-items:center;gap:8px;padding:12px 16px;display:flex}.channel-title[data-v-beb1848e]{flex:auto;align-items:center;gap:6px;min-width:0;margin-right:0;display:flex}.channel-name[data-v-beb1848e]{color:var(--text-primary);text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;font-size:13px;font-weight:600;overflow:hidden}.channel-title[data-v-beb1848e] .n-tag{flex-shrink:0;padding:0 5px;font-size:11px}.collapse-btn[data-v-beb1848e]{flex-shrink:0}.collapse-btn .n-icon.collapsed[data-v-beb1848e]{transform:rotate(-90deg)}.channel-actions[data-v-beb1848e]{flex-wrap:nowrap;flex:none;justify-content:flex-end;align-items:center;gap:4px;max-width:100%;margin-left:auto;display:flex}.channel-actions[data-v-beb1848e] .n-button{min-width:0;height:22px;padding:0 5px;font-size:11px}.channel-actions[data-v-beb1848e] .n-button__content{gap:2px}.channel-actions[data-v-beb1848e] .n-button__icon{margin:0}.apply-btn[data-v-beb1848e]{display:none}.channel-card:hover .apply-btn[data-v-beb1848e]{display:inline-flex}.test-result[data-v-beb1848e]{border-bottom:1px solid var(--border-primary);background:var(--bg-secondary);padding:10px 16px}.test-result.success[data-v-beb1848e]{background:#18a0580f;border-left:3px solid #18a058}.test-result.failed[data-v-beb1848e]{background:#f56c6c0f;border-left:3px solid #f56c6c}.test-result.warning[data-v-beb1848e]{background:#d990000f;border-left:3px solid #d99000}.test-result-header[data-v-beb1848e]{align-items:center;gap:12px;display:flex}.test-result-status[data-v-beb1848e]{align-items:center;gap:6px;font-size:13px;font-weight:500;display:flex}.test-result.success .test-result-status[data-v-beb1848e]{color:#18a058}.test-result.failed .test-result-status[data-v-beb1848e]{color:#f56c6c}.test-result.warning .test-result-status[data-v-beb1848e]{color:#b87900}.test-result-info[data-v-beb1848e]{flex:1;align-items:center;gap:10px;display:flex}.test-result-info .latency[data-v-beb1848e]{color:var(--text-primary);background:var(--bg-tertiary);border-radius:4px;padding:2px 8px;font-family:SF Mono,Monaco,monospace;font-size:13px;font-weight:600}.test-result-info .status-code[data-v-beb1848e]{color:var(--text-tertiary);font-family:SF Mono,Monaco,monospace;font-size:11px}.test-result-error[data-v-beb1848e]{color:#f56c6c;word-break:break-word;background:#f56c6c14;border-radius:4px;margin-top:8px;padding:8px 10px;font-size:12px;line-height:1.4}.test-result-warning[data-v-beb1848e]{color:#9b6800;word-break:break-word;background:#d9900014;border-radius:4px;margin-top:8px;padding:8px 10px;font-size:12px;line-height:1.4}.channel-info[data-v-beb1848e]{padding:12px 16px}.info-main[data-v-beb1848e]{flex-direction:column;gap:8px;display:flex}.info-row[data-v-beb1848e]{align-items:center;gap:12px;display:flex}.info-row .label[data-v-beb1848e]{min-width:80px;font-size:13px}.info-row .value[data-v-beb1848e]{flex:1;font-size:13px}.info-row .value.mono[data-v-beb1848e]{font-family:SF Mono,Monaco,monospace}.info-footer[data-v-beb1848e]{border-top:1px solid var(--border-primary);justify-content:space-between;align-items:center;margin-top:12px;padding-top:8px;display:flex}.footer-spacer[data-v-beb1848e]{flex:1}.channel-meta[data-v-beb1848e]{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:12px;font-size:12px;display:flex}.balance-meta-tag[data-v-beb1848e]{cursor:pointer;flex-shrink:0;max-width:110px}.balance-meta-tag[data-v-beb1848e] .n-tag__content{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.meta-item[data-v-beb1848e]{color:var(--text-tertiary);white-space:nowrap}.meta-value[data-v-beb1848e]{color:var(--text-secondary);font-weight:500}.meta-active[data-v-beb1848e]{color:#18a058}@media (width<=1024px){.channel-header[data-v-beb1848e],.channel-info[data-v-beb1848e]{padding:10px 12px}.channel-actions[data-v-beb1848e]{gap:6px}.info-row[data-v-beb1848e]{gap:8px}.info-row .label[data-v-beb1848e]{min-width:70px;font-size:12px}.info-row .value[data-v-beb1848e]{font-size:12px}.channel-meta[data-v-beb1848e]{gap:12px}}@media (width<=768px){.channel-header[data-v-beb1848e]{flex-direction:column;align-items:stretch;gap:8px;padding:8px 10px}.channel-title[data-v-beb1848e]{margin-right:0}.channel-actions[data-v-beb1848e]{justify-content:space-between}.channel-actions[data-v-beb1848e] .n-button{padding:0 8px;font-size:12px}.channel-info[data-v-beb1848e]{padding:8px 10px}.info-row[data-v-beb1848e]{flex-direction:column;align-items:flex-start;gap:4px}.info-row .label[data-v-beb1848e]{min-width:auto}.info-footer[data-v-beb1848e]{flex-direction:column;align-items:flex-start;gap:8px;margin-top:8px}.channel-meta[data-v-beb1848e]{justify-content:flex-start;align-self:stretch;gap:8px 12px}}@media (width<=640px){.channel-card[data-v-beb1848e]{border-radius:6px}.channel-header[data-v-beb1848e]{padding:6px 8px}.channel-name[data-v-beb1848e]{min-width:0;font-size:12px}.channel-title[data-v-beb1848e]{min-width:0}.channel-title[data-v-beb1848e] .n-tag{font-size:10px}.channel-info[data-v-beb1848e]{padding:6px 8px}.info-row .label[data-v-beb1848e],.info-row .value[data-v-beb1848e]{font-size:11px}.channel-actions[data-v-beb1848e] .n-button{height:24px;padding:0 6px;font-size:11px}.channel-meta[data-v-beb1848e]{font-size:11px}}@media (width<=480px){.channel-header[data-v-beb1848e],.channel-info[data-v-beb1848e]{padding:4px 6px}.channel-actions[data-v-beb1848e]{gap:4px}.channel-actions[data-v-beb1848e] .n-button{min-width:0;height:22px;padding:0 4px;font-size:10px}}.model-redirect-editor[data-v-8ebdcad0]{width:100%}.redirect-row[data-v-8ebdcad0]{align-items:center;gap:8px;margin-bottom:8px;display:flex}.redirect-input[data-v-8ebdcad0]{flex:1;min-width:0}.redirect-arrow[data-v-8ebdcad0]{flex-shrink:0;justify-content:center;align-items:center;width:24px;display:flex}.add-btn[data-v-8ebdcad0]{width:100%;margin-top:4px}.channel-auth-section[data-v-8fcf77d4]{gap:10px;width:100%;display:grid}.oauth-content[data-v-8fcf77d4],.oauth-quota[data-v-8fcf77d4]{gap:8px;display:grid}.oauth-meta[data-v-8fcf77d4]{color:var(--text-color-3)}.oauth-quota[data-v-8fcf77d4]{grid-template-columns:repeat(3,auto);align-items:center}.loading-container{justify-content:center;align-items:center;padding:80px 0;display:flex}.empty-state{justify-content:center;align-items:center;min-height:200px;display:flex}.model-catalog-control{flex-wrap:wrap;align-items:center;gap:6px 8px;margin:-3px 0 10px;font-size:12px;line-height:1.45;display:flex}.model-catalog-status{color:var(--n-text-color-3)}.model-catalog-status.is-loading{color:var(--n-info-color,var(--n-text-color-3))}.model-catalog-status.is-error{color:var(--n-error-color,#d03050)}.channels-list{flex-direction:column;gap:10px;display:flex;position:relative;overflow:hidden}.channel-card{border:1px solid var(--n-divider-color);background:var(--bg-secondary);border-left:3px solid #18a058;border-radius:6px;padding:10px 12px;transition:all .2s;box-shadow:0 1px 2px #0000000a}.channel-card.active{border-color:var(--n-primary-color);background:var(--n-primary-color-suppl,#18a05814);box-shadow:0 2px 8px #18a0581a}.channel-card:hover{border-color:var(--n-border-color);box-shadow:0 2px 8px #00000014}.channel-card.disabled{opacity:.6;border-left-color:#909399}.channel-card.status-frozen{background:linear-gradient(90deg,#d0305014 0%,#0000 100%);border-left-color:#d03050}.channel-card.status-checking{background:linear-gradient(90deg,#f0a02014 0%,#0000 100%);border-left-color:#f0a020}.channel-card.collapsed .channel-info{display:none}.channel-header{justify-content:space-between;align-items:center;gap:12px;display:flex}.channel-title{flex:1;align-items:center;gap:6px;min-width:0;display:flex}.channel-name{color:var(--n-text-color);white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:600;overflow:hidden}.channel-actions{flex-shrink:0;align-items:center;gap:6px;display:flex}.channel-actions .apply-btn{opacity:0;transition:opacity .2s}.channel-card:hover .channel-actions .apply-btn{opacity:1}.channel-actions .n-switch{margin-left:2px}.channel-info{border-top:1px dashed var(--n-border-color);margin-top:8px;padding-top:8px}.info-main{flex-direction:column;gap:4px;display:flex}.info-row{align-items:center;gap:8px;display:flex}.label{color:var(--n-text-color-3);flex-shrink:0;width:56px;font-size:11px}.value{color:var(--n-text-color-2);word-break:break-word;flex:1;font-size:12px}.value.mono{font-family:IBM Plex Mono,SFMono-Regular,Consolas,monospace}.info-footer{border-top:1px dashed var(--n-border-color);align-items:center;gap:8px;margin-top:6px;padding-top:6px;display:flex}.footer-spacer{flex:1}.channel-meta{flex-shrink:0;gap:12px;display:flex}.meta-item{color:var(--n-text-color-3);white-space:nowrap;font-size:11px}.meta-value{color:#18a058;font-weight:600}.meta-value.meta-active{color:#f0a020;animation:1.5s ease-in-out infinite pulse}@keyframes pulse{0%,to{opacity:1}50%{opacity:.6}}.collapse-btn{align-items:center;padding:0;display:flex}.collapse-btn .n-icon{transition:transform .2s}.collapse-btn .n-icon.collapsed{transform:rotate(-90deg)}.dialog-footer{justify-content:flex-end;gap:8px;display:flex}.channel-form{padding:0}.form-section{background:var(--n-color);border:1px solid var(--n-border-color);border-radius:6px;margin-bottom:10px;padding:12px}.form-section:last-child{margin-bottom:0}.section-title{color:var(--n-text-color);border-bottom:1px solid var(--n-border-color);align-items:center;gap:6px;margin-bottom:10px;padding-bottom:6px;font-size:12px;font-weight:600;display:flex}.section-desc{color:var(--n-text-color-3);font-size:11px;font-weight:400}.form-section.collapsible{background:var(--n-color-hover)}.channel-form .n-radio-group{width:100%}.channel-form .n-radio-group .n-space{gap:16px;width:100%;display:flex}.channel-form .n-radio{border:1px solid var(--n-border-color);border-radius:6px;flex:1;justify-content:center;padding:8px 16px;transition:all .2s;display:flex}.channel-form .n-radio:hover,.channel-form .n-radio.n-radio--checked{border-color:var(--n-primary-color);background:var(--n-primary-color-suppl,#18a05814)}.channel-form .n-form-item{margin-bottom:10px}.channel-form .n-form-item:last-child{margin-bottom:0}.channel-form .n-form-item-label{padding-bottom:0;font-size:12px}.auth-provider-status{flex-direction:column;justify-content:center;gap:6px;width:100%;min-height:28px;display:flex}.auth-provider-tags{min-height:24px}.auth-provider-message{color:var(--n-text-color-3);font-size:12px;line-height:1.45}.auth-provider-message.warning{color:#c97a00}.model-definitions-editor[data-v-60c67edc]{width:100%}.model-definitions-toolbar[data-v-60c67edc]{align-items:center;gap:10px;margin-bottom:8px;display:flex}.proxy-logs[data-v-1d31e3d2]{background:#fff;border-top:1px solid #e5e7eb;flex-direction:column;height:100%;display:flex}[data-theme=dark] .proxy-logs[data-v-1d31e3d2]{background:var(--bg-secondary);border-top:1px solid var(--border-primary)}.logs-header[data-v-1d31e3d2]{border-bottom:1px solid #e5e7eb;flex-shrink:0;justify-content:space-between;align-items:center;padding:12px 16px;display:flex}[data-theme=dark] .logs-header[data-v-1d31e3d2]{border-bottom:1px solid var(--border-primary)}.header-left[data-v-1d31e3d2]{align-items:center;gap:12px;display:flex}.title[data-v-1d31e3d2]{color:#18181b;margin:0;font-size:14px;font-weight:600}[data-theme=dark] .title[data-v-1d31e3d2]{color:var(--text-primary)}.today-stats[data-v-1d31e3d2]{align-items:center;gap:6px;display:flex}.today-stats[data-v-1d31e3d2] .n-tag{padding:2px 8px;font-size:11px;font-weight:600}.logs-table[data-v-1d31e3d2]{flex-direction:column;flex:1;display:flex;overflow:hidden}.table-header[data-v-1d31e3d2]{color:#6b7280;white-space:nowrap;background:#f9fafb;border-bottom:1px solid #e5e7eb;flex-shrink:0;padding:8px 12px;font-size:11px;font-weight:600;display:flex}[data-theme=dark] .table-header[data-v-1d31e3d2]{border-bottom:1px solid var(--border-primary);color:var(--text-tertiary);background:#ffffff05}.table-body[data-v-1d31e3d2]{flex:1;min-height:0;padding-bottom:40px;scroll-padding-bottom:40px;overflow:hidden auto}.table-row[data-v-1d31e3d2]{cursor:pointer;border-bottom:1px solid #f3f4f6;padding:8px 12px;font-size:12px;transition:background-color .2s;display:flex;position:relative}[data-theme=dark] .table-row[data-v-1d31e3d2]{border-bottom:1px solid #ffffff0a}.table-row[data-v-1d31e3d2]:hover{background:#f9fafb}[data-theme=dark] .table-row[data-v-1d31e3d2]:hover{background:#ffffff0a}.table-row.new-log[data-v-1d31e3d2],.action-row.new-log[data-v-1d31e3d2]{border-left:3px solid #18a058;padding-left:9px;animation:4.5s cubic-bezier(.25,.46,.45,.94) forwards newLogPulse-1d31e3d2}@keyframes newLogPulse-1d31e3d2{0%{background:linear-gradient(90deg,#18a05826 0%,#18a0580f 100%);box-shadow:0 0 8px #18a05833}5%{background:linear-gradient(90deg,#18a0582e 0%,#18a05814 100%);box-shadow:0 0 12px 1px #18a05840}15%{background:linear-gradient(90deg,#18a05829 0%,#18a05812 100%);box-shadow:0 0 10px 1px #18a05833}30%{background:linear-gradient(90deg,#18a0581f 0%,#18a0580d 100%);box-shadow:0 0 6px #18a05826}50%{background:linear-gradient(90deg,#18a05814 0%,#18a05808 100%);box-shadow:0 0 4px #18a0581a}70%{background:linear-gradient(90deg,#18a0580a 0%,#18a05804 100%);box-shadow:0 0 2px #18a0580f}85%{background:linear-gradient(90deg,#18a05805 0%,#18a05802 100%);box-shadow:0 0 1px #18a05808}to{background:0 0;border-left-color:#0000;box-shadow:0 0 #18a05800}}[data-theme=dark] .table-row.new-log[data-v-1d31e3d2],[data-theme=dark] .action-row.new-log[data-v-1d31e3d2]{animation:4.5s cubic-bezier(.25,.46,.45,.94) forwards newLogPulseDark-1d31e3d2}@keyframes newLogPulseDark-1d31e3d2{0%{background:linear-gradient(90deg,#18a05833 0%,#18a05814 100%);box-shadow:0 0 10px #18a05840}5%{background:linear-gradient(90deg,#18a0583d 0%,#18a0581a 100%);box-shadow:0 0 14px 2px #18a0584d}15%{background:linear-gradient(90deg,#18a05836 0%,#18a05817 100%);box-shadow:0 0 12px 1px #18a05840}30%{background:linear-gradient(90deg,#18a05829 0%,#18a05812 100%);box-shadow:0 0 8px 1px #18a0582e}50%{background:linear-gradient(90deg,#18a0581c 0%,#18a0580b 100%);box-shadow:0 0 5px #18a0581f}70%{background:linear-gradient(90deg,#18a0580f 0%,#18a05806 100%);box-shadow:0 0 3px #18a05814}85%{background:linear-gradient(90deg,#18a05808 0%,#18a05803 100%);box-shadow:0 0 1px #18a0580a}to{background:0 0;border-left-color:#0000;box-shadow:0 0 #18a05800}}.table-row-content[data-v-1d31e3d2]{width:100%;display:flex}.action-row[data-v-1d31e3d2]{color:#166534;background:#f0fdf4;border-bottom:1px solid #f3f4f6;border-left:3px solid #18a058;align-items:center;padding:10px 12px;font-size:13px;display:flex}[data-theme=dark] .action-row[data-v-1d31e3d2]{color:#4ade80;background:#18a0581f;border-bottom:1px solid #ffffff0a}.error-row[data-v-1d31e3d2]{color:#b42318;background:#fef2f2;border-bottom:1px solid #f3f4f6;border-left:3px solid #d03050;align-items:center;padding:10px 12px;font-size:13px;display:flex}[data-theme=dark] .error-row[data-v-1d31e3d2]{color:#fda4af;background:#d030501f;border-bottom:1px solid #ffffff0a}.action-message[data-v-1d31e3d2]{flex:1;font-weight:500}.action-time[data-v-1d31e3d2]{color:#6b7280;margin-left:12px;margin-right:22px;font-family:monospace;font-size:11px}[data-theme=dark] .action-time[data-v-1d31e3d2]{color:var(--text-tertiary)}.col[data-v-1d31e3d2]{white-space:nowrap;text-overflow:ellipsis;align-items:center;display:flex;overflow:hidden}.col-channel-claude[data-v-1d31e3d2]{flex:80px;min-width:0;overflow:hidden}.col-token-claude[data-v-1d31e3d2]{flex:0 0 68px;justify-content:center;min-width:0}.col-time-claude[data-v-1d31e3d2]{flex:0 0 85px;justify-content:flex-end;min-width:85px;padding-left:10px;padding-right:6px;font-family:monospace;font-size:11px}.col-channel-codex[data-v-1d31e3d2]{flex:65px;min-width:0;overflow:hidden}.col-token-codex[data-v-1d31e3d2]{flex:0 0 58px;justify-content:center;min-width:0}.col-time-codex[data-v-1d31e3d2]{flex:0 0 80px;justify-content:flex-end;min-width:80px;padding-left:10px;padding-right:6px;font-family:monospace;font-size:11px}.col-channel-gemini[data-v-1d31e3d2]{flex:80px;min-width:0;overflow:hidden}.col-token-gemini[data-v-1d31e3d2]{flex:0 0 68px;justify-content:center;min-width:0}.col-time-gemini[data-v-1d31e3d2]{flex:0 0 85px;justify-content:flex-end;min-width:85px;padding-left:10px;padding-right:6px;font-family:monospace;font-size:11px}.col-channel-opencode[data-v-1d31e3d2]{flex:65px;min-width:0;overflow:hidden}.col-token-opencode[data-v-1d31e3d2]{flex:0 0 58px;justify-content:center;min-width:0}.col-time-opencode[data-v-1d31e3d2]{flex:0 0 80px;justify-content:flex-end;min-width:80px;padding-left:10px;padding-right:6px;font-family:monospace;font-size:11px}.col-channel-omp[data-v-1d31e3d2]{flex:65px;min-width:0;overflow:hidden}.col-token-omp[data-v-1d31e3d2]{flex:0 0 58px;justify-content:center;min-width:0}.col-time-omp[data-v-1d31e3d2]{flex:0 0 80px;justify-content:flex-end;min-width:80px;padding-left:10px;padding-right:6px;font-family:monospace;font-size:11px}.col-channel-basic[data-v-1d31e3d2]{flex:80px;min-width:0;overflow:hidden}.col-token-basic[data-v-1d31e3d2]{flex:0 0 68px;justify-content:center;min-width:0}.col-time-basic[data-v-1d31e3d2]{flex:0 0 85px;justify-content:flex-end;min-width:85px;padding-left:10px;padding-right:6px;font-family:monospace;font-size:11px}.col-channel[data-v-1d31e3d2]{min-width:0;overflow:hidden}.col-channel .n-tag[data-v-1d31e3d2]{text-overflow:ellipsis;max-width:100%;overflow:hidden}.col-token[data-v-1d31e3d2]{justify-content:center;min-width:0}.col-time[data-v-1d31e3d2]{justify-content:flex-end;font-family:monospace;font-size:11px}.empty-state[data-v-1d31e3d2]{text-align:center;color:#9ca3af;padding:40px 16px;font-size:13px}[data-theme=dark] .empty-state[data-v-1d31e3d2]{color:var(--text-tertiary)}.logs-footer[data-v-1d31e3d2]{color:#6b7280;border-top:1px solid #e5e7eb;justify-content:space-between;align-items:center;padding:8px 16px;font-size:12px;display:flex}[data-theme=dark] .logs-footer[data-v-1d31e3d2]{border-top:1px solid var(--border-primary);color:var(--text-tertiary)}.status[data-v-1d31e3d2]{color:#ef4444;align-items:center;gap:4px;display:flex}.status.connected[data-v-1d31e3d2]{color:#10b981}[data-theme=dark] .status.connected[data-v-1d31e3d2]{color:#4ade80}.count[data-v-1d31e3d2]{color:#6b7280}[data-theme=dark] .count[data-v-1d31e3d2]{color:var(--text-tertiary)}@media (width<=1024px){.logs-header[data-v-1d31e3d2]{padding:10px 12px}.header-left[data-v-1d31e3d2]{gap:8px}.title[data-v-1d31e3d2]{font-size:13px}.today-stats[data-v-1d31e3d2] .n-tag{padding:1px 6px;font-size:10px}.table-header[data-v-1d31e3d2]{padding:6px 10px;font-size:10px}.table-row[data-v-1d31e3d2]{padding:6px 10px}.logs-footer[data-v-1d31e3d2]{padding:6px 12px;font-size:11px}.col-channel-claude[data-v-1d31e3d2],.col-channel-gemini[data-v-1d31e3d2],.col-channel-basic[data-v-1d31e3d2]{flex:0 0 75px}.col-token-claude[data-v-1d31e3d2],.col-token-gemini[data-v-1d31e3d2],.col-token-basic[data-v-1d31e3d2]{flex:0 0 55px}.col-time-claude[data-v-1d31e3d2],.col-time-gemini[data-v-1d31e3d2],.col-time-basic[data-v-1d31e3d2]{flex:0 0 80px;min-width:80px}.col-channel-codex[data-v-1d31e3d2],.col-channel-opencode[data-v-1d31e3d2],.col-channel-omp[data-v-1d31e3d2]{flex:0 0 65px}.col-token-codex[data-v-1d31e3d2],.col-token-opencode[data-v-1d31e3d2],.col-token-omp[data-v-1d31e3d2]{flex:0 0 48px}.col-time-codex[data-v-1d31e3d2],.col-time-opencode[data-v-1d31e3d2],.col-time-omp[data-v-1d31e3d2]{flex:0 0 75px;min-width:75px}}@media (width<=768px){.logs-header[data-v-1d31e3d2]{flex-direction:column;align-items:stretch;gap:8px;padding:8px 10px}.header-left[data-v-1d31e3d2]{justify-content:space-between}.title[data-v-1d31e3d2]{font-size:12px}.today-stats[data-v-1d31e3d2] .n-tag{padding:1px 4px;font-size:9px}.table-header[data-v-1d31e3d2],.table-row[data-v-1d31e3d2]{padding:4px 8px;font-size:10px}.table-row-content[data-v-1d31e3d2]{font-size:10px}.action-row[data-v-1d31e3d2]{padding:8px 10px;font-size:11px}.action-message[data-v-1d31e3d2]{font-size:11px}.action-time[data-v-1d31e3d2]{margin-right:12px;font-size:9px}.col-channel-claude[data-v-1d31e3d2],.col-channel-gemini[data-v-1d31e3d2],.col-channel-codex[data-v-1d31e3d2],.col-channel-opencode[data-v-1d31e3d2],.col-channel-omp[data-v-1d31e3d2],.col-channel-basic[data-v-1d31e3d2]{flex:0 0 60px}.col-channel[data-v-1d31e3d2] .n-tag{height:18px;padding:0 4px;font-size:9px}.col-token-claude[data-v-1d31e3d2],.col-token-gemini[data-v-1d31e3d2],.col-token-codex[data-v-1d31e3d2],.col-token-opencode[data-v-1d31e3d2],.col-token-omp[data-v-1d31e3d2],.col-token-basic[data-v-1d31e3d2]{flex:0 0 40px;font-size:9px}.col-time-claude[data-v-1d31e3d2],.col-time-gemini[data-v-1d31e3d2],.col-time-codex[data-v-1d31e3d2],.col-time-opencode[data-v-1d31e3d2],.col-time-omp[data-v-1d31e3d2],.col-time-basic[data-v-1d31e3d2]{flex:0 0 65px;min-width:65px;font-size:9px}.logs-footer[data-v-1d31e3d2]{padding:4px 10px;font-size:10px}}@media (width<=640px){.logs-header[data-v-1d31e3d2]{gap:6px;padding:6px 8px}.header-left[data-v-1d31e3d2]{flex-wrap:wrap;gap:4px}.title[data-v-1d31e3d2]{font-size:11px}.today-stats[data-v-1d31e3d2]{gap:4px}.today-stats[data-v-1d31e3d2] .n-tag{height:16px;padding:0 3px;font-size:8px}.table-header[data-v-1d31e3d2],.table-row[data-v-1d31e3d2]{padding:3px 6px;font-size:9px}.table-row-content[data-v-1d31e3d2]{font-size:9px}.action-row[data-v-1d31e3d2]{padding:6px 8px;font-size:10px}.action-message[data-v-1d31e3d2]{font-size:10px}.action-time[data-v-1d31e3d2]{margin-left:8px;margin-right:8px;font-size:8px}.col-channel-claude[data-v-1d31e3d2],.col-channel-gemini[data-v-1d31e3d2],.col-channel-codex[data-v-1d31e3d2],.col-channel-opencode[data-v-1d31e3d2],.col-channel-omp[data-v-1d31e3d2],.col-channel-basic[data-v-1d31e3d2]{flex:0 0 50px}.col-channel[data-v-1d31e3d2] .n-tag{height:16px;padding:0 3px;font-size:8px}.col-token-claude[data-v-1d31e3d2],.col-token-gemini[data-v-1d31e3d2],.col-token-codex[data-v-1d31e3d2],.col-token-opencode[data-v-1d31e3d2],.col-token-omp[data-v-1d31e3d2],.col-token-basic[data-v-1d31e3d2]{flex:0 0 32px;font-size:8px}.col-time-claude[data-v-1d31e3d2],.col-time-gemini[data-v-1d31e3d2],.col-time-codex[data-v-1d31e3d2],.col-time-opencode[data-v-1d31e3d2],.col-time-omp[data-v-1d31e3d2],.col-time-basic[data-v-1d31e3d2]{flex:0 0 55px;min-width:55px;font-size:8px}.logs-footer[data-v-1d31e3d2]{padding:3px 8px;font-size:9px}.status[data-v-1d31e3d2],.count[data-v-1d31e3d2]{font-size:9px}}@media (width<=480px){.logs-header[data-v-1d31e3d2]{padding:4px 6px}.title[data-v-1d31e3d2]{font-size:10px}.today-stats[data-v-1d31e3d2] .n-tag{height:14px;font-size:7px}.table-header[data-v-1d31e3d2],.table-row[data-v-1d31e3d2]{padding:2px 4px;font-size:8px}.action-row[data-v-1d31e3d2]{padding:4px 6px;font-size:9px}.action-row[data-v-1d31e3d2] .n-icon{margin-right:4px}.col-channel-claude[data-v-1d31e3d2],.col-channel-gemini[data-v-1d31e3d2],.col-channel-codex[data-v-1d31e3d2],.col-channel-opencode[data-v-1d31e3d2],.col-channel-omp[data-v-1d31e3d2],.col-channel-basic[data-v-1d31e3d2]{flex:0 0 45px}.col-token-claude[data-v-1d31e3d2],.col-token-gemini[data-v-1d31e3d2],.col-token-codex[data-v-1d31e3d2],.col-token-opencode[data-v-1d31e3d2],.col-token-omp[data-v-1d31e3d2],.col-token-basic[data-v-1d31e3d2]{flex:0 0 28px;font-size:7px}.col-time-claude[data-v-1d31e3d2],.col-time-gemini[data-v-1d31e3d2],.col-time-codex[data-v-1d31e3d2],.col-time-opencode[data-v-1d31e3d2],.col-time-omp[data-v-1d31e3d2],.col-time-basic[data-v-1d31e3d2]{flex:0 0 48px;min-width:48px;font-size:7px}.logs-footer[data-v-1d31e3d2]{padding:2px 6px;font-size:8px}}.right-panel[data-v-71b20d97]{background:var(--bg-primary);border-left:1px solid var(--border-primary);flex-direction:column;width:480px;min-width:480px;height:100%;display:flex}.toolbar-section[data-v-71b20d97]{background:var(--bg-secondary);border-bottom:1px solid var(--border-primary);flex-shrink:0;justify-content:space-between;align-items:center;padding:10px 14px;display:flex}.toolbar-left[data-v-71b20d97]{align-items:center;gap:8px;display:flex}.toolbar-label[data-v-71b20d97]{margin-right:2px;font-size:12px}.toolbar-right[data-v-71b20d97]{align-items:center;gap:2px;display:flex}.toolbar-btn[data-v-71b20d97]{--toolbar-button-text:var(--text-secondary);border-radius:4px;transition:transform .18s cubic-bezier(.4,0,.2,1);--n-text-color:var(--toolbar-button-text)!important;--n-text-color-hover:var(--toolbar-button-text)!important;--n-text-color-pressed:var(--toolbar-button-text)!important;--n-text-color-focus:var(--toolbar-button-text)!important;--n-color-hover:transparent!important;--n-color-pressed:transparent!important;--n-color-focus:transparent!important;color:var(--toolbar-button-text)!important;padding:5px!important}.toolbar-btn[data-v-71b20d97] .n-button__content,.toolbar-btn[data-v-71b20d97] .n-button__icon{color:var(--toolbar-button-text)!important}.toolbar-btn[data-v-71b20d97]:hover,.toolbar-btn[data-v-71b20d97]:focus,.toolbar-btn[data-v-71b20d97]:focus-visible,.toolbar-btn[data-v-71b20d97]:active{background:0 0;transform:translateY(-1px);color:var(--toolbar-button-text)!important}.toolbar-btn[data-v-71b20d97]:active{transform:translateY(0)}.toolbar-divider[data-v-71b20d97]{background:var(--border-primary);width:1px;height:16px;margin:0 4px}.oauth-banner[data-v-71b20d97]{color:#2080f0;background:#2080f014;border-bottom:1px solid #2080f033;flex-shrink:0;align-items:center;gap:6px;padding:6px 14px;font-size:12px;display:flex}.oauth-banner span[data-v-71b20d97]{flex:1}.channels-section[data-v-71b20d97]{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.channels-section.full-height[data-v-71b20d97]{flex:1}.panel-header[data-v-71b20d97]{background:var(--bg-secondary);border-bottom:1px solid var(--border-primary);flex-shrink:0;justify-content:space-between;align-items:center;padding:12px 14px;display:flex}.header-title[data-v-71b20d97]{align-items:baseline;gap:8px;min-width:0;display:flex}.header-title h3[data-v-71b20d97]{color:var(--text-primary);margin:0;font-size:13px;font-weight:600}.header-hint[data-v-71b20d97]{font-size:11px}.header-actions[data-v-71b20d97]{flex-shrink:0;align-items:center;gap:8px;display:flex}.channels-scroll-area[data-v-71b20d97]{flex:1;min-height:0;padding:12px;overflow-y:auto}.logs-section[data-v-71b20d97]{flex:0 0 340px;min-height:340px;max-height:340px;overflow:hidden}.logs-section.full-height[data-v-71b20d97]{flex:1;min-height:0;max-height:none}@media (width<=1024px){.right-panel[data-v-71b20d97]{width:420px;min-width:420px}.logs-section[data-v-71b20d97]{flex:0 0 300px;min-height:300px;max-height:300px}}@media (width<=768px){.right-panel[data-v-71b20d97]{width:100%;min-width:100%}.toolbar-section[data-v-71b20d97]{padding:8px 12px}.toolbar-label[data-v-71b20d97]{font-size:11px}.logs-section[data-v-71b20d97]{flex:0 0 260px;min-height:260px;max-height:260px}}@media (height<=600px){.right-panel[data-v-71b20d97]{overflow-y:auto}.channels-section[data-v-71b20d97]{flex:0 0 360px;min-height:360px}.logs-section[data-v-71b20d97]{flex:0 0 260px}}.layout[data-v-182ef64e]{background:var(--bg-primary);flex-direction:column;height:100vh;display:flex}.header[data-v-182ef64e]{background:var(--gradient-header);border-bottom:1px solid var(--border-primary);flex-shrink:0;justify-content:space-between;align-items:center;height:74px;padding:10px 22px;display:flex;box-shadow:inset 0 1px #ffffff57}.header-actions[data-v-182ef64e]{background:color-mix(in srgb, var(--bg-primary) 82%, transparent);border:1px solid var(--border-primary);border-radius:999px;align-items:center;gap:8px;padding:6px 10px;display:flex;box-shadow:0 14px 34px #0f171d14}.more-menu-trigger[data-v-182ef64e]{display:flex}.header-divider[data-v-182ef64e]{background:#95a7af59;width:1px;height:24px;margin:0 2px}.env-warning-btn[data-v-182ef64e]{cursor:pointer;background:#f59e0b1a;border-radius:4px;align-items:center;gap:4px;padding:4px 8px;transition:background .2s;display:flex}.env-warning-btn[data-v-182ef64e]:hover{background:#f59e0b2e}.env-warning-icon[data-v-182ef64e]{color:#f59e0b}.env-warning-count[data-v-182ef64e]{color:#f59e0b;font-size:11px;font-weight:600}.logo-section[data-v-182ef64e]{cursor:pointer;color:inherit;border-radius:18px;align-items:center;gap:14px;margin-left:-8px;padding:8px 14px;text-decoration:none;transition:background .2s;display:flex}.logo-section[data-v-182ef64e]:hover{background:var(--hover-bg)}.logo-wrapper[data-v-182ef64e]{background:linear-gradient(135deg,#18a0582e,#18a0580d);border-radius:14px;justify-content:center;align-items:center;width:40px;height:40px;display:flex;box-shadow:0 10px 24px #0f171d14}.logo-image[data-v-182ef64e]{object-fit:contain;width:22px;height:22px}.title-group[data-v-182ef64e]{flex-direction:column;gap:1px;display:flex}.title-main[data-v-182ef64e]{color:var(--brand-title-color);margin:0;font-size:18px;font-weight:700;line-height:1.2}.title-sub[data-v-182ef64e]{color:var(--text-tertiary);font-size:11px}.nav-tabs[data-v-182ef64e]{background:color-mix(in srgb, var(--bg-primary) 86%, transparent);border:1px solid var(--border-primary);border-radius:999px;align-items:center;gap:6px;margin-left:28px;padding:6px 8px;display:flex;box-shadow:0 14px 36px #0f171d14}.nav-tab[data-v-182ef64e]{cursor:pointer;border-radius:999px;align-items:center;gap:7px;padding:8px 14px;transition:background .2s;display:flex;position:relative}.nav-tab[data-v-182ef64e]:hover{background:var(--hover-bg)}.nav-tab.active[data-v-182ef64e]{background:var(--primary-color-soft)}.nav-tab.active[data-v-182ef64e]:after{content:"";border-radius:inherit;background:0 0;border:1px solid #18a05838;position:absolute;inset:0}.nav-icon[data-v-182ef64e]{color:var(--text-tertiary)}.nav-tab:hover .nav-icon[data-v-182ef64e],.nav-tab.active .nav-icon[data-v-182ef64e]{color:var(--primary-color,#18a058)}.nav-label[data-v-182ef64e]{color:var(--text-secondary);font-size:13px;font-weight:500}.nav-tab:hover .nav-label[data-v-182ef64e],.nav-tab.active .nav-label[data-v-182ef64e]{color:var(--primary-color,#18a058)}.nav-tab.active .nav-label[data-v-182ef64e]{font-weight:600}.main-container[data-v-182ef64e]{flex:1;height:calc(100vh - 74px);min-height:0;display:flex;overflow:hidden}.global-loading-overlay[data-v-182ef64e]{background:var(--bg-overlay);z-index:1000;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.left-content[data-v-182ef64e]{flex:1;min-width:0;height:100%;overflow:hidden}.favorites-button-wrapper[data-v-182ef64e]{position:relative}.favorites-badge[data-v-182ef64e]{color:#fff;pointer-events:none;background:#ff4d4f;border-radius:7px;justify-content:center;align-items:center;min-width:14px;height:14px;padding:0 3px;font-size:9px;font-weight:600;display:flex;position:absolute;top:-3px;right:-3px}.help-content[data-v-182ef64e]{max-height:70vh;overflow-y:auto}.help-section[data-v-182ef64e]{background:var(--bg-secondary);border:1px solid var(--border-primary);border-radius:8px;margin-bottom:20px;padding:16px}.help-section[data-v-182ef64e]:last-child{margin-bottom:0}.help-section h4[data-v-182ef64e]{color:var(--text-primary);border-bottom:1px solid var(--border-primary);margin:0 0 12px;padding-bottom:10px;font-size:15px;font-weight:600}.help-section p[data-v-182ef64e]{color:var(--text-secondary);margin:0 0 10px;font-size:13px;line-height:1.7}.help-section ul[data-v-182ef64e]{margin:8px 0 0;padding-left:20px}.help-section li[data-v-182ef64e]{color:var(--text-secondary);margin-bottom:4px;font-size:13px;line-height:1.8}.help-section li strong[data-v-182ef64e]{color:var(--primary-color,#18a058)}.command-list[data-v-182ef64e]{flex-direction:column;gap:8px;display:flex}.command-item[data-v-182ef64e]{background:var(--bg-primary);border:1px solid var(--border-primary);border-radius:6px;align-items:center;gap:12px;padding:10px 12px;display:flex}.command-item code[data-v-182ef64e]{min-width:140px;color:var(--primary-color,#18a058);background:#18a0581a;border-radius:4px;padding:4px 10px;font-family:monospace;font-size:12px;font-weight:600}.command-item span[data-v-182ef64e]{color:var(--text-secondary);font-size:13px}.help-section kbd[data-v-182ef64e]{color:var(--text-primary);background:var(--bg-primary);border:1px solid var(--border-primary);border-radius:3px;margin:0 2px;padding:2px 6px;font-family:monospace;font-size:11px;font-weight:600;display:inline-block}.link-list[data-v-182ef64e]{flex-wrap:wrap;gap:12px;display:flex}.link-list a[data-v-182ef64e]{color:var(--primary-color,#18a058);background:#18a05814;border-radius:4px;padding:6px 10px;font-size:13px;text-decoration:none}.link-list a[data-v-182ef64e]:hover{background:#18a05826}.panel-security-auth[data-v-182ef64e]{flex-direction:column;gap:12px;display:flex}.panel-security-error[data-v-182ef64e]{color:#d03050;font-size:12px}.panel-security-hint[data-v-182ef64e]{font-size:12px;line-height:1.6}.header-actions[data-v-182ef64e],.nav-tabs[data-v-182ef64e]{-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px)}[data-theme=dark] .header[data-v-182ef64e]{border-bottom-color:#2b424aeb;box-shadow:inset 0 -1px #ffffff08}[data-theme=dark] .header-actions[data-v-182ef64e],[data-theme=dark] .nav-tabs[data-v-182ef64e]{background:#0c151bdb;border-color:#364c56e6;box-shadow:0 14px 32px #00000042}[data-theme=dark] .logo-section[data-v-182ef64e]:hover{background:#ffffff0d}@media (width<=1024px){.header[data-v-182ef64e]{height:68px;padding:10px 16px}.main-container[data-v-182ef64e]{height:calc(100vh - 68px)}.nav-tabs[data-v-182ef64e]{margin-left:16px;padding:5px 6px}.title-sub[data-v-182ef64e]{display:none}}@media (width<=768px){.header[data-v-182ef64e]{height:60px;padding:8px 12px}.main-container[data-v-182ef64e]{height:calc(100vh - 60px)}.logo-wrapper[data-v-182ef64e]{width:28px;height:28px}.logo-image[data-v-182ef64e]{width:18px;height:18px}.title-main[data-v-182ef64e]{font-size:14px}.title-sub[data-v-182ef64e]{display:none}.nav-tabs[data-v-182ef64e]{gap:2px;margin-left:10px;padding:4px 5px}.nav-tab[data-v-182ef64e]{gap:4px;padding:6px 10px}.nav-label[data-v-182ef64e]{font-size:12px}.header-actions[data-v-182ef64e]{gap:2px}}@media (width<=640px){.header[data-v-182ef64e]{height:54px;padding:6px 8px}.main-container[data-v-182ef64e]{height:calc(100vh - 54px)}.title-group[data-v-182ef64e]{display:none}.nav-tabs[data-v-182ef64e]{flex:1;justify-content:center;margin-left:8px}.nav-tab[data-v-182ef64e]{flex-direction:column;gap:2px;padding:4px 6px}.nav-label[data-v-182ef64e]{font-size:10px}}.asset-panel{background:var(--bg-primary);flex-direction:column;flex:1;height:100%;min-height:0;display:flex}.asset-panel-header,.asset-drawer-toolbar{border-bottom:1px solid var(--border-primary);background:var(--bg-primary);justify-content:space-between;align-items:center;gap:16px;padding:14px 18px;display:flex}.asset-drawer-toolbar{background:var(--bg-secondary);justify-content:flex-end;padding:10px 12px}.asset-title-group,.asset-title-row,.asset-action-row,.asset-filter-bar,.asset-summary,.asset-meta,.asset-tags,.asset-card-actions{align-items:center;display:flex}.asset-title-group{gap:10px;min-width:0}.asset-heading{min-width:0}.asset-title-row{gap:8px;min-width:0}.asset-title{color:var(--text-primary);text-overflow:ellipsis;white-space:nowrap;font-size:15px;font-weight:650;line-height:1.25;overflow:hidden}.asset-subtitle{color:var(--text-tertiary);margin-top:3px;font-size:12px;line-height:1.35}.asset-platform-pill{border:1px solid var(--border-primary);background:var(--bg-secondary);color:var(--text-tertiary);border-radius:999px;flex-shrink:0;padding:2px 7px;font-size:11px;font-weight:500;line-height:1.4}.asset-action-row{flex-shrink:0;gap:6px}.asset-action-row .n-button{font-size:12px}.asset-summary{border-bottom:1px solid var(--border-primary);background:var(--bg-secondary);gap:8px;padding:10px 18px}.asset-panel.in-drawer .asset-summary{padding:10px 12px}.asset-summary-item{border:1px solid var(--border-primary);background:var(--bg-primary);border-radius:999px;align-items:baseline;gap:6px;min-width:0;padding:4px 8px;display:inline-flex}.asset-summary-label{color:var(--text-tertiary);font-size:11px}.asset-summary-value{color:var(--text-primary);font-variant-numeric:tabular-nums;font-size:12px;font-weight:650}.asset-filter-bar{border-bottom:1px solid var(--border-primary);background:var(--bg-primary);gap:10px;padding:12px 18px}.asset-panel.in-drawer .asset-filter-bar{padding:10px 12px}.asset-search{flex:1;min-width:160px}.asset-filter{flex-shrink:0;width:116px}.asset-panel-content{flex:1;min-height:0;padding:16px 18px;overflow-y:auto}.asset-panel.in-drawer .asset-panel-content{padding:12px}.asset-panel-content .n-spin-container{min-height:220px}.asset-empty{justify-content:center;align-items:center;min-height:220px;display:flex}.asset-list{flex-direction:column;gap:10px;display:flex}.asset-footer{border-top:1px solid var(--border-primary);background:var(--bg-secondary);color:var(--text-tertiary);align-items:center;gap:6px;padding:9px 18px;font-size:11px;display:flex}.asset-panel.in-drawer .asset-footer{padding:8px 12px}.asset-info-icon{color:var(--text-quaternary)}.asset-card{border:1px solid var(--border-primary);background:var(--bg-primary);cursor:pointer;border-radius:8px;justify-content:space-between;align-items:flex-start;gap:14px;min-height:96px;padding:14px;transition:border-color .16s ease-out,background-color .16s ease-out,box-shadow .16s ease-out;display:flex}.asset-card:hover{border-color:var(--border-secondary);background:var(--bg-secondary);box-shadow:var(--shadow-sm)}.asset-card.is-installed,.asset-card.is-managed,.asset-card.is-project{background:linear-gradient(0deg, oklch(62% .15 151/.045), oklch(62% .15 151/.045)), var(--bg-primary)}.asset-card.is-disabled,.asset-card.is-protected{background:var(--bg-secondary)}.asset-card-main{flex:1;min-width:0}.asset-card-top{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.asset-card-title-stack{min-width:0}.asset-card-title-row{align-items:center;gap:7px;min-width:0;display:flex}.asset-status-dot{background:var(--text-quaternary);border-radius:999px;flex:none;width:7px;height:7px}.asset-status-dot.active{background:var(--primary-color);box-shadow:0 0 0 3px oklch(62% .15 151/.1)}.asset-status-dot.warning{background:oklch(68% .14 78);box-shadow:0 0 0 3px oklch(68% .14 78/.1)}.asset-name{min-width:0;color:var(--text-primary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:650;line-height:1.35;overflow:hidden}.asset-name.mono{font-family:var(--font-mono,"SF Mono", "JetBrains Mono", "Cascadia Code", monospace)}.asset-tags{flex-wrap:wrap;gap:5px;margin-top:6px}.asset-description{color:var(--text-secondary);text-overflow:ellipsis;-webkit-line-clamp:2;-webkit-box-orient:vertical;margin-top:9px;font-size:12px;line-height:1.5;display:-webkit-box;overflow:hidden}.asset-meta{color:var(--text-tertiary);flex-wrap:wrap;gap:8px;margin-top:9px;font-size:11px}.asset-meta-item{text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:4px;min-width:0;max-width:100%;display:inline-flex;overflow:hidden}.asset-meta-item.mono{font-family:var(--font-mono,"SF Mono", "JetBrains Mono", "Cascadia Code", monospace)}.asset-link{color:var(--primary-color);flex-shrink:0;text-decoration:none}.asset-link:hover{text-decoration:underline}.asset-card-actions{flex-shrink:0;gap:5px}.asset-platform-strip{align-items:center;gap:5px;margin-left:6px;display:flex}.asset-platform-icon{border:1px solid var(--border-primary);background:var(--bg-secondary);width:24px;height:24px;color:var(--text-tertiary);cursor:pointer;border-radius:6px;justify-content:center;align-items:center;transition:border-color .16s ease-out,color .16s ease-out,background-color .16s ease-out;display:inline-flex}.asset-platform-icon:hover{border-color:var(--border-secondary);color:var(--text-secondary)}.asset-platform-icon.active{color:var(--primary-color);background:oklch(62% .15 151/.1);border-color:oklch(62% .15 151/.34)}.asset-detail-drawer-header{width:100%}.asset-detail-identity{align-items:center;gap:12px;min-width:0;display:flex}.asset-detail-icon{color:var(--primary-color);flex-shrink:0}.asset-detail-heading{flex-direction:column;gap:4px;min-width:0;display:flex}.asset-detail-name{color:var(--text-primary);text-overflow:ellipsis;white-space:nowrap;font-size:16px;font-weight:650;line-height:1.25;overflow:hidden}.asset-detail-name.mono{font-family:var(--font-mono,"SF Mono", "JetBrains Mono", "Cascadia Code", monospace)}.asset-detail-meta{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;display:flex}.asset-detail-subtle{min-width:0;color:var(--text-tertiary);align-items:center;gap:4px;font-size:12px;display:inline-flex}.asset-detail-body{flex-direction:column;height:100%;min-height:0;display:flex;overflow:hidden}.asset-detail-container{flex-direction:column;gap:22px;min-height:0;display:flex}.asset-detail-info-section{flex-direction:column;gap:10px;display:flex}.asset-detail-section-title{color:var(--text-primary);margin:0;font-size:14px;font-weight:650}.asset-detail-description{color:var(--text-secondary);margin:0;font-size:13px;line-height:1.65}.asset-detail-list{border:1px solid var(--border-primary);background:var(--bg-secondary);border-radius:8px;overflow:hidden}.asset-detail-row{border-bottom:1px solid var(--border-primary);grid-template-columns:104px minmax(0,1fr);gap:12px;padding:10px 12px;font-size:13px;display:grid}.asset-detail-row:last-child{border-bottom:0}.asset-detail-label{color:var(--text-tertiary)}.asset-detail-value{overflow-wrap:anywhere;min-width:0;color:var(--text-secondary)}.asset-detail-value.mono{font-family:var(--font-mono,"SF Mono", "JetBrains Mono", "Cascadia Code", monospace);font-size:12px}.asset-detail-box{border:1px solid var(--border-primary);background:var(--bg-secondary);min-width:0;color:var(--text-secondary);border-radius:8px;align-items:center;gap:8px;padding:12px;font-size:13px;display:flex}.asset-detail-box code,.asset-detail-code{font-family:var(--font-mono,"SF Mono", "JetBrains Mono", "Cascadia Code", monospace)}.asset-detail-box code{overflow-wrap:anywhere;color:var(--text-secondary);font-size:12px}.asset-detail-path-box{align-items:flex-start}.asset-detail-path-icon{flex:none;margin-top:1px}.asset-detail-path-text{flex:1;min-width:0;line-height:1.5}.asset-detail-path-copy{color:var(--text-tertiary);flex:none;margin:-4px -4px -4px 4px}.asset-detail-path-copy:hover{color:var(--text-primary)}.asset-detail-link{overflow-wrap:anywhere;min-width:0;color:var(--primary-color);text-decoration:none}.asset-detail-link:hover{text-decoration:underline}.asset-detail-actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.asset-detail-content{border:1px solid var(--border-primary);background:var(--bg-secondary);min-height:0;color:var(--text-secondary);overflow-wrap:anywhere;border-radius:8px;flex:1;padding:18px;font-size:13px;line-height:1.7;overflow:auto}.asset-detail-content h1,.asset-detail-content h2,.asset-detail-content h3,.asset-detail-content h4,.asset-detail-content h5,.asset-detail-content h6{color:var(--text-primary);margin:22px 0 10px;font-weight:650;line-height:1.35}.asset-detail-content h1:first-child,.asset-detail-content h2:first-child,.asset-detail-content h3:first-child{margin-top:0}.asset-detail-content h1{border-bottom:1px solid var(--border-primary);padding-bottom:10px;font-size:20px}.asset-detail-content h2{border-bottom:1px solid var(--border-primary);padding-bottom:7px;font-size:17px}.asset-detail-content h3{font-size:15px}.asset-detail-content h4{font-size:14px}.asset-detail-content h5,.asset-detail-content h6{color:var(--text-secondary);font-size:13px}.asset-detail-content p{margin:10px 0}.asset-detail-content strong{color:var(--text-primary);font-weight:650}.asset-detail-content code{border:1px solid var(--border-primary);background:var(--bg-tertiary);font-family:var(--font-mono,"SF Mono", "JetBrains Mono", "Cascadia Code", monospace);border-radius:5px;padding:2px 6px;font-size:12px}.asset-detail-content pre{border:1px solid var(--border-primary);background:var(--bg-primary);border-radius:8px;margin:14px 0;padding:14px;line-height:1.65;overflow-x:auto}.asset-detail-content pre code{color:var(--text-secondary);background:0 0;border:0;padding:0;font-size:12px}.asset-detail-content ul,.asset-detail-content ol{margin:10px 0;padding-left:24px}.asset-detail-content li{margin:5px 0}.asset-detail-content li>p{margin:6px 0}.asset-detail-content a{color:var(--primary-color);text-underline-offset:3px;text-decoration:underline;text-decoration-thickness:1px}.asset-detail-content a:hover{color:var(--primary-color-hover,var(--primary-color))}.asset-detail-content blockquote{border-left:1px solid var(--border-secondary);background:var(--bg-primary);color:var(--text-tertiary);border-radius:0 8px 8px 0;margin:14px 0;padding:10px 12px}.asset-detail-content blockquote>:first-child{margin-top:0}.asset-detail-content blockquote>:last-child{margin-bottom:0}.asset-detail-content table{border-collapse:collapse;width:max-content;max-width:100%;margin:14px 0;font-size:12px;display:block;overflow-x:auto}.asset-detail-content th,.asset-detail-content td{border:1px solid var(--border-primary);text-align:left;vertical-align:top;padding:8px 10px}.asset-detail-content th{background:var(--bg-tertiary);color:var(--text-primary);font-weight:650}.asset-detail-content img{border-radius:8px;max-width:100%;height:auto;margin:14px auto;display:block}.asset-detail-content hr{background:var(--border-primary);border:0;height:1px;margin:20px 0}.asset-detail-code{border:1px solid var(--border-primary);background:var(--bg-secondary);min-height:0;color:var(--text-secondary);white-space:pre-wrap;word-break:break-word;border-radius:8px;flex:1;margin:0;padding:16px;font-size:12px;line-height:1.65;overflow:auto}.asset-detail-loading,.asset-detail-empty,.asset-detail-error{color:var(--text-tertiary);text-align:center;flex-direction:column;justify-content:center;align-items:center;gap:16px;padding:64px 24px;display:flex}.asset-detail-empty-icon,.asset-detail-error-icon{color:var(--text-quaternary)}.asset-detail-error-icon{color:oklch(56% .18 26)}.asset-detail-tabs{flex:1;height:100%;min-height:0}.asset-detail-tabs .n-tabs-pane-wrapper{flex:1;min-height:0;padding-top:16px;overflow:hidden}.asset-detail-tabs .n-tab-pane{flex-direction:column;height:100%;min-height:0;display:flex;overflow:auto}@media (width<=720px){.asset-panel-header{flex-direction:column;align-items:flex-start}.asset-action-row,.asset-filter-bar,.asset-card-top{width:100%}.asset-action-row,.asset-summary{flex-wrap:wrap}.asset-filter-bar{flex-direction:column;align-items:stretch}.asset-filter{width:100%}.asset-card{flex-direction:column}.asset-card-actions{flex-wrap:wrap;align-self:flex-start}}:root{--bg-primary:#fff;--bg-secondary:#f4f7f8;--bg-tertiary:#edf3f1;--bg-elevated:#fff;--bg-overlay:#ffffffeb;--text-primary:#122025;--text-secondary:#58717d;--text-tertiary:#738995;--text-quaternary:#95a7af;--border-primary:#d8e1de;--border-secondary:#c3d0cb;--hover-bg:#18a05814;--active-bg:#18a0581f;--shadow-sm:0 8px 20px #0f171d0f;--shadow-md:0 18px 42px #0f171d1a;--shadow-lg:0 30px 70px #0f171d24;--font-mono:"SF Mono", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace;--gradient-header:linear-gradient(135deg, #f8fbfc 0%, #eef6f3 52%, #e9f7f1 100%);--gradient-bg:linear-gradient(180deg, #f4f7f8 0%, #eef4f2 100%);--gradient-card:linear-gradient(145deg, #fffffffa 0%, #f7faf8fa 100%);--primary-color:#18a058;--primary-color-soft:#18a0581f;--brand-title-color:#0d8f55}[data-theme=dark]{--bg-primary:#0a1116;--bg-secondary:#101920;--bg-tertiary:#16222a;--bg-elevated:#1a2831;--bg-overlay:#0a1116f0;--text-primary:#e7eef0;--text-secondary:#a8bbc0;--text-tertiary:#82969d;--text-quaternary:#667b83;--border-primary:#263740;--border-secondary:#334852;--hover-bg:#ffffff14;--active-bg:#ffffff1f;--shadow-sm:0 10px 24px #0000003d;--shadow-md:0 22px 52px #0000004d;--shadow-lg:0 36px 76px #0000005c;--font-mono:"SF Mono", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace;--gradient-header:linear-gradient(135deg, #0b1419 0%, #101b22 58%, #0d171d 100%);--gradient-bg:linear-gradient(180deg, #0a1116 0%, #0d151b 56%, #091016 100%);--gradient-card:linear-gradient(145deg, #121b22fa 0%, #0c151bfa 100%);--primary-color:#18a058;--primary-color-soft:#18a05829;--brand-title-color:#37d08c}body{background:var(--bg-primary);color:var(--text-primary);margin:0;padding:0;font-family:SF Pro Display,SF Pro Text,Segoe UI Variable Display,PingFang SC,Hiragino Sans GB,Microsoft YaHei,sans-serif;transition:background-color .3s,color .3s}#app{background:var(--bg-primary);min-height:100vh}*{scrollbar-width:thin;scrollbar-color:#18a05847 transparent}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background-color:#18a05847;border-radius:3px;transition:background-color .2s}::-webkit-scrollbar-thumb:hover{background-color:#18a05880}[data-theme=dark] ::-webkit-scrollbar-thumb{background-color:#18a05866}[data-theme=dark] ::-webkit-scrollbar-thumb:hover{background-color:#18a05899}
|