codexmate 0.0.23 → 0.0.24

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 (55) hide show
  1. package/README.md +4 -3
  2. package/README.zh.md +4 -2
  3. package/cli/auth-profiles.js +23 -7
  4. package/cli/doctor-core.js +903 -0
  5. package/cli/import-skills-url.js +334 -0
  6. package/cli.js +304 -208
  7. package/lib/cli-models-utils.js +0 -40
  8. package/lib/cli-network-utils.js +28 -2
  9. package/package.json +5 -2
  10. package/plugins/README.md +20 -0
  11. package/plugins/README.zh-CN.md +20 -0
  12. package/plugins/prompt-templates/comment-polish/index.mjs +25 -0
  13. package/plugins/prompt-templates/computed.mjs +253 -0
  14. package/plugins/prompt-templates/index.mjs +8 -0
  15. package/plugins/prompt-templates/manifest.mjs +15 -0
  16. package/plugins/prompt-templates/methods.mjs +619 -0
  17. package/plugins/prompt-templates/overview.mjs +90 -0
  18. package/plugins/prompt-templates/ownership.mjs +19 -0
  19. package/plugins/prompt-templates/rule-ack/index.mjs +21 -0
  20. package/plugins/prompt-templates/storage.mjs +64 -0
  21. package/plugins/registry.mjs +16 -0
  22. package/res/logo-pack.webp +0 -0
  23. package/web-ui/app.js +15 -32
  24. package/web-ui/index.html +4 -3
  25. package/web-ui/modules/app.computed.dashboard.mjs +22 -22
  26. package/web-ui/modules/app.computed.main-tabs.mjs +3 -0
  27. package/web-ui/modules/app.methods.agents.mjs +91 -3
  28. package/web-ui/modules/app.methods.codex-config.mjs +153 -164
  29. package/web-ui/modules/app.methods.navigation.mjs +34 -1
  30. package/web-ui/modules/app.methods.runtime.mjs +24 -2
  31. package/web-ui/modules/app.methods.session-browser.mjs +9 -2
  32. package/web-ui/modules/config-mode.computed.mjs +1 -3
  33. package/web-ui/modules/i18n.dict.mjs +2039 -0
  34. package/web-ui/modules/i18n.mjs +2 -1769
  35. package/web-ui/partials/index/layout-header.html +36 -32
  36. package/web-ui/partials/index/modal-config-template-agents.html +3 -4
  37. package/web-ui/partials/index/modal-health-check.html +33 -60
  38. package/web-ui/partials/index/panel-config-claude.html +35 -15
  39. package/web-ui/partials/index/panel-config-codex.html +47 -19
  40. package/web-ui/partials/index/panel-config-openclaw.html +8 -3
  41. package/web-ui/partials/index/panel-dashboard.html +186 -0
  42. package/web-ui/partials/index/panel-docs.html +1 -1
  43. package/web-ui/partials/index/panel-market.html +3 -0
  44. package/web-ui/partials/index/panel-orchestration.html +3 -0
  45. package/web-ui/partials/index/panel-plugins.html +16 -10
  46. package/web-ui/partials/index/panel-sessions.html +4 -1
  47. package/web-ui/partials/index/panel-settings.html +1 -1
  48. package/web-ui/partials/index/panel-usage.html +2 -1
  49. package/web-ui/styles/controls-forms.css +9 -2
  50. package/web-ui/styles/dashboard.css +274 -0
  51. package/web-ui/styles/layout-shell.css +2 -2
  52. package/web-ui/styles/sessions-list.css +3 -3
  53. package/web-ui/styles/sessions-usage.css +9 -0
  54. package/web-ui/styles.css +1 -0
  55. package/res/logo.png +0 -0
