codexmate 0.0.55 → 0.0.56
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/README.md +2 -0
- package/README.vi.md +2 -0
- package/README.zh.md +2 -0
- package/cli/local-bridge.js +221 -22
- package/cli.js +117 -5
- package/package.json +1 -1
- package/web-ui/app.js +2 -0
- package/web-ui/modules/app.computed.session.mjs +210 -0
- package/web-ui/modules/app.methods.claude-config.mjs +128 -12
- package/web-ui/modules/app.methods.codex-config.mjs +294 -65
- package/web-ui/modules/app.methods.navigation.mjs +4 -1
- package/web-ui/modules/app.methods.providers.mjs +15 -1
- package/web-ui/modules/app.methods.runtime.mjs +7 -2
- package/web-ui/modules/app.methods.session-actions.mjs +24 -0
- package/web-ui/modules/app.methods.startup-claude.mjs +34 -1
- package/web-ui/modules/i18n/locales/en.mjs +41 -2
- package/web-ui/modules/i18n/locales/ja.mjs +41 -2
- package/web-ui/modules/i18n/locales/vi.mjs +41 -8
- package/web-ui/modules/i18n/locales/zh-tw.mjs +41 -2
- package/web-ui/modules/i18n/locales/zh.mjs +41 -2
- package/web-ui/modules/provider-default-names.mjs +25 -0
- package/web-ui/partials/index/modal-health-check.html +69 -5
- package/web-ui/partials/index/panel-config-codex.html +2 -2
- package/web-ui/partials/index/panel-sessions.html +97 -17
- package/web-ui/res/web-ui-render.precompiled.js +345 -93
- package/web-ui/session-helpers.mjs +4 -1
- package/web-ui/styles/responsive.css +98 -0
- package/web-ui/styles/sessions-preview.css +212 -2
- package/web-ui/styles/sessions-toolbar-trash.css +61 -18
- package/web-ui/styles/skills-list.css +122 -0
- package/web-ui/styles/titles-cards.css +52 -0
|
@@ -24,6 +24,28 @@ function getResponseMessage(response, fallback) {
|
|
|
24
24
|
return fallback;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
|
|
28
|
+
function normalizeProviderName(value) {
|
|
29
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function providerIssueDetail(issues) {
|
|
33
|
+
if (!Array.isArray(issues) || !issues.length) return '';
|
|
34
|
+
return issues
|
|
35
|
+
.map((issue) => issue && (issue.message || issue.code || ''))
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.join(';');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function remoteProviderDetail(remote) {
|
|
41
|
+
if (!remote || typeof remote !== 'object') return '';
|
|
42
|
+
const parts = [];
|
|
43
|
+
if (remote.endpoint) parts.push(remote.endpoint);
|
|
44
|
+
if (remote.statusCode) parts.push(String(remote.statusCode));
|
|
45
|
+
if (remote.message) parts.push(remote.message);
|
|
46
|
+
return parts.join(' · ');
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
export function createCodexConfigMethods(options = {}) {
|
|
28
50
|
const {
|
|
29
51
|
api,
|
|
@@ -303,6 +325,7 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
303
325
|
async runHealthCheck(options = {}) {
|
|
304
326
|
this.healthCheckLoading = true;
|
|
305
327
|
this.healthCheckResult = null;
|
|
328
|
+
this.healthCheckFailedProviderSelections = {};
|
|
306
329
|
this.healthCheckBatchTotal = 0;
|
|
307
330
|
this.healthCheckBatchDone = 0;
|
|
308
331
|
this.healthCheckBatchFailed = 0;
|
|
@@ -354,7 +377,13 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
354
377
|
}
|
|
355
378
|
|
|
356
379
|
if (this.configMode === 'claude') {
|
|
357
|
-
const
|
|
380
|
+
const configs = this.claudeConfigs || {};
|
|
381
|
+
const currentName = typeof this.currentClaudeConfig === 'string' && configs[this.currentClaudeConfig]
|
|
382
|
+
? this.currentClaudeConfig
|
|
383
|
+
: Object.keys(configs)[0];
|
|
384
|
+
const entries = currentName && configs[currentName]
|
|
385
|
+
? [[currentName, configs[currentName]]]
|
|
386
|
+
: [];
|
|
358
387
|
this.healthCheckBatchTotal = entries.length;
|
|
359
388
|
|
|
360
389
|
const speedTasks = entries.map(([name, config]) => this.runClaudeSpeedTest(name, config)
|
|
@@ -395,80 +424,44 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
395
424
|
speedTests: results
|
|
396
425
|
}
|
|
397
426
|
};
|
|
427
|
+
if (!silent) {
|
|
428
|
+
this.showHealthCheckModal = true;
|
|
429
|
+
}
|
|
398
430
|
if (ok && !silent) {
|
|
399
|
-
this.showMessage('
|
|
431
|
+
this.showMessage(this.t('toast.check.success'), 'success');
|
|
400
432
|
}
|
|
401
433
|
return;
|
|
402
434
|
}
|
|
403
435
|
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
? (this.providersList || [])
|
|
408
|
-
.map((provider) => typeof provider === 'string'
|
|
409
|
-
? provider.trim()
|
|
410
|
-
: String((provider && provider.name) || '').trim())
|
|
411
|
-
.filter(Boolean)
|
|
412
|
-
: [];
|
|
413
|
-
const currentProvider = String(this.currentProvider || '').trim();
|
|
414
|
-
const orderedProviders = currentProvider && providers.includes(currentProvider)
|
|
415
|
-
? [currentProvider, ...providers.filter((name) => name !== currentProvider)]
|
|
416
|
-
: providers;
|
|
417
|
-
this.healthCheckBatchTotal = orderedProviders.length;
|
|
418
|
-
|
|
419
|
-
const speedTasks = orderedProviders.map((provider) => this.runSpeedTest(provider, { silent: true, timeoutMs: speedTimeoutMs })
|
|
420
|
-
.then((result) => {
|
|
421
|
-
if (!result || result.ok !== true) {
|
|
422
|
-
this.healthCheckBatchFailed += 1;
|
|
423
|
-
}
|
|
424
|
-
return { name: provider, result };
|
|
425
|
-
})
|
|
426
|
-
.catch((err) => {
|
|
427
|
-
this.healthCheckBatchFailed += 1;
|
|
428
|
-
return {
|
|
429
|
-
name: provider,
|
|
430
|
-
result: { ok: false, error: err && err.message ? err.message : 'Speed test failed' }
|
|
431
|
-
};
|
|
432
|
-
})
|
|
433
|
-
.finally(() => {
|
|
434
|
-
this.healthCheckBatchDone += 1;
|
|
435
|
-
})
|
|
436
|
-
);
|
|
437
|
-
|
|
438
|
-
const configTask = api('config-health-check', { remote: this.configMode === 'codex' });
|
|
439
|
-
const [res, pairs] = await Promise.all([
|
|
440
|
-
configTask,
|
|
441
|
-
Promise.all(speedTasks)
|
|
436
|
+
const [configRes, providersRes] = await Promise.all([
|
|
437
|
+
api('config-health-check', { remote: true }),
|
|
438
|
+
api('providers-health', { remote: false })
|
|
442
439
|
]);
|
|
443
|
-
if (hasResponseError(
|
|
440
|
+
if (providersRes && typeof providersRes === 'object' && !hasResponseError(providersRes)) {
|
|
441
|
+
this.providersHealthResult = providersRes;
|
|
442
|
+
}
|
|
443
|
+
if (hasResponseError(configRes)) {
|
|
444
444
|
this.healthCheckResult = null;
|
|
445
445
|
if (!silent) {
|
|
446
|
-
this.showMessage(getResponseMessage(
|
|
447
|
-
}
|
|
448
|
-
} else if (res && typeof res === 'object') {
|
|
449
|
-
const issues = Array.isArray(res.issues) ? [...res.issues] : [];
|
|
450
|
-
let remote = res.remote || null;
|
|
451
|
-
if (shouldRunSpeedTests) {
|
|
452
|
-
const results = {};
|
|
453
|
-
for (const pair of pairs) {
|
|
454
|
-
results[pair.name] = pair.result || null;
|
|
455
|
-
const issue = this.buildSpeedTestIssue(pair.name, pair.result);
|
|
456
|
-
if (issue) issues.push(issue);
|
|
457
|
-
}
|
|
458
|
-
remote = remote && typeof remote === 'object'
|
|
459
|
-
? { ...remote, speedTests: results }
|
|
460
|
-
: { type: 'speed-test', speedTests: results };
|
|
446
|
+
this.showMessage(getResponseMessage(configRes, this.t('toast.check.fail')), 'error');
|
|
461
447
|
}
|
|
462
|
-
|
|
463
|
-
const
|
|
448
|
+
} else if (configRes && typeof configRes === 'object') {
|
|
449
|
+
const issues = Array.isArray(configRes.issues) ? [...configRes.issues] : [];
|
|
450
|
+
const ok = typeof configRes.ok === 'boolean' ? configRes.ok : issues.length === 0;
|
|
464
451
|
this.healthCheckResult = {
|
|
465
|
-
...
|
|
452
|
+
...configRes,
|
|
466
453
|
ok,
|
|
467
454
|
issues,
|
|
468
|
-
remote
|
|
455
|
+
remote: configRes.remote || null
|
|
469
456
|
};
|
|
470
|
-
|
|
471
|
-
|
|
457
|
+
this.healthCheckBatchTotal = 1;
|
|
458
|
+
this.healthCheckBatchDone = 1;
|
|
459
|
+
this.healthCheckBatchFailed = ok ? 0 : 1;
|
|
460
|
+
if (!silent) {
|
|
461
|
+
this.showHealthCheckModal = true;
|
|
462
|
+
}
|
|
463
|
+
if (!silent) {
|
|
464
|
+
this.showMessage(this.t(ok ? 'toast.check.success' : 'toast.check.fail'), ok ? 'success' : 'error');
|
|
472
465
|
}
|
|
473
466
|
} else {
|
|
474
467
|
this.healthCheckResult = null;
|
|
@@ -479,16 +472,252 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
479
472
|
} catch (e) {
|
|
480
473
|
this.healthCheckResult = null;
|
|
481
474
|
if (!(options && options.silent)) {
|
|
482
|
-
this.showMessage('
|
|
475
|
+
this.showMessage(this.t('toast.check.fail'), 'error');
|
|
483
476
|
}
|
|
484
477
|
} finally {
|
|
485
478
|
this.healthCheckBatchTotal = this.healthCheckBatchTotal || 0;
|
|
486
479
|
this.healthCheckBatchDone = Math.min(this.healthCheckBatchDone || 0, this.healthCheckBatchTotal || 0);
|
|
487
480
|
this.healthCheckLoading = false;
|
|
488
|
-
|
|
489
|
-
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
getHealthCheckFailedProviderItems() {
|
|
485
|
+
const result = this.healthCheckResult && typeof this.healthCheckResult === 'object'
|
|
486
|
+
? this.healthCheckResult
|
|
487
|
+
: null;
|
|
488
|
+
if (!result) return [];
|
|
489
|
+
const remote = result.remote && typeof result.remote === 'object' ? result.remote : null;
|
|
490
|
+
const items = new Map();
|
|
491
|
+
const currentMode = this.configMode === 'claude' ? 'claude' : 'codex';
|
|
492
|
+
const pushItem = (name, detail, status = 'red', mode = currentMode) => {
|
|
493
|
+
const providerName = normalizeProviderName(name);
|
|
494
|
+
if (!providerName) return;
|
|
495
|
+
const key = `${mode}:${providerName}`;
|
|
496
|
+
const provider = (this.providersList || []).find((item) => item && item.name === providerName) || providerName;
|
|
497
|
+
const baseDeletable = mode === 'claude'
|
|
498
|
+
? !!(this.claudeConfigs && this.claudeConfigs[providerName])
|
|
499
|
+
: (typeof this.shouldShowProviderDelete === 'function' ? this.shouldShowProviderDelete(provider) : true);
|
|
500
|
+
const writeAllowed = typeof this.isToolConfigWriteAllowed === 'function'
|
|
501
|
+
? this.isToolConfigWriteAllowed(mode)
|
|
502
|
+
: true;
|
|
503
|
+
const deletable = baseDeletable && writeAllowed;
|
|
504
|
+
const blockedReason = !baseDeletable ? 'reserved' : (!writeAllowed ? 'readonly' : '');
|
|
505
|
+
const previous = items.get(key) || {};
|
|
506
|
+
items.set(key, {
|
|
507
|
+
key,
|
|
508
|
+
mode,
|
|
509
|
+
name: providerName,
|
|
510
|
+
status: previous.status || status || 'red',
|
|
511
|
+
detail: detail || previous.detail || '',
|
|
512
|
+
deletable,
|
|
513
|
+
blockedReason: previous.blockedReason || blockedReason,
|
|
514
|
+
selected: !!(this.healthCheckFailedProviderSelections && this.healthCheckFailedProviderSelections[key])
|
|
515
|
+
});
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
if (remote && remote.type === 'providers-health' && Array.isArray(remote.providers)) {
|
|
519
|
+
for (const provider of remote.providers) {
|
|
520
|
+
if (!provider || provider.status === 'green') continue;
|
|
521
|
+
pushItem(provider.provider, providerIssueDetail(provider.issues) || remoteProviderDetail(provider.remote), provider.status || 'red', 'codex');
|
|
490
522
|
}
|
|
491
523
|
}
|
|
524
|
+
|
|
525
|
+
if (remote && remote.type === 'remote-health-check' && (result.ok === false || remote.ok === false)) {
|
|
526
|
+
pushItem(remote.provider, remoteProviderDetail(remote), 'red', 'codex');
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (remote && remote.speedTests && typeof remote.speedTests === 'object') {
|
|
530
|
+
for (const [name, speedResult] of Object.entries(remote.speedTests)) {
|
|
531
|
+
if (speedResult && speedResult.ok === true) continue;
|
|
532
|
+
pushItem(name, speedResult && speedResult.error ? speedResult.error : this.t('config.health.fail'), 'red', this.configMode === 'claude' ? 'claude' : 'codex');
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (Array.isArray(result.issues)) {
|
|
537
|
+
for (const issue of result.issues) {
|
|
538
|
+
if (!issue || typeof issue !== 'object') continue;
|
|
539
|
+
const name = normalizeProviderName(issue.provider || issue.providerName || issue.name);
|
|
540
|
+
if (name) pushItem(name, issue.message || issue.code || '', 'red', currentMode);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return Array.from(items.values());
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
getSelectableHealthCheckFailedProviderItems() {
|
|
548
|
+
return this.getHealthCheckFailedProviderItems().filter((item) => item.deletable);
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
hasHealthCheckFailedProviderSelection() {
|
|
552
|
+
return this.getSelectableHealthCheckFailedProviderItems().some((item) => item.selected);
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
areAllHealthCheckFailedProvidersSelected() {
|
|
556
|
+
const selectable = this.getSelectableHealthCheckFailedProviderItems();
|
|
557
|
+
return selectable.length > 0 && selectable.every((item) => item.selected);
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
setAllHealthCheckFailedProviderSelections(checked) {
|
|
561
|
+
const selectable = this.getSelectableHealthCheckFailedProviderItems();
|
|
562
|
+
const next = { ...(this.healthCheckFailedProviderSelections || {}) };
|
|
563
|
+
for (const item of selectable) {
|
|
564
|
+
next[item.key] = !!checked;
|
|
565
|
+
}
|
|
566
|
+
this.healthCheckFailedProviderSelections = next;
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
toggleHealthCheckFailedProviderSelection(item, checked) {
|
|
570
|
+
if (!item || !item.key || !item.deletable) return;
|
|
571
|
+
this.healthCheckFailedProviderSelections = {
|
|
572
|
+
...(this.healthCheckFailedProviderSelections || {}),
|
|
573
|
+
[item.key]: !!checked
|
|
574
|
+
};
|
|
575
|
+
},
|
|
576
|
+
|
|
577
|
+
async deleteSelectedHealthCheckFailedProviders() {
|
|
578
|
+
const selectedItems = this.getHealthCheckFailedProviderItems()
|
|
579
|
+
.filter((item) => item.selected && item.deletable);
|
|
580
|
+
if (!selectedItems.length) {
|
|
581
|
+
this.showMessage(this.t('toast.health.noFailedProviderSelection'), 'info');
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
this.healthCheckFailedProviderDeleting = true;
|
|
585
|
+
const deleted = [];
|
|
586
|
+
try {
|
|
587
|
+
const claudeItems = selectedItems.filter((item) => item.mode === 'claude');
|
|
588
|
+
const codexItems = selectedItems.filter((item) => item.mode !== 'claude');
|
|
589
|
+
|
|
590
|
+
if (claudeItems.length) {
|
|
591
|
+
const configs = this.claudeConfigs && typeof this.claudeConfigs === 'object' ? this.claudeConfigs : {};
|
|
592
|
+
const names = claudeItems.map((item) => item.name).filter((name) => configs[name]);
|
|
593
|
+
if (names.length) {
|
|
594
|
+
const deletedCurrentClaude = names.includes(this.currentClaudeConfig);
|
|
595
|
+
const remainingNames = Object.keys(configs).filter((name) => !names.includes(name));
|
|
596
|
+
if (remainingNames.length === 0) {
|
|
597
|
+
throw new Error(this.t('toast.claude.keepOne'));
|
|
598
|
+
}
|
|
599
|
+
for (const name of names) {
|
|
600
|
+
const config = configs[name];
|
|
601
|
+
if (config && (config.providerCacheRef || config.source === 'provider-cache')) {
|
|
602
|
+
if (typeof this.deleteClaudeProviderCacheRef === 'function') {
|
|
603
|
+
const ok = await this.deleteClaudeProviderCacheRef(config);
|
|
604
|
+
if (!ok) throw new Error(this.t('toast.delete.fail'));
|
|
605
|
+
} else {
|
|
606
|
+
const cacheName = typeof config.providerCacheRef === 'string' && config.providerCacheRef.trim()
|
|
607
|
+
? config.providerCacheRef.trim()
|
|
608
|
+
: name;
|
|
609
|
+
const res = await api('delete-provider-cache-record', { name: cacheName, group: 'claude' });
|
|
610
|
+
if (res && res.error) throw new Error(res.error);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (typeof this.rememberDeletedClaudeSettingsImport === 'function') {
|
|
614
|
+
this.rememberDeletedClaudeSettingsImport(config);
|
|
615
|
+
}
|
|
616
|
+
delete configs[name];
|
|
617
|
+
deleted.push(name);
|
|
618
|
+
}
|
|
619
|
+
if (deletedCurrentClaude) {
|
|
620
|
+
this.currentClaudeConfig = typeof this.selectClaudeFallbackConfigName === 'function'
|
|
621
|
+
? this.selectClaudeFallbackConfigName(names)
|
|
622
|
+
: remainingNames[0];
|
|
623
|
+
}
|
|
624
|
+
if (typeof this.saveClaudeConfigs === 'function') {
|
|
625
|
+
this.saveClaudeConfigs();
|
|
626
|
+
}
|
|
627
|
+
if (deletedCurrentClaude && typeof this.applyCurrentClaudeConfigSilently === 'function') {
|
|
628
|
+
await this.applyCurrentClaudeConfigSilently();
|
|
629
|
+
} else if (typeof this.refreshClaudeModelContext === 'function') {
|
|
630
|
+
this.refreshClaudeModelContext();
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
for (const item of codexItems) {
|
|
636
|
+
const res = await api('delete-provider', { name: item.name });
|
|
637
|
+
if (res && res.error) {
|
|
638
|
+
throw new Error(res.error);
|
|
639
|
+
}
|
|
640
|
+
this.providersList = (this.providersList || []).filter((provider) => provider && provider.name !== item.name);
|
|
641
|
+
if (this.currentModels && this.currentModels[item.name]) {
|
|
642
|
+
delete this.currentModels[item.name];
|
|
643
|
+
}
|
|
644
|
+
if (res && res.switched && res.provider) {
|
|
645
|
+
this.currentProvider = res.provider;
|
|
646
|
+
if (res.model) this.currentModel = res.model;
|
|
647
|
+
this.providersList = (this.providersList || []).map((provider) => ({
|
|
648
|
+
...provider,
|
|
649
|
+
current: provider.name === res.provider
|
|
650
|
+
}));
|
|
651
|
+
}
|
|
652
|
+
deleted.push(item.name);
|
|
653
|
+
}
|
|
654
|
+
const deletedSet = new Set(deleted);
|
|
655
|
+
this.healthCheckFailedProviderSelections = {};
|
|
656
|
+
const result = this.healthCheckResult;
|
|
657
|
+
const remote = result && result.remote;
|
|
658
|
+
const remainingIssues = Array.isArray(result && result.issues)
|
|
659
|
+
? result.issues.filter((issue) => {
|
|
660
|
+
const name = normalizeProviderName(issue && (issue.provider || issue.providerName || issue.name));
|
|
661
|
+
return !name || !deletedSet.has(name);
|
|
662
|
+
})
|
|
663
|
+
: [];
|
|
664
|
+
if (result && typeof result === 'object') {
|
|
665
|
+
if (remote && remote.type === 'providers-health' && Array.isArray(remote.providers)) {
|
|
666
|
+
const providers = remote.providers.filter((provider) => !deletedSet.has(provider && provider.provider));
|
|
667
|
+
const summary = {
|
|
668
|
+
total: providers.length,
|
|
669
|
+
green: providers.filter((p) => p && p.status === 'green').length,
|
|
670
|
+
yellow: providers.filter((p) => p && p.status === 'yellow').length,
|
|
671
|
+
red: providers.filter((p) => p && p.status === 'red').length
|
|
672
|
+
};
|
|
673
|
+
this.healthCheckResult = {
|
|
674
|
+
...result,
|
|
675
|
+
ok: remainingIssues.length === 0 && summary.yellow === 0 && summary.red === 0,
|
|
676
|
+
remote: {
|
|
677
|
+
...remote,
|
|
678
|
+
providers,
|
|
679
|
+
summary
|
|
680
|
+
},
|
|
681
|
+
issues: remainingIssues
|
|
682
|
+
};
|
|
683
|
+
this.healthCheckBatchTotal = summary.total;
|
|
684
|
+
this.healthCheckBatchDone = summary.total;
|
|
685
|
+
this.healthCheckBatchFailed = summary.yellow + summary.red;
|
|
686
|
+
} else if (remote && remote.type === 'speed-test' && remote.speedTests && typeof remote.speedTests === 'object') {
|
|
687
|
+
const speedTests = Object.fromEntries(
|
|
688
|
+
Object.entries(remote.speedTests).filter(([name]) => !deletedSet.has(normalizeProviderName(name)))
|
|
689
|
+
);
|
|
690
|
+
const total = Object.keys(speedTests).length;
|
|
691
|
+
const failed = Object.values(speedTests).filter((entry) => !entry || entry.ok !== true).length;
|
|
692
|
+
this.healthCheckResult = {
|
|
693
|
+
...result,
|
|
694
|
+
ok: remainingIssues.length === 0 && failed === 0,
|
|
695
|
+
remote: {
|
|
696
|
+
...remote,
|
|
697
|
+
speedTests
|
|
698
|
+
},
|
|
699
|
+
issues: remainingIssues
|
|
700
|
+
};
|
|
701
|
+
this.healthCheckBatchTotal = total;
|
|
702
|
+
this.healthCheckBatchDone = total;
|
|
703
|
+
this.healthCheckBatchFailed = failed;
|
|
704
|
+
} else {
|
|
705
|
+
const removedRemote = remote && remote.type === 'remote-health-check' && deletedSet.has(remote.provider);
|
|
706
|
+
this.healthCheckResult = {
|
|
707
|
+
...result,
|
|
708
|
+
ok: remainingIssues.length === 0,
|
|
709
|
+
remote: removedRemote ? null : remote,
|
|
710
|
+
issues: remainingIssues
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
this.showMessage(this.t('toast.health.deleteFailedProvidersDone', { count: deleted.length }), 'success');
|
|
715
|
+
this.showHealthCheckModal = false;
|
|
716
|
+
} catch (e) {
|
|
717
|
+
this.showMessage(e && e.message ? e.message : this.t('toast.delete.fail'), 'error');
|
|
718
|
+
} finally {
|
|
719
|
+
this.healthCheckFailedProviderDeleting = false;
|
|
720
|
+
}
|
|
492
721
|
},
|
|
493
722
|
|
|
494
723
|
async runProvidersHealthCheck(options = {}) {
|
|
@@ -724,7 +724,10 @@
|
|
|
724
724
|
? this.activeSessionMessages.length
|
|
725
725
|
: 0;
|
|
726
726
|
if (total <= 0) return;
|
|
727
|
-
|
|
727
|
+
const initialBatchSize = Number.isFinite(this.sessionPreviewInitialBatchSize)
|
|
728
|
+
? Math.max(1, Math.floor(this.sessionPreviewInitialBatchSize))
|
|
729
|
+
: 12;
|
|
730
|
+
this.sessionPreviewVisibleCount = Math.min(total, initialBatchSize);
|
|
728
731
|
this.invalidateSessionTimelineMeasurementCache();
|
|
729
732
|
},
|
|
730
733
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { nextCodexProviderName } from './provider-default-names.mjs';
|
|
2
|
+
|
|
1
3
|
const PROVIDER_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
2
4
|
const RESERVED_PROXY_PROVIDER_NAME = 'codexmate-proxy';
|
|
3
5
|
const RESERVED_LOCAL_PROVIDER_NAME = 'local';
|
|
@@ -327,6 +329,18 @@ export function createProvidersMethods(options = {}) {
|
|
|
327
329
|
this.showAddModal = true;
|
|
328
330
|
},
|
|
329
331
|
|
|
332
|
+
openAddProviderModal() {
|
|
333
|
+
this.newProvider = {
|
|
334
|
+
name: nextCodexProviderName(this.providersList),
|
|
335
|
+
url: '',
|
|
336
|
+
key: '',
|
|
337
|
+
model: '',
|
|
338
|
+
useTransform: false
|
|
339
|
+
};
|
|
340
|
+
this.showAddProviderKey = false;
|
|
341
|
+
this.showAddModal = true;
|
|
342
|
+
},
|
|
343
|
+
|
|
330
344
|
async openEditModal(provider) {
|
|
331
345
|
const requestId = Symbol('openEditModal');
|
|
332
346
|
this._openEditModalRequestId = requestId;
|
|
@@ -536,7 +550,7 @@ export function createProvidersMethods(options = {}) {
|
|
|
536
550
|
closeAddModal() {
|
|
537
551
|
this.showAddModal = false;
|
|
538
552
|
this.showAddProviderKey = false;
|
|
539
|
-
this.newProvider = { name:
|
|
553
|
+
this.newProvider = { name: nextCodexProviderName(this.providersList), url: '', key: '', model: '', useTransform: false };
|
|
540
554
|
},
|
|
541
555
|
|
|
542
556
|
toggleAddProviderKey() {
|
|
@@ -154,6 +154,10 @@ export function createRuntimeMethods(options = {}) {
|
|
|
154
154
|
const baseUrl = config && typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
|
|
155
155
|
const apiKey = config && typeof config.apiKey === 'string' ? config.apiKey.trim() : '';
|
|
156
156
|
const model = config && typeof config.model === 'string' ? config.model.trim() : '';
|
|
157
|
+
const targetApiRaw = config && typeof config.targetApi === 'string' ? config.targetApi.trim().toLowerCase() : '';
|
|
158
|
+
const targetApi = targetApiRaw === 'ollama'
|
|
159
|
+
? 'ollama'
|
|
160
|
+
: (targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions' ? 'chat_completions' : 'responses');
|
|
157
161
|
this.claudeSpeedLoading[name] = true;
|
|
158
162
|
try {
|
|
159
163
|
if (!baseUrl) {
|
|
@@ -161,7 +165,7 @@ export function createRuntimeMethods(options = {}) {
|
|
|
161
165
|
this.claudeSpeedResults[name] = res;
|
|
162
166
|
return res;
|
|
163
167
|
}
|
|
164
|
-
if (!apiKey) {
|
|
168
|
+
if (!apiKey && targetApi !== 'ollama') {
|
|
165
169
|
const res = { ok: false, error: 'Missing API key' };
|
|
166
170
|
this.claudeSpeedResults[name] = res;
|
|
167
171
|
return res;
|
|
@@ -175,7 +179,8 @@ export function createRuntimeMethods(options = {}) {
|
|
|
175
179
|
kind: 'claude',
|
|
176
180
|
url: baseUrl,
|
|
177
181
|
apiKey,
|
|
178
|
-
model
|
|
182
|
+
model,
|
|
183
|
+
targetApi
|
|
179
184
|
});
|
|
180
185
|
if (res.error) {
|
|
181
186
|
this.claudeSpeedResults[name] = { ok: false, error: res.error };
|
|
@@ -165,6 +165,30 @@ export function createSessionActionMethods(options = {}) {
|
|
|
165
165
|
this.showMessage(this.t('toast.copy.fail'), 'error');
|
|
166
166
|
},
|
|
167
167
|
|
|
168
|
+
async copySessionWorkspaceBrief() {
|
|
169
|
+
const summary = this.activeSessionWorkspaceSummary;
|
|
170
|
+
const text = summary && typeof summary.briefText === 'string'
|
|
171
|
+
? summary.briefText.trim()
|
|
172
|
+
: '';
|
|
173
|
+
if (!text) {
|
|
174
|
+
this.showMessage(this.t('sessions.workspace.copy.empty'), 'error');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const ok = this.fallbackCopyText(text);
|
|
178
|
+
if (ok) {
|
|
179
|
+
this.showMessage(this.t('sessions.workspace.copy.success'), 'success');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
184
|
+
await navigator.clipboard.writeText(text);
|
|
185
|
+
this.showMessage(this.t('sessions.workspace.copy.success'), 'success');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
} catch (_) {}
|
|
189
|
+
this.showMessage(this.t('toast.copy.fail'), 'error');
|
|
190
|
+
},
|
|
191
|
+
|
|
168
192
|
getSessionExportKey(session) {
|
|
169
193
|
return `${session.source || 'unknown'}:${session.sessionId || ''}:${session.filePath || ''}`;
|
|
170
194
|
},
|
|
@@ -8,6 +8,31 @@ import {
|
|
|
8
8
|
normalizeClaudeSettingsEnv,
|
|
9
9
|
normalizeClaudeValue
|
|
10
10
|
} from '../logic.mjs';
|
|
11
|
+
import { nextClaudeConfigName } from './provider-default-names.mjs';
|
|
12
|
+
|
|
13
|
+
const DELETED_CLAUDE_SETTINGS_IMPORTS_STORAGE_KEY = 'deletedClaudeSettingsImports';
|
|
14
|
+
|
|
15
|
+
function normalizeDeletedClaudeImportUrl(value) {
|
|
16
|
+
return typeof value === 'string' ? value.trim().replace(/\/+$/g, '') : '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function shouldSuppressDeletedClaudeSettingsImport(env = {}) {
|
|
20
|
+
const normalized = normalizeClaudeSettingsEnv(env);
|
|
21
|
+
const baseUrl = normalizeDeletedClaudeImportUrl(normalized.baseUrl);
|
|
22
|
+
const model = normalizeClaudeValue(normalized.model);
|
|
23
|
+
if (!baseUrl || !model) return false;
|
|
24
|
+
try {
|
|
25
|
+
const raw = localStorage.getItem(DELETED_CLAUDE_SETTINGS_IMPORTS_STORAGE_KEY);
|
|
26
|
+
const parsed = raw ? JSON.parse(raw) : [];
|
|
27
|
+
const entries = Array.isArray(parsed) ? parsed : [];
|
|
28
|
+
return entries.some((entry) => entry
|
|
29
|
+
&& typeof entry === 'object'
|
|
30
|
+
&& normalizeDeletedClaudeImportUrl(entry.baseUrl) === baseUrl
|
|
31
|
+
&& normalizeClaudeValue(entry.model) === model);
|
|
32
|
+
} catch (_) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
11
36
|
|
|
12
37
|
export function createStartupClaudeMethods(options = {}) {
|
|
13
38
|
const {
|
|
@@ -250,7 +275,7 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
250
275
|
},
|
|
251
276
|
|
|
252
277
|
shouldSuppressClaudeSettingsImport(env) {
|
|
253
|
-
return isLikelyBuiltinClaudeProxySettingsEnv(env);
|
|
278
|
+
return isLikelyBuiltinClaudeProxySettingsEnv(env) || shouldSuppressDeletedClaudeSettingsImport(env);
|
|
254
279
|
},
|
|
255
280
|
|
|
256
281
|
findDuplicateClaudeConfigName(config) {
|
|
@@ -573,6 +598,14 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
573
598
|
},
|
|
574
599
|
|
|
575
600
|
openClaudeConfigModal() {
|
|
601
|
+
this.newClaudeConfig = {
|
|
602
|
+
name: nextClaudeConfigName(this.claudeConfigs),
|
|
603
|
+
apiKey: '',
|
|
604
|
+
externalCredentialType: '',
|
|
605
|
+
baseUrl: '',
|
|
606
|
+
model: '',
|
|
607
|
+
targetApi: 'responses'
|
|
608
|
+
};
|
|
576
609
|
this.showAddClaudeConfigKey = false;
|
|
577
610
|
this.showClaudeConfigModal = true;
|
|
578
611
|
},
|
|
@@ -432,6 +432,8 @@ const en = Object.freeze({
|
|
|
432
432
|
'toast.apply.fail': 'Failed to apply configuration',
|
|
433
433
|
'toast.check.success': 'Check passed',
|
|
434
434
|
'toast.check.fail': 'Check failed',
|
|
435
|
+
'toast.health.noFailedProviderSelection': 'Select at least one failed provider first',
|
|
436
|
+
'toast.health.deleteFailedProvidersDone': 'Deleted {count} failed provider(s)',
|
|
435
437
|
'toast.noChanges': 'No changes detected',
|
|
436
438
|
'toast.template.loadFail': 'Failed to load template',
|
|
437
439
|
'toast.template.empty': 'Template cannot be empty',
|
|
@@ -660,6 +662,32 @@ const en = Object.freeze({
|
|
|
660
662
|
'sessions.preview.rerender': 'Re-render',
|
|
661
663
|
'sessions.preview.preparing': 'Preparing session content...',
|
|
662
664
|
'sessions.preview.clipped': 'Showing the latest {count} messages only.',
|
|
665
|
+
'sessions.workspace.kicker': 'Work memory',
|
|
666
|
+
'sessions.workspace.title': 'Session reuse summary',
|
|
667
|
+
'sessions.workspace.subtitle': 'Extracted signals, commands, files, links, and risks from {count} loaded messages.',
|
|
668
|
+
'sessions.workspace.copy': 'Copy brief',
|
|
669
|
+
'sessions.workspace.copy.title': 'Session workspace brief',
|
|
670
|
+
'sessions.workspace.copy.source': 'Source',
|
|
671
|
+
'sessions.workspace.copy.path': 'Path',
|
|
672
|
+
'sessions.workspace.copy.empty': 'No session brief to copy yet',
|
|
673
|
+
'sessions.workspace.copy.success': 'Workspace brief copied',
|
|
674
|
+
'sessions.workspace.metric.messages': 'Messages',
|
|
675
|
+
'sessions.workspace.metric.user': 'User',
|
|
676
|
+
'sessions.workspace.metric.assistant': 'Assistant',
|
|
677
|
+
'sessions.workspace.metric.commands': 'Commands',
|
|
678
|
+
'sessions.workspace.metric.artifacts': 'Artifacts',
|
|
679
|
+
'sessions.workspace.metric.risks': 'Risks / todos',
|
|
680
|
+
'sessions.workspace.signals': 'Task signals',
|
|
681
|
+
'sessions.workspace.commands': 'Reusable commands',
|
|
682
|
+
'sessions.workspace.files': 'Related files',
|
|
683
|
+
'sessions.workspace.links': 'Related links',
|
|
684
|
+
'sessions.workspace.risks': 'Risks and todos',
|
|
685
|
+
'sessions.workspace.nextSteps': 'Next steps',
|
|
686
|
+
'sessions.workspace.empty': 'No strong signals yet. Continue with the raw messages below.',
|
|
687
|
+
'sessions.workspace.noneCommands': 'No command signals',
|
|
688
|
+
'sessions.workspace.noneFiles': 'No file signals',
|
|
689
|
+
'sessions.workspace.noneLinks': 'No link signals',
|
|
690
|
+
'sessions.workspace.noneRisks': 'No risks or todos detected',
|
|
663
691
|
'sessions.preview.shownCount': 'Shown {shown} / {total}',
|
|
664
692
|
'sessions.preview.loadMore': 'Load more (remaining {remain})',
|
|
665
693
|
'sessions.preview.loadingMore': 'Loading older messages...',
|
|
@@ -895,7 +923,18 @@ const en = Object.freeze({
|
|
|
895
923
|
'config.health.title': 'Config health check',
|
|
896
924
|
'config.health.run': 'Run check',
|
|
897
925
|
'config.health.running': 'Checking...',
|
|
898
|
-
'config.health.hint': '
|
|
926
|
+
'config.health.hint': 'Checks only the currently selected provider; unselected providers do not count as this check\'s errors.',
|
|
927
|
+
'config.health.codexHint': 'Checks the current Codex route provider, endpoint, auth, and model through the local bridge.',
|
|
928
|
+
'config.health.providersSummary': 'Providers: {total} total · {green} ok · {yellow} config issues · {red} failed',
|
|
929
|
+
'config.health.failedProviders.title': 'Failed providers to delete',
|
|
930
|
+
'config.health.failedProviders.selectAria': 'Select failed provider {name} for deletion',
|
|
931
|
+
'config.health.failedProviders.clearSelection': 'Clear',
|
|
932
|
+
'config.health.failedProviders.selectAll': 'Select all',
|
|
933
|
+
'config.health.failedProviders.selectAllAria': 'Select all deletable failed providers',
|
|
934
|
+
'config.health.failedProviders.notDeletable': 'Reserved/system provider; cannot be deleted here.',
|
|
935
|
+
'config.health.failedProviders.writeRequired': 'Enable provider writes in this tab before deleting.',
|
|
936
|
+
'config.health.failedProviders.deleteSelected': 'Remove selected configs',
|
|
937
|
+
'config.health.failedProviders.deleting': 'Deleting...',
|
|
899
938
|
'config.health.progress': '{done}/{total} done · {failed} failed',
|
|
900
939
|
'config.health.ok': 'Passed',
|
|
901
940
|
'config.health.fail': 'Failed',
|
|
@@ -1301,7 +1340,7 @@ const en = Object.freeze({
|
|
|
1301
1340
|
'claude.health.title': 'Config health check',
|
|
1302
1341
|
'claude.health.run': 'Run check',
|
|
1303
1342
|
'claude.health.running': 'Checking...',
|
|
1304
|
-
'claude.health.hint': '
|
|
1343
|
+
'claude.health.hint': 'Checks only the currently selected Claude config; unselected configs do not count as this check\'s errors.',
|
|
1305
1344
|
'claude.health.progress': '{done}/{total} done · {failed} failed',
|
|
1306
1345
|
'claude.md.title': 'CLAUDE.md',
|
|
1307
1346
|
'claude.md.open': 'Open CLAUDE.md',
|