codexmate 0.0.42 → 0.0.44

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 (34) hide show
  1. package/README.md +2 -0
  2. package/README.zh.md +2 -0
  3. package/cli/claude-proxy.js +611 -14
  4. package/cli/update.js +77 -7
  5. package/cli.js +191 -23
  6. package/package.json +2 -2
  7. package/web-ui/app.js +39 -10
  8. package/web-ui/logic.claude.mjs +65 -2
  9. package/web-ui/logic.runtime.mjs +0 -7
  10. package/web-ui/modules/app.methods.agents.mjs +6 -6
  11. package/web-ui/modules/app.methods.claude-config.mjs +65 -49
  12. package/web-ui/modules/app.methods.codex-config.mjs +10 -10
  13. package/web-ui/modules/app.methods.index.mjs +1 -1
  14. package/web-ui/modules/app.methods.install.mjs +129 -1
  15. package/web-ui/modules/app.methods.openclaw-persist.mjs +1 -1
  16. package/web-ui/modules/app.methods.providers.mjs +17 -16
  17. package/web-ui/modules/app.methods.runtime.mjs +27 -21
  18. package/web-ui/modules/app.methods.session-actions.mjs +25 -20
  19. package/web-ui/modules/app.methods.session-timeline.mjs +0 -1
  20. package/web-ui/modules/app.methods.startup-claude.mjs +29 -3
  21. package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -0
  22. package/web-ui/modules/i18n/locales/en.mjs +65 -1
  23. package/web-ui/modules/i18n/locales/ja.mjs +65 -1
  24. package/web-ui/modules/i18n/locales/vi.mjs +77 -2
  25. package/web-ui/modules/i18n/locales/zh.mjs +66 -2
  26. package/web-ui/partials/index/layout-header.html +24 -0
  27. package/web-ui/partials/index/modals-basic.html +18 -1
  28. package/web-ui/partials/index/panel-config-claude.html +5 -2
  29. package/web-ui/partials/index/panel-config-codex.html +2 -1
  30. package/web-ui/res/web-ui-render.precompiled.js +115 -22
  31. package/web-ui/styles/controls-forms.css +5 -5
  32. package/web-ui/styles/layout-shell.css +145 -0
  33. package/web-ui/styles/responsive.css +12 -0
  34. package/web-ui/styles/titles-cards.css +10 -4
