codexmate 0.0.23 → 0.0.25

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 (73) hide show
  1. package/README.md +32 -9
  2. package/README.zh.md +33 -9
  3. package/cli/auth-profiles.js +23 -7
  4. package/cli/builtin-proxy.js +35 -0
  5. package/cli/claude-proxy.js +24 -0
  6. package/cli/doctor-core.js +903 -0
  7. package/cli/import-skills-url.js +356 -0
  8. package/cli/openai-bridge.js +51 -4
  9. package/cli/session-usage.js +8 -2
  10. package/cli.js +1921 -399
  11. package/lib/automation.js +404 -0
  12. package/lib/cli-models-utils.js +0 -40
  13. package/lib/cli-network-utils.js +28 -2
  14. package/lib/cli-path-utils.js +21 -5
  15. package/lib/cli-sessions.js +32 -1
  16. package/lib/download-artifacts.js +17 -2
  17. package/lib/mcp-stdio.js +13 -0
  18. package/package.json +3 -3
  19. package/plugins/README.md +20 -0
  20. package/plugins/README.zh-CN.md +20 -0
  21. package/plugins/prompt-templates/comment-polish/index.mjs +25 -0
  22. package/plugins/prompt-templates/computed.mjs +253 -0
  23. package/plugins/prompt-templates/index.mjs +8 -0
  24. package/plugins/prompt-templates/manifest.mjs +15 -0
  25. package/plugins/prompt-templates/methods.mjs +619 -0
  26. package/plugins/prompt-templates/overview.mjs +90 -0
  27. package/plugins/prompt-templates/ownership.mjs +19 -0
  28. package/plugins/prompt-templates/rule-ack/index.mjs +21 -0
  29. package/plugins/prompt-templates/storage.mjs +64 -0
  30. package/plugins/registry.mjs +16 -0
  31. package/web-ui/app.js +21 -35
  32. package/web-ui/index.html +4 -3
  33. package/web-ui/logic.sessions.mjs +2 -2
  34. package/web-ui/modules/app.computed.dashboard.mjs +24 -22
  35. package/web-ui/modules/app.computed.main-tabs.mjs +3 -0
  36. package/web-ui/modules/app.computed.session.mjs +17 -0
  37. package/web-ui/modules/app.methods.agents.mjs +91 -3
  38. package/web-ui/modules/app.methods.codex-config.mjs +153 -164
  39. package/web-ui/modules/app.methods.install.mjs +28 -0
  40. package/web-ui/modules/app.methods.navigation.mjs +34 -1
  41. package/web-ui/modules/app.methods.runtime.mjs +24 -2
  42. package/web-ui/modules/app.methods.session-actions.mjs +8 -1
  43. package/web-ui/modules/app.methods.session-browser.mjs +37 -6
  44. package/web-ui/modules/app.methods.session-trash.mjs +4 -2
  45. package/web-ui/modules/config-mode.computed.mjs +1 -3
  46. package/web-ui/modules/i18n.dict.mjs +2055 -0
  47. package/web-ui/modules/i18n.mjs +2 -1769
  48. package/web-ui/partials/index/layout-header.html +48 -34
  49. package/web-ui/partials/index/modal-config-template-agents.html +3 -4
  50. package/web-ui/partials/index/modal-health-check.html +33 -60
  51. package/web-ui/partials/index/panel-config-claude.html +35 -15
  52. package/web-ui/partials/index/panel-config-codex.html +47 -19
  53. package/web-ui/partials/index/panel-config-openclaw.html +8 -3
  54. package/web-ui/partials/index/panel-dashboard.html +186 -0
  55. package/web-ui/partials/index/panel-docs.html +1 -1
  56. package/web-ui/partials/index/panel-market.html +3 -0
  57. package/web-ui/partials/index/panel-orchestration.html +3 -0
  58. package/web-ui/partials/index/panel-plugins.html +16 -10
  59. package/web-ui/partials/index/panel-sessions.html +8 -3
  60. package/web-ui/partials/index/panel-settings.html +1 -1
  61. package/web-ui/partials/index/panel-usage.html +9 -1
  62. package/web-ui/res/logo-pack.webp +0 -0
  63. package/web-ui/styles/controls-forms.css +58 -4
  64. package/web-ui/styles/dashboard.css +274 -0
  65. package/web-ui/styles/layout-shell.css +3 -2
  66. package/web-ui/styles/responsive.css +0 -2
  67. package/web-ui/styles/sessions-list.css +5 -7
  68. package/web-ui/styles/sessions-toolbar-trash.css +4 -4
  69. package/web-ui/styles/sessions-usage.css +33 -0
  70. package/web-ui/styles.css +1 -0
  71. package/res/logo.png +0 -0
  72. /package/{res → web-ui/res}/json5.min.js +0 -0
  73. /package/{res → web-ui/res}/vue.global.prod.js +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
