codexmate 0.0.55 → 0.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +2 -0
  2. package/README.vi.md +2 -0
  3. package/README.zh.md +2 -0
  4. package/cli/local-bridge.js +221 -22
  5. package/cli.js +797 -134
  6. package/lib/task-orchestrator.js +90 -21
  7. package/lib/task-workspace-chat.js +292 -0
  8. package/package.json +2 -2
  9. package/web-ui/app.js +57 -129
  10. package/web-ui/modules/app.computed.main-tabs.mjs +304 -7
  11. package/web-ui/modules/app.computed.session.mjs +210 -0
  12. package/web-ui/modules/app.methods.agents.mjs +3 -0
  13. package/web-ui/modules/app.methods.claude-config.mjs +178 -22
  14. package/web-ui/modules/app.methods.codex-config.mjs +294 -65
  15. package/web-ui/modules/app.methods.navigation.mjs +71 -84
  16. package/web-ui/modules/app.methods.openclaw-editing.mjs +3 -6
  17. package/web-ui/modules/app.methods.providers.mjs +15 -1
  18. package/web-ui/modules/app.methods.runtime.mjs +7 -2
  19. package/web-ui/modules/app.methods.session-actions.mjs +25 -12
  20. package/web-ui/modules/app.methods.session-browser.mjs +23 -54
  21. package/web-ui/modules/app.methods.session-trash.mjs +0 -1
  22. package/web-ui/modules/app.methods.startup-claude.mjs +35 -17
  23. package/web-ui/modules/app.methods.task-orchestration.mjs +347 -8
  24. package/web-ui/modules/app.methods.tool-config-permissions.mjs +0 -3
  25. package/web-ui/modules/app.methods.web-ui-preferences.mjs +415 -68
  26. package/web-ui/modules/config-template-confirm-pref.mjs +2 -3
  27. package/web-ui/modules/i18n/locales/en.mjs +187 -28
  28. package/web-ui/modules/i18n/locales/ja.mjs +184 -25
  29. package/web-ui/modules/i18n/locales/vi.mjs +186 -33
  30. package/web-ui/modules/i18n/locales/zh-tw.mjs +187 -28
  31. package/web-ui/modules/i18n/locales/zh.mjs +189 -30
  32. package/web-ui/modules/i18n.mjs +5 -12
  33. package/web-ui/modules/provider-default-names.mjs +25 -0
  34. package/web-ui/modules/sessions-filters-url.mjs +1 -2
  35. package/web-ui/partials/index/layout-header.html +37 -14
  36. package/web-ui/partials/index/modal-health-check.html +69 -5
  37. package/web-ui/partials/index/panel-config-codex.html +2 -2
  38. package/web-ui/partials/index/panel-orchestration.html +489 -282
  39. package/web-ui/partials/index/panel-sessions.html +97 -17
  40. package/web-ui/res/web-ui-render.precompiled.js +1423 -732
  41. package/web-ui/session-helpers.mjs +4 -1
  42. package/web-ui/styles/layout-shell.css +157 -1
  43. package/web-ui/styles/navigation-panels.css +11 -0
  44. package/web-ui/styles/responsive.css +98 -0
  45. package/web-ui/styles/sessions-preview.css +212 -2
  46. package/web-ui/styles/sessions-toolbar-trash.css +61 -18
  47. package/web-ui/styles/skills-list.css +122 -0
  48. package/web-ui/styles/task-orchestration.css +2161 -4
  49. 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 entries = Object.entries(this.claudeConfigs || {});
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('检查通过', 'success');
431
+ this.showMessage(this.t('toast.check.success'), 'success');
400
432
  }
401
433
  return;
402
434
  }
403
435
 
404
- const shouldRunSpeedTests = this.configMode === 'codex';
405
- const speedTimeoutMs = shouldRunSpeedTests ? 3500 : 0;
406
- const providers = shouldRunSpeedTests
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(res)) {
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(res, '检查失败'), 'error');
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 ok = issues.length === 0;
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
- ...res,
452
+ ...configRes,
466
453
  ok,
467
454
  issues,
468
- remote
455
+ remote: configRes.remote || null
469
456
  };
470
- if (ok && !silent) {
471
- this.showMessage('检查通过', 'success');
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('检查失败', 'error');
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
- if (typeof this.runProvidersHealthCheck === 'function' && this.configMode === 'codex') {
489
- void this.runProvidersHealthCheck({ remote: true });
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 = {}) {