@@ -1,4 +1,5 @@
1
- export function createInstallMethods() {
1
+ export function createInstallMethods(options = {}) {
2
+ const { api } = options;
2
3
  return {
3
4
  normalizeInstallPackageManager(value) {
4
5
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
@@ -16,6 +17,133 @@ export function createInstallMethods() {
16
17
  return 'install';
17
18
  },
18
19
 
20
+ normalizePackageVersion(value) {
21
+ const normalized = typeof value === 'string' ? value.trim().replace(/^v/i, '') : '';
22
+ return /^\d+(?:\.\d+){0,2}(?:[-+][0-9A-Za-z.-]+)?$/.test(normalized) ? normalized : '';
23
+ },
24
+
25
+ comparePackageVersions(left, right) {
26
+ const normalizeParts = (value) => {
27
+ const normalized = this.normalizePackageVersion(value);
28
+ if (!normalized) return null;
29
+ return normalized.split(/[+-]/)[0].split('.').map((part) => Number.parseInt(part, 10) || 0);
30
+ };
31
+ const a = normalizeParts(left);
32
+ const b = normalizeParts(right);
33
+ if (!a || !b) return 0;
34
+ for (let i = 0; i < 3; i += 1) {
35
+ const diff = (a[i] || 0) - (b[i] || 0);
36
+ if (diff < 0) return -1;
37
+ if (diff > 0) return 1;
38
+ }
39
+ return 0;
40
+ },
41
+
42
+ isAppUpdateAvailable() {
43
+ const current = this.normalizePackageVersion(this.appVersion);
44
+ const latest = this.normalizePackageVersion(this.appLatestVersion);
45
+ if (!current || !latest) return false;
46
+ return this.comparePackageVersions(current, latest) < 0;
47
+ },
48
+
49
+ isAppVersionStatusVisible() {
50
+ return !!(this.appVersion || this.appLatestVersion || this.appVersionStatusLoading || this.appVersionStatusChecked || this.appVersionStatusError);
51
+ },
52
+
53
+ appVersionStatusKind() {
54
+ if (this.appVersionStatusLoading) return 'loading';
55
+ if (this.appVersionStatusError) return 'error';
56
+ if (this.isAppUpdateAvailable()) return 'available';
57
+ if (this.appVersionStatusChecked) return 'current';
58
+ return 'idle';
59
+ },
60
+
61
+ appUpdateNoticeText() {
62
+ if (this.appVersionStatusLoading) return this.t('side.update.checking');
63
+ if (this.appVersionStatusError) return this.t('side.update.retry');
64
+ const latest = this.normalizePackageVersion(this.appLatestVersion);
65
+ if (this.isAppUpdateAvailable()) return latest
66
+ ? this.t('side.update.availableWithVersion', { version: latest })
67
+ : this.t('side.update.available');
68
+ if (this.appVersionStatusChecked) return this.t('side.update.upToDate');
69
+ return this.t('side.update.check');
70
+ },
71
+
72
+ appUpdateNoticeMeta() {
73
+ if (this.appVersionStatusLoading) return this.t('side.update.checkingMeta');
74
+ if (this.appVersionStatusError) return this.appVersionStatusError;
75
+ const current = this.normalizePackageVersion(this.appVersion);
76
+ const latest = this.normalizePackageVersion(this.appLatestVersion);
77
+ if (current && latest) {
78
+ return this.t('side.update.metaVersions', { current, latest });
79
+ }
80
+ if (current) {
81
+ return this.t('side.update.currentOnly', { current });
82
+ }
83
+ return this.t('side.update.meta');
84
+ },
85
+
86
+ appVersionStatusTitle() {
87
+ const source = typeof this.appVersionStatusSource === 'string' ? this.appVersionStatusSource.trim() : '';
88
+ const checkedAt = typeof this.appVersionStatusCheckedAt === 'string' ? this.appVersionStatusCheckedAt.trim() : '';
89
+ const suffix = [source, checkedAt].filter(Boolean).join(' · ');
90
+ const meta = this.appUpdateNoticeMeta();
91
+ return suffix ? `${meta} · ${suffix}` : meta;
92
+ },
93
+
94
+ handleAppVersionStatusClick() {
95
+ if (this.isAppUpdateAvailable()) {
96
+ this.openAppUpdateDocs();
97
+ return;
98
+ }
99
+ void this.loadAppVersionStatus({ silent: false, force: true });
100
+ },
101
+
102
+ async loadAppVersionStatus(options = {}) {
103
+ if (typeof api !== 'function') return false;
104
+ if (this.appVersionStatusLoading) return false;
105
+ this.appVersionStatusLoading = true;
106
+ this.appVersionStatusError = '';
107
+ try {
108
+ const res = await api('version-status', options.force ? { force: true } : {});
109
+ if (res && res.currentVersion && !this.appVersion) {
110
+ this.appVersion = this.normalizePackageVersion(res.currentVersion) || String(res.currentVersion || '');
111
+ }
112
+ if (res && res.latestVersion) {
113
+ this.appLatestVersion = this.normalizePackageVersion(res.latestVersion) || String(res.latestVersion || '');
114
+ }
115
+ if (res && typeof res.source === 'string') {
116
+ this.appVersionStatusSource = res.source;
117
+ }
118
+ if (res && typeof res.checkedAt === 'string') {
119
+ this.appVersionStatusCheckedAt = res.checkedAt;
120
+ }
121
+ if (res && res.error) {
122
+ this.appVersionStatusError = res.error;
123
+ this.appVersionStatusChecked = true;
124
+ if (!options.silent) this.showMessage(res.error, 'error');
125
+ return false;
126
+ }
127
+ this.appVersionStatusChecked = true;
128
+ return true;
129
+ } catch (e) {
130
+ const message = e && e.message ? e.message : this.t('side.update.checkFailed');
131
+ this.appVersionStatusError = message;
132
+ this.appVersionStatusChecked = true;
133
+ if (!options.silent) this.showMessage(message, 'error');
134
+ return false;
135
+ } finally {
136
+ this.appVersionStatusLoading = false;
137
+ }
138
+ },
139
+
140
+ openAppUpdateDocs() {
141
+ this.installCommandAction = 'update';
142
+ if (typeof this.switchMainTab === 'function') {
143
+ this.switchMainTab('docs');
144
+ }
145
+ },
146
+
19
147
  normalizeInstallRegistryPreset(value) {
20
148
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
21
149
  if (normalized === 'default' || normalized === 'npmmirror' || normalized === 'tencent' || normalized === 'custom') {
@@ -274,7 +274,7 @@ export function createOpenclawPersistMethods(options = {}) {
274
274
  try {
275
275
  const name = this.persistOpenclawConfig();
276
276
  if (!name) return;
277
- this.showMessage('操作成功', 'success');
277
+ this.showMessage(this.t('toast.operation.success'), 'success');
278
278
  } finally {
279
279
  this.openclawSaving = false;
280
280
  }
@@ -170,7 +170,7 @@ export function createProvidersMethods(options = {}) {
170
170
  normalizeProviderDraftState(this.newProvider);
171
171
  const validation = getProviderValidationForContext(this, 'add');
172
172
  if (!validation.ok) {
173
- return this.showMessage(validation.errors.name || validation.errors.url || validation.errors.key || validation.errors.model || '名称、URL、API Key 和模型名称必填', 'error');
173
+ return this.showMessage(validation.errors.name || validation.errors.url || validation.errors.key || validation.errors.model || this.t('toast.provider.fieldsRequired'), 'error');
174
174
  }
175
175
 
176
176
  try {
@@ -206,7 +206,7 @@ export function createProvidersMethods(options = {}) {
206
206
  };
207
207
  this.providersList = [...this.providersList, newProvider];
208
208
 
209
- this.showMessage('操作成功', 'success');
209
+ this.showMessage(this.t('toast.operation.success'), 'success');
210
210
  this.closeAddModal();
211
211
 
212
212
  if (suggestedModel) {
@@ -214,7 +214,7 @@ export function createProvidersMethods(options = {}) {
214
214
  this.currentModels[validation.name] = suggestedModel;
215
215
  }
216
216
  } catch (e) {
217
- this.showMessage('添加失败', 'error');
217
+ this.showMessage(this.t('toast.provider.addFail'), 'error');
218
218
  }
219
219
  },
220
220
 
@@ -275,7 +275,7 @@ export function createProvidersMethods(options = {}) {
275
275
 
276
276
  async deleteProvider(name) {
277
277
  if (this.isNonDeletableProvider(name)) {
278
- this.showMessage('provider 为保留项,不可删除', 'info');
278
+ this.showMessage(this.t('toast.provider.notDeletable'), 'info');
279
279
  return;
280
280
  }
281
281
  try {
@@ -301,12 +301,13 @@ export function createProvidersMethods(options = {}) {
301
301
  ...p,
302
302
  current: p.name === res.provider
303
303
  }));
304
- this.showMessage(`已删除提供商,自动切换到 ${res.provider}${res.model ? ` / ${res.model}` : ''}`, 'success');
304
+ const modelSuffix = res.model ? ` / ${res.model}` : '';
305
+ this.showMessage(this.t('toast.provider.deletedAndSwitched', { provider: res.provider, model: modelSuffix }), 'success');
305
306
  } else {
306
- this.showMessage('操作成功', 'success');
307
+ this.showMessage(this.t('toast.operation.success'), 'success');
307
308
  }
308
309
  } catch (_) {
309
- this.showMessage('删除失败', 'error');
310
+ this.showMessage(this.t('toast.delete.fail'), 'error');
310
311
  }
311
312
  },
312
313
 
@@ -330,7 +331,7 @@ export function createProvidersMethods(options = {}) {
330
331
  const requestId = Symbol('openEditModal');
331
332
  this._openEditModalRequestId = requestId;
332
333
  if (!this.shouldShowProviderEdit(provider)) {
333
- this.showMessage('provider 为保留项,不可编辑', 'info');
334
+ this.showMessage(this.t('toast.provider.notEditable'), 'info');
334
335
  return;
335
336
  }
336
337
  const isTransformProvider = (() => {
@@ -398,14 +399,14 @@ export function createProvidersMethods(options = {}) {
398
399
 
399
400
  async updateProvider() {
400
401
  if (this.editingProvider.readOnly || this.editingProvider.nonEditable) {
401
- this.showMessage('provider 为保留项,不可编辑', 'error');
402
+ this.showMessage(this.t('toast.provider.notEditable'), 'error');
402
403
  this.closeEditModal();
403
404
  return;
404
405
  }
405
406
  normalizeProviderDraftState(this.editingProvider);
406
407
  const validation = getProviderValidationForContext(this, 'edit');
407
408
  if (!validation.ok) {
408
- return this.showMessage(validation.errors.name || validation.errors.url || 'URL 必填', 'error');
409
+ return this.showMessage(validation.errors.name || validation.errors.url || this.t('toast.provider.urlRequired'), 'error');
409
410
  }
410
411
 
411
412
  const params = { name: validation.name, url: validation.url };
@@ -441,9 +442,9 @@ export function createProvidersMethods(options = {}) {
441
442
  });
442
443
 
443
444
  this.closeEditModal();
444
- this.showMessage('操作成功', 'success');
445
+ this.showMessage(this.t('toast.operation.success'), 'success');
445
446
  } catch (e) {
446
- this.showMessage('更新失败', 'error');
447
+ this.showMessage(this.t('toast.provider.updateFail'), 'error');
447
448
  }
448
449
  },
449
450
 
@@ -469,10 +470,10 @@ export function createProvidersMethods(options = {}) {
469
470
  return;
470
471
  }
471
472
  const backup = res.backupFile ? `(已备份: ${res.backupFile})` : '';
472
- this.showMessage(`配置已重装${backup}`, 'success');
473
+ this.showMessage(this.t('toast.provider.resetSuccess', { backup }), 'success');
473
474
  await this.loadAll();
474
475
  } catch (e) {
475
- this.showMessage('重装失败', 'error');
476
+ this.showMessage(this.t('toast.provider.resetFail'), 'error');
476
477
  } finally {
477
478
  this.resetConfigLoading = false;
478
479
  }
@@ -501,7 +502,7 @@ export function createProvidersMethods(options = {}) {
501
502
  }
502
503
  return p;
503
504
  });
504
- this.showMessage('操作成功', 'success');
505
+ this.showMessage(this.t('toast.operation.success'), 'success');
505
506
  this.closeModelModal();
506
507
  }
507
508
  } catch (_) {
@@ -525,7 +526,7 @@ export function createProvidersMethods(options = {}) {
525
526
  }
526
527
  return p;
527
528
  });
528
- this.showMessage('操作成功', 'success');
529
+ this.showMessage(this.t('toast.operation.success'), 'success');
529
530
  }
530
531
  } catch (_) {
531
532
  this.showMessage('删除模型失败', 'error');
@@ -4,21 +4,21 @@ import {
4
4
  } from '../logic.mjs';
5
5
 
6
6
  const UI_MESSAGE_KEY_BY_TEXT = Object.freeze({
7
- '操作成功': 'toast.operationSuccess',
8
- '操作失败': 'toast.operationFailed',
9
- '添加失败': 'toast.addFailed',
10
- '更新失败': 'toast.updateFailed',
11
- '删除失败': 'toast.deleteFailed',
12
- '已删除': 'toast.deleted',
13
- '已复制': 'toast.copied',
14
- '复制失败': 'toast.copyFailed',
7
+ '操作成功': 'toast.operation.success',
8
+ '操作失败': 'toast.operation.fail',
9
+ '添加失败': 'toast.provider.addFail',
10
+ '更新失败': 'toast.provider.updateFail',
11
+ '删除失败': 'toast.delete.fail',
12
+ '已删除': 'toast.delete.ok',
13
+ '已复制': 'toast.copy.ok',
14
+ '复制失败': 'toast.copy.fail',
15
15
  '剪贴板为空': 'toast.clipboardEmpty',
16
16
  '无法读取剪贴板': 'toast.clipboardReadFailed',
17
17
  '已粘贴': 'toast.pasted',
18
18
  '未检测到改动': 'toast.noChanges',
19
- '配置已应用': 'toast.configApplied',
20
- '应用配置失败': 'toast.applyConfigFailed',
21
- '应用失败': 'toast.applyFailed',
19
+ '配置已应用': 'toast.apply.success',
20
+ '应用配置失败': 'toast.apply.fail',
21
+ '应用失败': 'toast.apply.fail',
22
22
  '配置已加载': 'toast.configLoaded',
23
23
  '配置就绪': 'toast.configReady',
24
24
  '加载配置失败': 'toast.loadConfigFailed',
@@ -26,12 +26,12 @@ const UI_MESSAGE_KEY_BY_TEXT = Object.freeze({
26
26
  '读取配置超时': 'toast.readConfigTimeout',
27
27
  '备份失败': 'toast.backupFailed',
28
28
  '备份成功,开始下载': 'toast.backupReadyDownload',
29
- '导入失败': 'toast.importFailed',
30
- '导入成功': 'toast.importSuccess',
29
+ '导入失败': 'toast.import.fail',
30
+ '导入成功': 'toast.import.ok',
31
31
  '导入 skill 失败': 'toast.importSkillFailed',
32
- '导出失败': 'toast.exportFailed',
33
- '保存失败': 'toast.saveFailed',
34
- '加载文件失败': 'toast.loadFileFailed',
32
+ '导出失败': 'toast.export.fail',
33
+ '保存失败': 'toast.save.fail',
34
+ '加载文件失败': 'toast.load.fail',
35
35
  '请填写名称': 'toast.nameRequired',
36
36
  '请输入名称': 'toast.nameRequired',
37
37
  '名称已存在': 'toast.nameExists',
@@ -45,8 +45,8 @@ const UI_MESSAGE_KEY_BY_TEXT = Object.freeze({
45
45
  '不可分享': 'toast.notShareable',
46
46
  '已移入回收站': 'toast.movedToTrash',
47
47
  '生成命令失败': 'toast.commandGenerationFailed',
48
- '没有可复制内容': 'toast.nothingToCopy',
49
- '没有可导出内容': 'toast.nothingToExport',
48
+ '没有可复制内容': 'toast.copy.empty',
49
+ '没有可导出内容': 'toast.export.empty',
50
50
  '会话已恢复': 'toast.sessionRestored',
51
51
  '恢复失败': 'toast.restoreFailed',
52
52
  '已彻底删除': 'toast.purged',
@@ -66,16 +66,22 @@ const UI_MESSAGE_PREFIX_ENTRIES = Object.freeze(
66
66
  Object.entries(UI_MESSAGE_KEY_BY_TEXT).sort((a, b) => b[0].length - a[0].length)
67
67
  );
68
68
 
69
- function translateUiMessage(context, text) {
69
+ export function translateUiMessage(context, text) {
70
70
  if (!context || typeof context.t !== 'function' || typeof text !== 'string') return text;
71
+ const translateKey = (key) => {
72
+ const translated = context.t(key);
73
+ return typeof translated === 'string' && translated && translated !== key ? translated : '';
74
+ };
71
75
  const exactKey = UI_MESSAGE_KEY_BY_TEXT[text];
72
- if (exactKey) return context.t(exactKey);
76
+ if (exactKey) return translateKey(exactKey) || text;
73
77
  const prefixEntry = UI_MESSAGE_PREFIX_ENTRIES.find(([sourceText]) => {
74
78
  return text.length > sourceText.length && text.startsWith(sourceText);
75
79
  });
76
80
  if (!prefixEntry) return text;
77
81
  const [sourceText, key] = prefixEntry;
78
- return `${context.t(key)}${text.slice(sourceText.length)}`;
82
+ const translatedPrefix = translateKey(key);
83
+ if (!translatedPrefix) return text;
84
+ return `${translatedPrefix}${text.slice(sourceText.length)}`;
79
85
  }
80
86
 
81
87
  function clearProgressResetTimer(context, timerKey) {
@@ -126,7 +126,7 @@ export function createSessionActionMethods(options = {}) {
126
126
  return;
127
127
  }
128
128
  } catch (_) {}
129
- this.showMessage('复制失败', 'error');
129
+ this.showMessage(this.t('toast.copy.fail'), 'error');
130
130
  },
131
131
 
132
132
  getSessionFilePath(session) {
@@ -152,7 +152,7 @@ export function createSessionActionMethods(options = {}) {
152
152
  return;
153
153
  }
154
154
  } catch (_) {}
155
- this.showMessage('复制失败', 'error');
155
+ this.showMessage(this.t('toast.copy.fail'), 'error');
156
156
  },
157
157
 
158
158
  getSessionExportKey(session) {
@@ -312,15 +312,15 @@ export function createSessionActionMethods(options = {}) {
312
312
  copyAgentsContent() {
313
313
  const text = typeof this.agentsContent === 'string' ? this.agentsContent : '';
314
314
  if (!text) {
315
- this.showMessage('没有可复制内容', 'info');
315
+ this.showMessage(this.t('toast.copy.empty'), 'info');
316
316
  return;
317
317
  }
318
318
  const ok = this.fallbackCopyText(text);
319
319
  if (ok) {
320
- this.showMessage('已复制', 'success');
320
+ this.showMessage(this.t('toast.copy.ok'), 'success');
321
321
  return;
322
322
  }
323
- this.showMessage('复制失败', 'error');
323
+ this.showMessage(this.t('toast.copy.fail'), 'error');
324
324
  },
325
325
 
326
326
  exportAgentsContent() {
@@ -344,7 +344,7 @@ export function createSessionActionMethods(options = {}) {
344
344
  async copyInstallCommand(cmd) {
345
345
  const text = typeof cmd === 'string' ? cmd.trim() : '';
346
346
  if (!text) {
347
- this.showMessage('没有可复制内容', 'info');
347
+ this.showMessage(this.t('toast.copy.empty'), 'info');
348
348
  return;
349
349
  }
350
350
  try {
@@ -359,7 +359,7 @@ export function createSessionActionMethods(options = {}) {
359
359
  this.showMessage('已复制命令', 'success');
360
360
  return;
361
361
  }
362
- this.showMessage('复制失败', 'error');
362
+ this.showMessage(this.t('toast.copy.fail'), 'error');
363
363
  },
364
364
 
365
365
  async copyResumeCommand(session) {
@@ -370,17 +370,17 @@ export function createSessionActionMethods(options = {}) {
370
370
  const command = this.buildResumeCommand(session);
371
371
  const ok = this.fallbackCopyText(command);
372
372
  if (ok) {
373
- this.showMessage('已复制', 'success');
373
+ this.showMessage(this.t('toast.copy.ok'), 'success');
374
374
  return;
375
375
  }
376
376
  try {
377
377
  if (navigator.clipboard && window.isSecureContext) {
378
378
  await navigator.clipboard.writeText(command);
379
- this.showMessage('已复制', 'success');
379
+ this.showMessage(this.t('toast.copy.ok'), 'success');
380
380
  return;
381
381
  }
382
382
  } catch (_) {}
383
- this.showMessage('复制失败', 'error');
383
+ this.showMessage(this.t('toast.copy.fail'), 'error');
384
384
  },
385
385
 
386
386
  buildProviderShareCommand(payload) {
@@ -412,11 +412,16 @@ export function createSessionActionMethods(options = {}) {
412
412
  const model = typeof payload.model === 'string' && payload.model.trim()
413
413
  ? payload.model.trim()
414
414
  : 'glm-4.7';
415
- if (!baseUrl || !apiKey) return '';
415
+ const targetApiRaw = typeof payload.targetApi === 'string' ? payload.targetApi.trim().toLowerCase() : '';
416
+ const targetApi = targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions'
417
+ ? 'chat_completions'
418
+ : (targetApiRaw === 'ollama' ? 'ollama' : 'responses');
419
+ if (!baseUrl || (!apiKey && targetApi !== 'ollama')) return '';
416
420
  const urlArg = this.quoteShellArg(baseUrl);
417
421
  const keyArg = this.quoteShellArg(apiKey);
418
422
  const modelArg = this.quoteShellArg(model);
419
- return `${this.getShareCommandPrefixInvocation()} claude ${urlArg} ${keyArg} ${modelArg}`;
423
+ const targetArg = targetApi !== 'responses' ? ` --target-api ${this.quoteShellArg(targetApi)}` : '';
424
+ return `${this.getShareCommandPrefixInvocation()} claude ${urlArg} ${keyArg} ${modelArg}${targetArg}`;
420
425
  },
421
426
 
422
427
  async copyProviderShareCommand(provider) {
@@ -446,17 +451,17 @@ export function createSessionActionMethods(options = {}) {
446
451
  }
447
452
  const ok = this.fallbackCopyText(command);
448
453
  if (ok) {
449
- this.showMessage('已复制', 'success');
454
+ this.showMessage(this.t('toast.copy.ok'), 'success');
450
455
  return;
451
456
  }
452
457
  try {
453
458
  if (navigator.clipboard && window.isSecureContext) {
454
459
  await navigator.clipboard.writeText(command);
455
- this.showMessage('已复制', 'success');
460
+ this.showMessage(this.t('toast.copy.ok'), 'success');
456
461
  return;
457
462
  }
458
463
  } catch (_) {}
459
- this.showMessage('复制失败', 'error');
464
+ this.showMessage(this.t('toast.copy.fail'), 'error');
460
465
  } catch (_) {
461
466
  this.showMessage('生成命令失败', 'error');
462
467
  } finally {
@@ -485,17 +490,17 @@ export function createSessionActionMethods(options = {}) {
485
490
  }
486
491
  const ok = this.fallbackCopyText(command);
487
492
  if (ok) {
488
- this.showMessage('已复制', 'success');
493
+ this.showMessage(this.t('toast.copy.ok'), 'success');
489
494
  return;
490
495
  }
491
496
  try {
492
497
  if (navigator.clipboard && window.isSecureContext) {
493
498
  await navigator.clipboard.writeText(command);
494
- this.showMessage('已复制', 'success');
499
+ this.showMessage(this.t('toast.copy.ok'), 'success');
495
500
  return;
496
501
  }
497
502
  } catch (_) {}
498
- this.showMessage('复制失败', 'error');
503
+ this.showMessage(this.t('toast.copy.fail'), 'error');
499
504
  } catch (_) {
500
505
  this.showMessage('生成命令失败', 'error');
501
506
  } finally {
@@ -524,7 +529,7 @@ export function createSessionActionMethods(options = {}) {
524
529
  return;
525
530
  }
526
531
 
527
- this.showMessage('操作成功', 'success');
532
+ this.showMessage(this.t('toast.operation.success'), 'success');
528
533
  if (typeof this.invalidateSessionsUsageData === 'function') {
529
534
  this.invalidateSessionsUsageData({ preserveList: true });
530
535
  }
@@ -608,7 +613,7 @@ export function createSessionActionMethods(options = {}) {
608
613
  // The delete already succeeded remotely; keep the success result.
609
614
  }
610
615
  } catch (_) {
611
- this.showMessage('删除失败', 'error');
616
+ this.showMessage(this.t('toast.delete.fail'), 'error');
612
617
  } finally {
613
618
  this.sessionDeleting[key] = false;
614
619
  }
@@ -354,7 +354,6 @@ export function createSessionTimelineMethods() {
354
354
  shortEdge,
355
355
  maxTouchPoints: touchPoints,
356
356
  userAgent,
357
- isMobileUa,
358
357
  coarsePointer,
359
358
  noHover
360
359
  });
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  findDuplicateClaudeConfigName,
3
3
  getClaudeModelCatalogForBaseUrl,
4
+ isLikelyBuiltinClaudeProxySettingsEnv,
5
+ matchBuiltinClaudeProxyConfigFromSettings,
4
6
  matchClaudeConfigFromSettings,
5
7
  normalizeClaudeConfig,
6
8
  normalizeClaudeSettingsEnv,
@@ -125,6 +127,9 @@ export function createStartupClaudeMethods(options = {}) {
125
127
  codex: statusRes.toolConfigPermissions.codex === true,
126
128
  claude: statusRes.toolConfigPermissions.claude === true
127
129
  };
130
+ try {
131
+ localStorage.setItem('toolConfigPermissions', JSON.stringify(this.toolConfigPermissions));
132
+ } catch (_) {}
128
133
  }
129
134
  this.providersList = listRes.providers;
130
135
  if (typeof this.loadLocalBridgeExcluded === 'function') { this.loadLocalBridgeExcluded(); }
@@ -238,6 +243,14 @@ export function createStartupClaudeMethods(options = {}) {
238
243
  return matchClaudeConfigFromSettings(this.claudeConfigs, env);
239
244
  },
240
245
 
246
+ matchBuiltinClaudeProxyConfigFromSettings(env) {
247
+ return matchBuiltinClaudeProxyConfigFromSettings(this.claudeConfigs, env, this.currentClaudeConfig);
248
+ },
249
+
250
+ shouldSuppressClaudeSettingsImport(env) {
251
+ return isLikelyBuiltinClaudeProxySettingsEnv(env);
252
+ },
253
+
241
254
  findDuplicateClaudeConfigName(config) {
242
255
  return findDuplicateClaudeConfigName(this.claudeConfigs, config);
243
256
  },
@@ -253,7 +266,8 @@ export function createStartupClaudeMethods(options = {}) {
253
266
  baseUrl: next.baseUrl,
254
267
  model: next.model || previous.model || 'glm-4.7',
255
268
  hasKey: !!(next.apiKey || externalCredentialType),
256
- externalCredentialType
269
+ externalCredentialType,
270
+ targetApi: next.targetApi || previous.targetApi || 'responses'
257
271
  };
258
272
  },
259
273
 
@@ -329,7 +343,8 @@ export function createStartupClaudeMethods(options = {}) {
329
343
  }
330
344
  return;
331
345
  }
332
- const matchName = this.matchClaudeConfigFromSettings((res && res.env) || {});
346
+ const settingsEnv = (res && res.env) || {};
347
+ const matchName = this.matchClaudeConfigFromSettings(settingsEnv);
333
348
  if (matchName) {
334
349
  if (this.currentClaudeConfig !== matchName) {
335
350
  this.currentClaudeConfig = matchName;
@@ -338,7 +353,18 @@ export function createStartupClaudeMethods(options = {}) {
338
353
  this.refreshClaudeModelContext({ silentError: silentModelError });
339
354
  return;
340
355
  }
341
- const importedName = this.ensureClaudeConfigFromSettings((res && res.env) || {});
356
+ const builtinProxyMatch = this.matchBuiltinClaudeProxyConfigFromSettings(settingsEnv);
357
+ if (builtinProxyMatch) {
358
+ if (this.currentClaudeConfig !== builtinProxyMatch) {
359
+ this.currentClaudeConfig = builtinProxyMatch;
360
+ try { localStorage.setItem('currentClaudeConfig', builtinProxyMatch); } catch (_) {}
361
+ }
362
+ this.refreshClaudeModelContext({ silentError: silentModelError });
363
+ return;
364
+ }
365
+ const importedName = this.shouldSuppressClaudeSettingsImport(settingsEnv)
366
+ ? ''
367
+ : this.ensureClaudeConfigFromSettings(settingsEnv);
342
368
  if (importedName) {
343
369
  if (this.currentClaudeConfig !== importedName) {
344
370
  this.currentClaudeConfig = importedName;
@@ -64,6 +64,9 @@ export function createToolConfigPermissionMethods(options = {}) {
64
64
  return;
65
65
  }
66
66
  this.toolConfigPermissions = normalizePermissions(res && res.permissions);
67
+ try {
68
+ localStorage.setItem('toolConfigPermissions', JSON.stringify(this.toolConfigPermissions));
69
+ } catch (_) {}
67
70
  this.showMessage(
68
71
  nextAllowWrite
69
72
  ? this.t('toolConfig.allowToast')