+ }
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,
@@ -179,17 +178,20 @@ document.addEventListener('DOMContentLoaded', () => {
179
178
  sessionPathOptionsMap: {
180
179
  all: [],
181
180
  codex: [],
182
- claude: []
181
+ claude: [],
182
+ gemini: []
183
183
  },
184
184
  sessionPathOptionsLoadedMap: {
185
185
  all: false,
186
186
  codex: false,
187
- claude: false
187
+ claude: false,
188
+ gemini: false
188
189
  },
189
190
  sessionPathRequestSeqMap: {
190
191
  all: 0,
191
192
  codex: 0,
192
- claude: 0
193
+ claude: 0,
194
+ gemini: 0
193
195
  },
194
196
  sessionExporting: {},
195
197
  sessionCloning: {},
@@ -245,38 +247,14 @@ document.addEventListener('DOMContentLoaded', () => {
245
247
  providerSwitchInProgress: false,
246
248
  pendingProviderSwitch: '',
247
249
  providerSwitchDisplayTarget: '',
248
- healthCheckDialogLockedProvider: '',
249
- healthCheckDialogSelectedProvider: '',
250
- healthCheckDialogPrompt: '请简短回复:连接正常。',
251
- healthCheckDialogMessages: [],
252
- healthCheckDialogSending: false,
253
- healthCheckDialogLastResult: null,
250
+ healthCheckBatchTotal: 0,
251
+ healthCheckBatchDone: 0,
252
+ healthCheckBatchFailed: 0,
254
253
  installPackageManager: 'npm',
255
254
  installCommandAction: 'install',
256
255
  installRegistryPreset: 'default',
257
256
  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
- ],
257
+ installStatusTargets: null,
280
258
  newProvider: { name: '', url: '', key: '', useTransform: false },
281
259
  resetConfigLoading: false,
282
260
  editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false },
@@ -434,7 +412,7 @@ document.addEventListener('DOMContentLoaded', () => {
434
412
  }
435
413
  {
436
414
  const NAV_STATE_STORAGE_KEY = 'codexmateNavState.v1';
437
- const mainTabSet = new Set(['config', 'sessions', 'usage', 'orchestration', 'market', 'plugins', 'docs', 'settings']);
415
+ const mainTabSet = new Set(['dashboard', 'config', 'sessions', 'usage', 'orchestration', 'market', 'plugins', 'docs', 'settings']);
438
416
  let restored = null;
439
417
  try {
440
418
  const raw = localStorage.getItem(NAV_STATE_STORAGE_KEY) || '';
@@ -557,6 +535,14 @@ document.addEventListener('DOMContentLoaded', () => {
557
535
  if (!startupOk) {
558
536
  return;
559
537
  }
538
+ if (this.mainTab === 'dashboard') {
539
+ if (!this.__doctorLoadedOnce) {
540
+ this.__doctorLoadedOnce = true;
541
+ if (typeof this.runHealthCheck === 'function') {
542
+ void this.runHealthCheck({ doctor: true, silent: true });
543
+ }
544
+ }
545
+ }
560
546
  void this.refreshClaudeSelectionFromSettings({ silent: true });
561
547
  void this.syncDefaultOpenclawConfigEntry({ silent: true });
562
548
  };
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>
@@ -29,14 +29,14 @@ function shouldUseFastSessionBrowseLimit(options = {}) {
29
29
 
30
30
  export function isSessionQueryEnabled(source) {
31
31
  const normalized = normalizeSessionSource(source, '');
32
- return normalized === 'codex' || normalized === 'claude' || normalized === 'all';
32
+ return normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'all';
33
33
  }
34
34
 
35
35
  export function normalizeSessionSource(source, fallback = 'all') {
36
36
  const normalized = typeof source === 'string'
37
37
  ? source.trim().toLowerCase()
38
38
  : '';
39
- if (normalized === 'codex' || normalized === 'claude' || normalized === 'all') {
39
+ if (normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'all') {
40
40
  return normalized;
41
41
  }
42
42
  return fallback;
@@ -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,41 @@ 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
+ if (this.sessionFilterSource === 'gemini') return this.t('dashboard.sessionSource.gemini');
81
+ if (this.sessionFilterSource === 'codebuddy') return this.t('dashboard.sessionSource.codebuddy');
82
+ return this.t('dashboard.sessionSource.all');
81
83
  },
82
84
  inspectorSessionPathLabel() {
83
85
  const value = typeof this.sessionPathFilter === 'string' ? this.sessionPathFilter.trim() : '';
84
- return value || '全部路径';
86
+ return value || this.t('dashboard.sessionPath.all');
85
87
  },
86
88
  inspectorSessionQueryLabel() {
87
- if (!this.isSessionQueryEnabled) return '当前来源不支持';
89
+ if (!this.isSessionQueryEnabled) return this.t('dashboard.sessionQuery.unsupported');
88
90
  const value = typeof this.sessionQuery === 'string' ? this.sessionQuery.trim() : '';
89
- return value || '未设置';
91
+ return value || this.t('dashboard.sessionQuery.unset');
90
92
  },
91
93
  inspectorHealthStatus() {
92
- if (this.initError) return '读取失败';
93
- if (this.loading) return '初始化中';
94
- return '正常';
94
+ if (this.initError) return this.t('dashboard.healthStatus.failRead');
95
+ if (this.loading) return this.t('dashboard.healthStatus.initializing');
96
+ return this.t('dashboard.healthStatus.ok');
95
97
  },
96
98
  inspectorHealthTone() {
97
99
  if (this.initError) return 'error';
@@ -100,12 +102,12 @@ export function createDashboardComputed() {
100
102
  },
101
103
  inspectorModelLoadStatus() {
102
104
  if (this.codexModelsLoading || this.claudeModelsLoading) {
103
- return '加载中';
105
+ return this.t('dashboard.modelStatus.loading');
104
106
  }
105
107
  if (this.modelsSource === 'error' || this.claudeModelsSource === 'error') {
106
- return '加载异常';
108
+ return this.t('dashboard.modelStatus.error');
107
109
  }
108
- return '正常';
110
+ return this.t('dashboard.modelStatus.ok');
109
111
  },
110
112
  installTroubleshootingTips() {
111
113
  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');
@@ -549,6 +549,23 @@ export function createSessionComputed() {
549
549
  ];
550
550
  },
551
551
 
552
+ usageCurrentSessionStats() {
553
+ const summary = this.sessionUsageCharts && this.sessionUsageCharts.summary
554
+ ? this.sessionUsageCharts.summary
555
+ : null;
556
+ if (!summary) return null;
557
+ const t = typeof this.t === 'function' ? this.t : null;
558
+ return {
559
+ apiDurationLabel: formatUsageDuration(summary.activeDurationMs || 0, { compact: true, lang: this.lang }),
560
+ totalDurationLabel: formatUsageDuration(summary.totalDurationMs || 0, { compact: true, lang: this.lang }),
561
+ tokenLabel: formatCompactUsageSummaryNumber(summary.totalTokens || 0),
562
+ label: t ? t('usage.currentSession.title') : '当前会话',
563
+ apiDurationText: t ? t('usage.currentSession.apiDuration') : 'API时长',
564
+ totalDurationText: t ? t('usage.currentSession.totalDuration') : '总时长',
565
+ tokenText: t ? t('usage.currentSession.tokens') : 'Token'
566
+ };
567
+ },
568
+
552
569
  sessionUsageDaily() {
553
570
  const baseBuckets = this.sessionUsageCharts && Array.isArray(this.sessionUsageCharts.buckets)
554
571
  ? this.sessionUsageCharts.buckets
@@ -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
  }