@@ -0,0 +1,90 @@
1
+ import {
2
+ persistPromptTemplatesToStorage,
3
+ readPromptTemplatesFromStorage,
4
+ readPromptTemplateSelectedIdFromStorage,
5
+ persistPromptTemplateSelectedIdToStorage
6
+ } from './storage.mjs';
7
+ import { buildBuiltinCommentPolishTemplate } from './comment-polish/index.mjs';
8
+ import { buildBuiltinRuleAckTemplate } from './rule-ack/index.mjs';
9
+
10
+ function ensureBuiltinTemplates(rawList, builtins) {
11
+ const list = Array.isArray(rawList) ? rawList.filter(Boolean) : [];
12
+ const builtinList = Array.isArray(builtins) ? builtins.filter(Boolean) : [];
13
+ const rest = list.filter((item) => !(item && item.isBuiltin === true));
14
+ const overridden = new Set(
15
+ rest
16
+ .map((item) => (item && typeof item.id === 'string' ? item.id.trim() : ''))
17
+ .filter(Boolean)
18
+ );
19
+ const resolvedBuiltins = builtinList.filter((item) => !(item && overridden.has(item.id)));
20
+ return [...resolvedBuiltins, ...rest];
21
+ }
22
+
23
+ export async function loadPromptTemplatesOverview(ctx, options = {}) {
24
+ const app = ctx && typeof ctx === 'object' ? ctx : {};
25
+ const silent = !!(options && options.silent);
26
+ const forceRefresh = !!(options && options.forceRefresh);
27
+
28
+ const shouldReload = forceRefresh || app.promptTemplatesLoadedOnce !== true;
29
+ if (!shouldReload) return true;
30
+
31
+ const t = typeof app.t === 'function' ? app.t : null;
32
+ const rawList = readPromptTemplatesFromStorage(localStorage);
33
+ const normalized = ensureBuiltinTemplates(rawList, [
34
+ buildBuiltinCommentPolishTemplate(t),
35
+ buildBuiltinRuleAckTemplate(t)
36
+ ]);
37
+ app.promptTemplatesListRaw = normalized;
38
+ persistPromptTemplatesToStorage(normalized, localStorage);
39
+
40
+ app.promptTemplatesLoadedOnce = true;
41
+
42
+ if (!app.promptTemplatesMode) {
43
+ app.promptTemplatesMode = 'compose';
44
+ }
45
+ if (app.promptTemplatesMode !== 'compose' && app.promptTemplatesMode !== 'manage') {
46
+ app.promptTemplatesMode = 'compose';
47
+ }
48
+ if (app.mainTab === 'plugins') {
49
+ app.promptTemplatesMode = 'compose';
50
+ }
51
+
52
+ if (app.mainTab === 'plugins' && app.promptTemplatesMode === 'compose') {
53
+ const list = Array.isArray(app.promptTemplatesList) ? app.promptTemplatesList : [];
54
+ const storedSelected = readPromptTemplateSelectedIdFromStorage(localStorage);
55
+ const exists = list.some((item) => item && item.id === app.promptComposerSelectedTemplateId);
56
+ const storedExists = storedSelected ? list.some((item) => item && item.id === storedSelected) : false;
57
+ if (storedExists && storedSelected !== app.promptComposerSelectedTemplateId) {
58
+ app.promptComposerSelectedTemplateId = storedSelected;
59
+ } else if (!app.promptComposerSelectedTemplateId || !exists) {
60
+ app.promptComposerSelectedTemplateId = 'builtin_comment_polish';
61
+ }
62
+ persistPromptTemplateSelectedIdToStorage(app.promptComposerSelectedTemplateId, localStorage);
63
+ if (!app.promptComposerVarValuesRaw || typeof app.promptComposerVarValuesRaw !== 'object') {
64
+ app.promptComposerVarValuesRaw = {};
65
+ }
66
+ }
67
+
68
+ if (app.promptTemplatesMode === 'manage') {
69
+ const currentSelected = typeof app.promptTemplateSelectedId === 'string'
70
+ ? app.promptTemplateSelectedId
71
+ : '';
72
+ const first = Array.isArray(app.promptTemplatesList) && app.promptTemplatesList.length
73
+ ? app.promptTemplatesList[0]
74
+ : null;
75
+ if (!currentSelected && first && typeof app.selectPromptTemplate === 'function') {
76
+ app.selectPromptTemplate(first.id);
77
+ }
78
+ }
79
+
80
+ if (app.mainTab === 'plugins' && app.promptTemplatesMode === 'compose' && typeof app.$nextTick === 'function') {
81
+ app.$nextTick(() => {
82
+ const input = app.$refs && app.$refs.promptComposerCodeInput
83
+ ? app.$refs.promptComposerCodeInput
84
+ : null;
85
+ if (input && typeof input.focus === 'function') input.focus();
86
+ });
87
+ }
88
+
89
+ return true;
90
+ }
@@ -0,0 +1,19 @@
1
+ export const pluginOwnership = {
2
+ pluginId: 'prompt-templates',
3
+ createdBy: 'ymkiux',
4
+ maintainers: ['ymkiux']
5
+ };
6
+
7
+ export const templateOwnershipById = {
8
+ builtin_comment_polish: {
9
+ templateId: 'builtin_comment_polish',
10
+ createdBy: 'ymkiux',
11
+ maintainers: ['ymkiux']
12
+ },
13
+ builtin_rule_ack: {
14
+ templateId: 'builtin_rule_ack',
15
+ createdBy: 'ymkiux',
16
+ maintainers: ['ymkiux']
17
+ }
18
+ };
19
+
@@ -0,0 +1,21 @@
1
+ import { pluginOwnership, templateOwnershipById } from '../ownership.mjs';
2
+
3
+ export function buildBuiltinRuleAckTemplate(t) {
4
+ const tr = (key, fallback, params = null) => (typeof t === 'function' ? t(key, params) : fallback);
5
+ const line1 = tr('plugins.builtin.ruleAck.line1', '请根据【{{rule}}】,收到请回复');
6
+ const timestamp = new Date().toISOString();
7
+ const ownership = templateOwnershipById && templateOwnershipById.builtin_rule_ack
8
+ ? templateOwnershipById.builtin_rule_ack
9
+ : pluginOwnership;
10
+ return {
11
+ id: 'builtin_rule_ack',
12
+ name: tr('plugins.builtin.ruleAck.name', '规则确认回复'),
13
+ description: tr('plugins.builtin.ruleAck.desc', '请根据【{{rule}}】,收到请回复'),
14
+ template: line1,
15
+ createdAt: timestamp,
16
+ updatedAt: timestamp,
17
+ isBuiltin: true,
18
+ createdBy: ownership && typeof ownership.createdBy === 'string' ? ownership.createdBy : '',
19
+ maintainers: ownership && Array.isArray(ownership.maintainers) ? ownership.maintainers : []
20
+ };
21
+ }
@@ -0,0 +1,64 @@
1
+ const STORAGE_KEY = 'codexmate.plugins.promptTemplates.v1';
2
+ const SELECTED_TEMPLATE_STORAGE_KEY = 'codexmate.plugins.promptTemplates.selectedTemplateId.v1';
3
+
4
+ export function readPromptTemplatesFromStorage(storage = localStorage) {
5
+ if (!storage) return [];
6
+ let raw = '';
7
+ try {
8
+ raw = storage.getItem(STORAGE_KEY) || '';
9
+ } catch (_) {
10
+ raw = '';
11
+ }
12
+ if (!raw) return [];
13
+ try {
14
+ const parsed = JSON.parse(raw);
15
+ if (!Array.isArray(parsed)) return [];
16
+ return parsed;
17
+ } catch (_) {
18
+ return [];
19
+ }
20
+ }
21
+
22
+ export function persistPromptTemplatesToStorage(list, storage = localStorage) {
23
+ if (!storage) return false;
24
+ try {
25
+ storage.setItem(STORAGE_KEY, JSON.stringify(Array.isArray(list) ? list : []));
26
+ return true;
27
+ } catch (_) {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ export function clearPromptTemplatesStorage(storage = localStorage) {
33
+ if (!storage) return;
34
+ try {
35
+ storage.removeItem(STORAGE_KEY);
36
+ } catch (_) {}
37
+ }
38
+
39
+ export function readPromptTemplateSelectedIdFromStorage(storage = localStorage) {
40
+ if (!storage) return '';
41
+ let raw = '';
42
+ try {
43
+ raw = storage.getItem(SELECTED_TEMPLATE_STORAGE_KEY) || '';
44
+ } catch (_) {
45
+ raw = '';
46
+ }
47
+ const id = typeof raw === 'string' ? raw.trim() : '';
48
+ return id;
49
+ }
50
+
51
+ export function persistPromptTemplateSelectedIdToStorage(templateId, storage = localStorage) {
52
+ if (!storage) return false;
53
+ const id = typeof templateId === 'string' ? templateId.trim() : '';
54
+ try {
55
+ if (!id) {
56
+ storage.removeItem(SELECTED_TEMPLATE_STORAGE_KEY);
57
+ return true;
58
+ }
59
+ storage.setItem(SELECTED_TEMPLATE_STORAGE_KEY, id);
60
+ return true;
61
+ } catch (_) {
62
+ return false;
63
+ }
64
+ }
@@ -0,0 +1,16 @@
1
+ import { pluginMeta as promptTemplatesMeta } from './prompt-templates/manifest.mjs';
2
+ import { loadPromptTemplatesOverview } from './prompt-templates/overview.mjs';
3
+
4
+ export const pluginsRegistry = [
5
+ { id: promptTemplatesMeta.id, meta: promptTemplatesMeta, loadOverview: loadPromptTemplatesOverview }
6
+ ];
7
+
8
+ export function getFirstPluginId() {
9
+ return pluginsRegistry.length ? pluginsRegistry[0].id : '';
10
+ }
11
+
12
+ export function getPluginEntry(id) {
13
+ const key = typeof id === 'string' ? id.trim() : '';
14
+ if (!key) return null;
15
+ return pluginsRegistry.find((item) => item && item.id === key) || null;
16
+ }
Binary file
package/web-ui/app.js CHANGED
@@ -30,8 +30,7 @@ document.addEventListener('DOMContentLoaded', () => {
30
30
  data() {
31
31
  return {
32
32
  lang: 'zh',
33
- // 默认选中首个主标签:Docs
34
- mainTab: 'docs',
33
+ mainTab: 'dashboard',
35
34
  configMode: 'codex',
36
35
  currentProvider: '',
37
36
  currentModel: '',
@@ -63,9 +62,9 @@ document.addEventListener('DOMContentLoaded', () => {
63
62
  showEditConfigModal: false,
64
63
  showOpenclawConfigModal: false,
65
64
  showConfigTemplateModal: false,
66
- showHealthCheckDialog: false,
67
65
  showAgentsModal: false,
68
66
  showSkillsModal: false,
67
+ showHealthCheckModal: false,
69
68
  // Plugins
70
69
  pluginsActiveId: 'prompt-templates',
71
70
  pluginsLoading: false,
@@ -245,38 +244,14 @@ document.addEventListener('DOMContentLoaded', () => {
245
244
  providerSwitchInProgress: false,
246
245
  pendingProviderSwitch: '',
247
246
  providerSwitchDisplayTarget: '',
248
- healthCheckDialogLockedProvider: '',
249
- healthCheckDialogSelectedProvider: '',
250
- healthCheckDialogPrompt: '请简短回复:连接正常。',
251
- healthCheckDialogMessages: [],
252
- healthCheckDialogSending: false,
253
- healthCheckDialogLastResult: null,
247
+ healthCheckBatchTotal: 0,
248
+ healthCheckBatchDone: 0,
249
+ healthCheckBatchFailed: 0,
254
250
  installPackageManager: 'npm',
255
251
  installCommandAction: 'install',
256
252
  installRegistryPreset: 'default',
257
253
  installRegistryCustom: '',
258
- installStatusTargets: [
259
- {
260
- id: 'claude',
261
- name: 'Claude Code CLI',
262
- packageName: '@anthropic-ai/claude-code',
263
- installed: false,
264
- bin: 'claude',
265
- version: '',
266
- commandPath: '',
267
- error: ''
268
- },
269
- {
270
- id: 'codex',
271
- name: 'Codex CLI',
272
- packageName: '@openai/codex',
273
- installed: false,
274
- bin: 'codex',
275
- version: '',
276
- commandPath: '',
277
- error: ''
278
- }
279
- ],
254
+ installStatusTargets: null,
280
255
  newProvider: { name: '', url: '', key: '', useTransform: false },
281
256
  resetConfigLoading: false,
282
257
  editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false },
@@ -434,7 +409,7 @@ document.addEventListener('DOMContentLoaded', () => {
434
409
  }
435
410
  {
436
411
  const NAV_STATE_STORAGE_KEY = 'codexmateNavState.v1';
437
- const mainTabSet = new Set(['config', 'sessions', 'usage', 'orchestration', 'market', 'plugins', 'docs', 'settings']);
412
+ const mainTabSet = new Set(['dashboard', 'config', 'sessions', 'usage', 'orchestration', 'market', 'plugins', 'docs', 'settings']);
438
413
  let restored = null;
439
414
  try {
440
415
  const raw = localStorage.getItem(NAV_STATE_STORAGE_KEY) || '';
@@ -557,6 +532,14 @@ document.addEventListener('DOMContentLoaded', () => {
557
532
  if (!startupOk) {
558
533
  return;
559
534
  }
535
+ if (this.mainTab === 'dashboard') {
536
+ if (!this.__doctorLoadedOnce) {
537
+ this.__doctorLoadedOnce = true;
538
+ if (typeof this.runHealthCheck === 'function') {
539
+ void this.runHealthCheck({ doctor: true, silent: true });
540
+ }
541
+ }
542
+ }
560
543
  void this.refreshClaudeSelectionFromSettings({ silent: true });
561
544
  void this.syncDefaultOpenclawConfigEntry({ silent: true });
562
545
  };
package/web-ui/index.html CHANGED
@@ -4,14 +4,15 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Codex Mate</title>
7
- <link rel="icon" type="image/png" href="/res/logo.png">
8
- <link rel="apple-touch-icon" href="/res/logo.png">
7
+ <link rel="icon" type="image/webp" href="/res/logo-pack.webp">
8
+ <link rel="apple-touch-icon" href="/res/logo-pack.webp">
9
9
  <script src="/res/vue.global.prod.js"></script>
10
10
  <script src="/res/json5.min.js"></script>
11
11
  <link rel="stylesheet" href="/web-ui/styles.css">
12
12
  </head>
13
13
  <body>
14
14
  <!-- @include ./partials/index/layout-header.html -->
15
+ <!-- @include ./partials/index/panel-dashboard.html -->
15
16
  <!-- @include ./partials/index/panel-config-codex.html -->
16
17
  <!-- @include ./partials/index/panel-config-claude.html -->
17
18
  <!-- @include ./partials/index/panel-config-openclaw.html -->
@@ -24,10 +25,10 @@
24
25
  <!-- @include ./partials/index/panel-plugins.html -->
25
26
  <!-- @include ./partials/index/layout-footer.html -->
26
27
  <!-- @include ./partials/index/modals-basic.html -->
27
- <!-- @include ./partials/index/modal-health-check.html -->
28
28
  <!-- @include ./partials/index/modal-openclaw-config.html -->
29
29
  <!-- @include ./partials/index/modal-config-template-agents.html -->
30
30
  <!-- @include ./partials/index/modal-skills.html -->
31
+ <!-- @include ./partials/index/modal-health-check.html -->
31
32
  <!-- @include ./partials/index/modal-confirm-toast.html -->
32
33
  <script type="module" src="/web-ui/app.js"></script>
33
34
  </body>
@@ -37,7 +37,7 @@ export function createDashboardComputed() {
37
37
  },
38
38
  displayProvidersList() {
39
39
  const list = Array.isArray(this.providersList) ? this.providersList : [];
40
- return list.filter((item) => String(item && item.name ? item.name : '').trim().toLowerCase() !== 'codexmate-proxy');
40
+ return list;
41
41
  },
42
42
  installTargetCards() {
43
43
  const targets = Array.isArray(this.installStatusTargets) ? this.installStatusTargets : [];
@@ -59,39 +59,39 @@ export function createDashboardComputed() {
59
59
  },
60
60
  inspectorBusyStatus() {
61
61
  const tasks = [];
62
- if (this.loading) tasks.push('初始化');
63
- if (this.sessionsLoading) tasks.push('会话加载');
64
- if (this.codexModelsLoading || this.claudeModelsLoading) tasks.push('模型加载');
65
- if (this.codexApplying || this.configTemplateApplying || this.openclawApplying) tasks.push('配置应用');
66
- if (this.agentsSaving) tasks.push('AGENTS 保存');
67
- if (this.skillsLoading || this.skillsDeleting || this.skillsScanningImports || this.skillsImporting || this.skillsZipImporting || this.skillsExporting) tasks.push('Skills 管理');
62
+ if (this.loading) tasks.push(this.t('dashboard.busy.init'));
63
+ if (this.sessionsLoading) tasks.push(this.t('dashboard.busy.sessions'));
64
+ if (this.codexModelsLoading || this.claudeModelsLoading) tasks.push(this.t('dashboard.busy.models'));
65
+ if (this.codexApplying || this.configTemplateApplying || this.openclawApplying) tasks.push(this.t('dashboard.busy.configApply'));
66
+ if (this.agentsSaving) tasks.push(this.t('dashboard.busy.agents'));
67
+ if (this.skillsLoading || this.skillsDeleting || this.skillsScanningImports || this.skillsImporting || this.skillsZipImporting || this.skillsExporting) tasks.push(this.t('dashboard.busy.skills'));
68
68
  if (this.taskOrchestration && (this.taskOrchestration.loading || this.taskOrchestration.planning || this.taskOrchestration.running || this.taskOrchestration.queueAdding || this.taskOrchestration.queueStarting || this.taskOrchestration.retrying || this.taskOrchestration.selectedRunLoading)) {
69
- tasks.push('任务编排');
69
+ tasks.push(this.t('dashboard.busy.tasks'));
70
70
  }
71
- return tasks.length ? tasks.join(' / ') : '空闲';
71
+ return tasks.length ? tasks.join(' / ') : this.t('dashboard.busy.idle');
72
72
  },
73
73
  inspectorMessageSummary() {
74
74
  const value = typeof this.message === 'string' ? this.message.trim() : '';
75
- return value || '暂无提示';
75
+ return value || this.t('dashboard.message.none');
76
76
  },
77
77
  inspectorSessionSourceLabel() {
78
- if (this.sessionFilterSource === 'codex') return 'Codex';
79
- if (this.sessionFilterSource === 'claude') return 'Claude Code';
80
- return '全部';
78
+ if (this.sessionFilterSource === 'codex') return this.t('dashboard.sessionSource.codex');
79
+ if (this.sessionFilterSource === 'claude') return this.t('dashboard.sessionSource.claude');
80
+ return this.t('dashboard.sessionSource.all');
81
81
  },
82
82
  inspectorSessionPathLabel() {
83
83
  const value = typeof this.sessionPathFilter === 'string' ? this.sessionPathFilter.trim() : '';
84
- return value || '全部路径';
84
+ return value || this.t('dashboard.sessionPath.all');
85
85
  },
86
86
  inspectorSessionQueryLabel() {
87
- if (!this.isSessionQueryEnabled) return '当前来源不支持';
87
+ if (!this.isSessionQueryEnabled) return this.t('dashboard.sessionQuery.unsupported');
88
88
  const value = typeof this.sessionQuery === 'string' ? this.sessionQuery.trim() : '';
89
- return value || '未设置';
89
+ return value || this.t('dashboard.sessionQuery.unset');
90
90
  },
91
91
  inspectorHealthStatus() {
92
- if (this.initError) return '读取失败';
93
- if (this.loading) return '初始化中';
94
- return '正常';
92
+ if (this.initError) return this.t('dashboard.healthStatus.failRead');
93
+ if (this.loading) return this.t('dashboard.healthStatus.initializing');
94
+ return this.t('dashboard.healthStatus.ok');
95
95
  },
96
96
  inspectorHealthTone() {
97
97
  if (this.initError) return 'error';
@@ -100,12 +100,12 @@ export function createDashboardComputed() {
100
100
  },
101
101
  inspectorModelLoadStatus() {
102
102
  if (this.codexModelsLoading || this.claudeModelsLoading) {
103
- return '加载中';
103
+ return this.t('dashboard.modelStatus.loading');
104
104
  }
105
105
  if (this.modelsSource === 'error' || this.claudeModelsSource === 'error') {
106
- return '加载异常';
106
+ return this.t('dashboard.modelStatus.error');
107
107
  }
108
- return '正常';
108
+ return this.t('dashboard.modelStatus.ok');
109
109
  },
110
110
  installTroubleshootingTips() {
111
111
  const platform = this.resolveInstallPlatform();
@@ -136,6 +136,7 @@ function createTaskDraftReadiness(metrics) {
136
136
  export function createMainTabsComputed() {
137
137
  return {
138
138
  mainTabKicker() {
139
+ if (this.mainTab === 'dashboard') return this.t('kicker.dashboard');
139
140
  if (this.mainTab === 'config') return this.t('kicker.config');
140
141
  if (this.mainTab === 'sessions') return this.t('kicker.sessions');
141
142
  if (this.mainTab === 'usage') return this.t('kicker.usage');
@@ -146,6 +147,7 @@ export function createMainTabsComputed() {
146
147
  return this.t('kicker.settings');
147
148
  },
148
149
  mainTabTitle() {
150
+ if (this.mainTab === 'dashboard') return this.t('title.dashboard');
149
151
  if (this.mainTab === 'config') return this.t('title.config');
150
152
  if (this.mainTab === 'sessions') return this.t('title.sessions');
151
153
  if (this.mainTab === 'usage') return this.t('title.usage');
@@ -156,6 +158,7 @@ export function createMainTabsComputed() {
156
158
  return this.t('title.settings');
157
159
  },
158
160
  mainTabSubtitle() {
161
+ if (this.mainTab === 'dashboard') return this.t('subtitle.dashboard');
159
162
  if (this.mainTab === 'config') return this.t('subtitle.config');
160
163
  if (this.mainTab === 'sessions') return this.t('subtitle.sessions');
161
164
  if (this.mainTab === 'usage') return this.t('subtitle.usage');
@@ -193,8 +193,8 @@ export function createAgentsMethods(options = {}) {
193
193
  const fileName = (options.fileName || this.openclawWorkspaceFileName || 'AGENTS.md').trim();
194
194
  this.agentsContext = 'openclaw-workspace';
195
195
  this.agentsWorkspaceFileName = fileName;
196
- this.agentsModalTitle = `OpenClaw 工作区文件: ${fileName}`;
197
- this.agentsModalHint = `保存后会写入 OpenClaw Workspace 下的 ${fileName}。`;
196
+ this.agentsModalTitle = tr('modal.agents.title.openclawWorkspaceFile', `OpenClaw 工作区文件: ${fileName}`, { fileName });
197
+ this.agentsModalHint = tr('modal.agents.hint.openclawWorkspaceFile', `保存后会写入 OpenClaw Workspace 下的 ${fileName}。`, { fileName });
198
198
  return;
199
199
  }
200
200
  this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
@@ -224,7 +224,47 @@ export function createAgentsMethods(options = {}) {
224
224
  this._agentsDiffPreviewRequestToken = null;
225
225
  },
226
226
  handleGlobalKeydown(event) {
227
- if (!event || event.key !== 'Escape') {
227
+ if (!event) {
228
+ return;
229
+ }
230
+ const isCmdLike = !!(event.metaKey || event.ctrlKey);
231
+ const key = typeof event.key === 'string' ? event.key : '';
232
+ const isSearchHotkey = isCmdLike && !event.altKey && (key === 'k' || key === 'K');
233
+ if (isSearchHotkey) {
234
+ const target = event.target;
235
+ const tag = target && target.tagName ? String(target.tagName).toUpperCase() : '';
236
+ const isTypingTarget = !!(
237
+ tag === 'INPUT'
238
+ || tag === 'TEXTAREA'
239
+ || tag === 'SELECT'
240
+ || (target && target.isContentEditable)
241
+ );
242
+ if (!isTypingTarget) {
243
+ event.preventDefault();
244
+ event.stopPropagation();
245
+ try {
246
+ const focusSelector = (() => {
247
+ if (this.showSkillsModal) return '.skills-filter-row input.form-input';
248
+ if (this.mainTab === 'sessions') return '#panel-sessions .session-query-input';
249
+ if (this.mainTab === 'plugins' && this.pluginsActiveId === 'prompt-templates' && this.promptTemplatesMode !== 'compose') {
250
+ return '#panel-plugins .prompt-templates-toolbar input.form-input';
251
+ }
252
+ return '';
253
+ })();
254
+ if (focusSelector) {
255
+ const el = document.querySelector(focusSelector);
256
+ if (el && typeof el.focus === 'function') {
257
+ el.focus();
258
+ if (typeof el.select === 'function') {
259
+ el.select();
260
+ }
261
+ }
262
+ }
263
+ } catch (_) {}
264
+ }
265
+ return;
266
+ }
267
+ if (key !== 'Escape') {
228
268
  return;
229
269
  }
230
270
  if (this.showConfirmDialog) {
@@ -233,6 +273,54 @@ export function createAgentsMethods(options = {}) {
233
273
  this.resolveConfirmDialog(false);
234
274
  return;
235
275
  }
276
+ if (this.showSkillsModal && typeof this.closeSkillsModal === 'function') {
277
+ event.preventDefault();
278
+ event.stopPropagation();
279
+ this.closeSkillsModal();
280
+ return;
281
+ }
282
+ if (this.showOpenclawConfigModal && typeof this.closeOpenclawConfigModal === 'function') {
283
+ event.preventDefault();
284
+ event.stopPropagation();
285
+ this.closeOpenclawConfigModal();
286
+ return;
287
+ }
288
+ if (this.showConfigTemplateModal && typeof this.closeConfigTemplateModal === 'function') {
289
+ event.preventDefault();
290
+ event.stopPropagation();
291
+ this.closeConfigTemplateModal();
292
+ return;
293
+ }
294
+ if (this.showEditConfigModal && typeof this.closeEditConfigModal === 'function') {
295
+ event.preventDefault();
296
+ event.stopPropagation();
297
+ this.closeEditConfigModal();
298
+ return;
299
+ }
300
+ if (this.showClaudeConfigModal && typeof this.closeClaudeConfigModal === 'function') {
301
+ event.preventDefault();
302
+ event.stopPropagation();
303
+ this.closeClaudeConfigModal();
304
+ return;
305
+ }
306
+ if (this.showModelModal && typeof this.closeModelModal === 'function') {
307
+ event.preventDefault();
308
+ event.stopPropagation();
309
+ this.closeModelModal();
310
+ return;
311
+ }
312
+ if (this.showAddModal && typeof this.closeAddModal === 'function') {
313
+ event.preventDefault();
314
+ event.stopPropagation();
315
+ this.closeAddModal();
316
+ return;
317
+ }
318
+ if (this.showEditModal && typeof this.closeEditModal === 'function') {
319
+ event.preventDefault();
320
+ event.stopPropagation();
321
+ this.closeEditModal();
322
+ return;
323
+ }
236
324
  if (!this.showAgentsModal) {
237
325
  return;
238
326
  }