codexmate 0.0.55 → 0.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) 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 +117 -5
  6. package/package.json +1 -1
  7. package/web-ui/app.js +2 -0
  8. package/web-ui/modules/app.computed.session.mjs +210 -0
  9. package/web-ui/modules/app.methods.claude-config.mjs +128 -12
  10. package/web-ui/modules/app.methods.codex-config.mjs +294 -65
  11. package/web-ui/modules/app.methods.navigation.mjs +4 -1
  12. package/web-ui/modules/app.methods.providers.mjs +15 -1
  13. package/web-ui/modules/app.methods.runtime.mjs +7 -2
  14. package/web-ui/modules/app.methods.session-actions.mjs +24 -0
  15. package/web-ui/modules/app.methods.startup-claude.mjs +34 -1
  16. package/web-ui/modules/i18n/locales/en.mjs +41 -2
  17. package/web-ui/modules/i18n/locales/ja.mjs +41 -2
  18. package/web-ui/modules/i18n/locales/vi.mjs +41 -8
  19. package/web-ui/modules/i18n/locales/zh-tw.mjs +41 -2
  20. package/web-ui/modules/i18n/locales/zh.mjs +41 -2
  21. package/web-ui/modules/provider-default-names.mjs +25 -0
  22. package/web-ui/partials/index/modal-health-check.html +69 -5
  23. package/web-ui/partials/index/panel-config-codex.html +2 -2
  24. package/web-ui/partials/index/panel-sessions.html +97 -17
  25. package/web-ui/res/web-ui-render.precompiled.js +345 -93
  26. package/web-ui/session-helpers.mjs +4 -1
  27. package/web-ui/styles/responsive.css +98 -0
  28. package/web-ui/styles/sessions-preview.css +212 -2
  29. package/web-ui/styles/sessions-toolbar-trash.css +61 -18
  30. package/web-ui/styles/skills-list.css +122 -0
  31. package/web-ui/styles/titles-cards.css +52 -0
@@ -51,6 +51,190 @@ function formatUsageRangeLabel(range, t) {
51
51
  return '近 7 天';
52
52
  }
53
53
 
54
+ function normalizeWorkspaceText(value) {
55
+ if (value == null) {
56
+ return '';
57
+ }
58
+ return String(value)
59
+ .replace(/<[^>]*>/g, ' ')
60
+ .replace(/&nbsp;/gi, ' ')
61
+ .replace(/&lt;/gi, '<')
62
+ .replace(/&gt;/gi, '>')
63
+ .replace(/&amp;/gi, '&')
64
+ .replace(/\s+/g, ' ')
65
+ .trim();
66
+ }
67
+
68
+ function trimWorkspaceLine(value, maxLength = 160) {
69
+ const text = normalizeWorkspaceText(value);
70
+ if (!text) return '';
71
+ const limit = Number.isFinite(Number(maxLength)) ? Math.max(20, Math.floor(Number(maxLength))) : 160;
72
+ return text.length > limit ? `${text.slice(0, Math.max(0, limit - 1)).trimEnd()}…` : text;
73
+ }
74
+
75
+ function pushUniqueWorkspaceItem(items, value, limit = 6, maxLength = 160) {
76
+ if (!Array.isArray(items) || items.length >= limit) return;
77
+ const text = trimWorkspaceLine(value, maxLength);
78
+ if (!text) return;
79
+ const key = text.toLowerCase();
80
+ if (items.some(item => String(item || '').toLowerCase() === key)) return;
81
+ items.push(text);
82
+ }
83
+
84
+ function extractWorkspaceLines(text) {
85
+ return String(text || '')
86
+ .split(/\r?\n|(?:^|\s)(?:[-*•]|\d+[.)])\s+|[。;;]\s*/g)
87
+ .map(line => normalizeWorkspaceText(line))
88
+ .filter(Boolean);
89
+ }
90
+
91
+ function isWorkspaceCommandLine(line) {
92
+ return /^\$\s+/.test(line)
93
+ || /^(?:npm|pnpm|yarn|bun|node|npx|git|gh|codex|claude|gemini|codebuddy|openclaw|pytest|go test|cargo test|make|docker)\b/i.test(line);
94
+ }
95
+
96
+ function extractInlineWorkspaceCommands(text) {
97
+ const source = normalizeWorkspaceText(text);
98
+ if (!source) return [];
99
+ const matches = source.match(/(?:^|[^A-Za-z0-9_])(?:\$\s*)?(?:(?:npm|pnpm|yarn|bun|node|npx|git|gh|codex|claude|gemini|codebuddy|openclaw|pytest|make|docker)\s+(?:run\s+)?[A-Za-z0-9:_./@-]+(?:\s+&&\s+(?:npm|pnpm|yarn|bun|node|npx|git|gh|codex|claude|gemini|codebuddy|openclaw|pytest|make|docker)\s+(?:run\s+)?[A-Za-z0-9:_./@-]+)?|go\s+test(?:\s+[A-Za-z0-9:_./@-]+)?|cargo\s+test(?:\s+[A-Za-z0-9:_./@-]+)?)(?=$|\s|[,。;,;])/gi) || [];
100
+ return matches.map(match => match.replace(/^[^A-Za-z0-9_$]*\$?\s*/, '').trim());
101
+ }
102
+
103
+ function extractWorkspaceArtifacts(line, target) {
104
+ if (!target || typeof line !== 'string') return;
105
+ const urlMatches = line.match(/https?:\/\/[^\s)\]"'<>,。;、]+/gi) || [];
106
+ for (const url of urlMatches) {
107
+ pushUniqueWorkspaceItem(target.links, url.replace(/[,。;、.,;:!?]+$/g, ''), 5, 140);
108
+ }
109
+ const fileMatches = line.match(/(?:[A-Za-z]:[\\/]|\/)?(?:[A-Za-z0-9_.@+-]+[\\/])*[A-Za-z0-9_.@+-]+\.(?:mjs|js|cjs|ts|tsx|jsx|vue|css|scss|html|json|jsonl|md|yml|yaml|toml|lock|py|go|rs|sh|txt)/g) || [];
110
+ for (const filePath of fileMatches) {
111
+ pushUniqueWorkspaceItem(target.files, filePath, 8, 120);
112
+ }
113
+ }
114
+
115
+ function buildWorkspaceBriefText(summary, labels = {}) {
116
+ if (!summary || !summary.available) return '';
117
+ const label = (key, fallback) => (labels && labels[key]) || fallback;
118
+ const lines = [
119
+ `# ${label('title', 'Session workspace brief')}`,
120
+ '',
121
+ `${label('source', 'Source')}: ${summary.sourceLabel || summary.source || '-'}`,
122
+ `${label('messages', 'Messages')}: ${summary.messageCount}`,
123
+ `${label('path', 'Path')}: ${summary.cwd || '-'}`,
124
+ ''
125
+ ];
126
+ const sections = [
127
+ ['signals', label('signals', 'Signals'), summary.signals],
128
+ ['commands', label('commands', 'Reusable commands'), summary.commands],
129
+ ['files', label('files', 'Files'), summary.files],
130
+ ['links', label('links', 'Links'), summary.links],
131
+ ['risks', label('risks', 'Risks / blockers'), summary.risks],
132
+ ['nextSteps', label('nextSteps', 'Next steps'), summary.nextSteps]
133
+ ];
134
+ for (const [, sectionTitle, items] of sections) {
135
+ if (!Array.isArray(items) || !items.length) continue;
136
+ lines.push(`## ${sectionTitle}`);
137
+ for (const item of items) {
138
+ lines.push(`- ${item}`);
139
+ }
140
+ lines.push('');
141
+ }
142
+ return lines.join('\n').trimEnd();
143
+ }
144
+
145
+ export function buildSessionWorkspaceSummary(session, messages, options = {}) {
146
+ const safeMessages = Array.isArray(messages) ? messages : [];
147
+ const roleCounts = { user: 0, assistant: 0, system: 0, other: 0 };
148
+ const signals = [];
149
+ const commands = [];
150
+ const files = [];
151
+ const links = [];
152
+ const risks = [];
153
+ const nextSteps = [];
154
+ const target = { files, links };
155
+ let firstUser = '';
156
+ let lastUser = '';
157
+ let lastAssistant = '';
158
+ let firstTimestamp = '';
159
+ let lastTimestamp = '';
160
+
161
+ safeMessages.forEach((message) => {
162
+ if (!message || typeof message !== 'object') return;
163
+ const normalizedRole = String(message.normalizedRole || message.role || '').trim().toLowerCase();
164
+ const role = normalizedRole === 'user' || normalizedRole === 'assistant' || normalizedRole === 'system'
165
+ ? normalizedRole
166
+ : 'other';
167
+ roleCounts[role] += 1;
168
+ const timestamp = normalizeWorkspaceText(message.timestamp || '');
169
+ if (timestamp && !firstTimestamp) firstTimestamp = timestamp;
170
+ if (timestamp) lastTimestamp = timestamp;
171
+ const text = normalizeWorkspaceText(message.text || message.content || '');
172
+ if (!text) return;
173
+ if (role === 'user') {
174
+ if (!firstUser) firstUser = text;
175
+ lastUser = text;
176
+ } else if (role === 'assistant') {
177
+ lastAssistant = text;
178
+ }
179
+ const lines = extractWorkspaceLines(message.text || message.content || text);
180
+ for (const command of extractInlineWorkspaceCommands(message.text || message.content || text)) {
181
+ pushUniqueWorkspaceItem(commands, command, 6, 140);
182
+ }
183
+ for (const line of lines) {
184
+ extractWorkspaceArtifacts(line, target);
185
+ if (isWorkspaceCommandLine(line)) {
186
+ pushUniqueWorkspaceItem(commands, line.replace(/^\$\s+/, ''), 6, 140);
187
+ }
188
+ if (/(?:blocker|blocked|risk|failed|failure|error|timeout|regression|冲突|失败|错误|阻塞|风险|不可合|超时|回归)/i.test(line)) {
189
+ pushUniqueWorkspaceItem(risks, line, 5, 150);
190
+ }
191
+ if (/(?:todo|next|follow[- ]?up|remaining|后续|下一步|待办|继续|剩余)/i.test(line)) {
192
+ pushUniqueWorkspaceItem(nextSteps, line, 5, 150);
193
+ }
194
+ }
195
+ });
196
+
197
+ pushUniqueWorkspaceItem(signals, firstUser, 3, 150);
198
+ if (lastUser && lastUser !== firstUser) {
199
+ pushUniqueWorkspaceItem(signals, lastUser, 3, 150);
200
+ }
201
+ pushUniqueWorkspaceItem(signals, lastAssistant, 3, 150);
202
+
203
+ const source = session && typeof session.source === 'string' ? session.source : '';
204
+ const sourceLabel = session && typeof session.sourceLabel === 'string' ? session.sourceLabel : source;
205
+ const cwd = session && typeof session.cwd === 'string' ? session.cwd : '';
206
+ const messageCount = safeMessages.length;
207
+ const available = messageCount > 0;
208
+ const metrics = [
209
+ { key: 'messages', value: String(messageCount), label: options.messagesLabel || 'Messages' },
210
+ { key: 'user', value: String(roleCounts.user), label: options.userLabel || 'User' },
211
+ { key: 'assistant', value: String(roleCounts.assistant), label: options.assistantLabel || 'Assistant' },
212
+ { key: 'commands', value: String(commands.length), label: options.commandsLabel || 'Commands' },
213
+ { key: 'artifacts', value: String(files.length + links.length), label: options.artifactsLabel || 'Artifacts' },
214
+ { key: 'risks', value: String(risks.length + nextSteps.length), label: options.risksLabel || 'Risks' }
215
+ ];
216
+
217
+ const summary = {
218
+ available,
219
+ source,
220
+ sourceLabel,
221
+ cwd,
222
+ firstTimestamp,
223
+ lastTimestamp,
224
+ messageCount,
225
+ roleCounts,
226
+ metrics,
227
+ signals,
228
+ commands,
229
+ files,
230
+ links,
231
+ risks,
232
+ nextSteps
233
+ };
234
+ summary.briefText = buildWorkspaceBriefText(summary, options.briefLabels || {});
235
+ return summary;
236
+ }
237
+
54
238
  function formatUsageDuration(value, options = {}) {
55
239
  const normalizedLang = typeof options.lang === 'string' ? options.lang.trim().toLowerCase() : '';
56
240
  const isEn = normalizedLang === 'en';
@@ -267,6 +451,32 @@ export function createSessionComputed() {
267
451
  if (index < 0) return 0;
268
452
  return Math.round(((index + 1) / this.sessionTimelineNodes.length) * 100);
269
453
  },
454
+ activeSessionWorkspaceSummary() {
455
+ if (this.mainTab !== 'sessions' || !this.activeSession) {
456
+ return buildSessionWorkspaceSummary(null, []);
457
+ }
458
+ const t = typeof this.t === 'function' ? this.t : null;
459
+ return buildSessionWorkspaceSummary(this.activeSession, this.activeSessionMessages, {
460
+ messagesLabel: t ? t('sessions.workspace.metric.messages') : 'Messages',
461
+ userLabel: t ? t('sessions.workspace.metric.user') : 'User',
462
+ assistantLabel: t ? t('sessions.workspace.metric.assistant') : 'Assistant',
463
+ commandsLabel: t ? t('sessions.workspace.metric.commands') : 'Commands',
464
+ artifactsLabel: t ? t('sessions.workspace.metric.artifacts') : 'Artifacts',
465
+ risksLabel: t ? t('sessions.workspace.metric.risks') : 'Risks',
466
+ briefLabels: {
467
+ title: t ? t('sessions.workspace.copy.title') : 'Session workspace brief',
468
+ source: t ? t('sessions.workspace.copy.source') : 'Source',
469
+ messages: t ? t('sessions.workspace.metric.messages') : 'Messages',
470
+ path: t ? t('sessions.workspace.copy.path') : 'Path',
471
+ signals: t ? t('sessions.workspace.signals') : 'Signals',
472
+ commands: t ? t('sessions.workspace.commands') : 'Reusable commands',
473
+ files: t ? t('sessions.workspace.files') : 'Files',
474
+ links: t ? t('sessions.workspace.links') : 'Links',
475
+ risks: t ? t('sessions.workspace.risks') : 'Risks / blockers',
476
+ nextSteps: t ? t('sessions.workspace.nextSteps') : 'Next steps'
477
+ }
478
+ });
479
+ },
270
480
  sessionQueryPlaceholder() {
271
481
  if (this.isSessionQueryEnabled) {
272
482
  return typeof this.t === 'function'
@@ -1,3 +1,5 @@
1
+ import { nextClaudeConfigName } from './provider-default-names.mjs';
2
+
1
3
  function normalizeClaudeText(value) {
2
4
  return typeof value === 'string' ? value.trim() : '';
3
5
  }
@@ -6,6 +8,21 @@ function normalizeClaudeBaseUrl(value) {
6
8
  return normalizeClaudeText(value).replace(/\/+$/g, '');
7
9
  }
8
10
 
11
+ const DELETED_CLAUDE_SETTINGS_IMPORTS_KEY = 'deletedClaudeSettingsImports';
12
+
13
+ function buildDeletedClaudeSettingsFingerprint(config = {}) {
14
+ const safe = config && typeof config === 'object' ? config : {};
15
+ const baseUrl = normalizeClaudeBaseUrl(safe.baseUrl);
16
+ const model = normalizeClaudeText(safe.model);
17
+ if (!baseUrl || !model) return null;
18
+ return {
19
+ baseUrl,
20
+ model,
21
+ providerCacheRef: normalizeClaudeText(safe.providerCacheRef),
22
+ deletedAt: Date.now()
23
+ };
24
+ }
25
+
9
26
  function isValidClaudeHttpUrl(value) {
10
27
  if (!value) return false;
11
28
  try {
@@ -114,6 +131,46 @@ export function createClaudeConfigMethods(options = {}) {
114
131
  this.syncClaudeBridgeProviders();
115
132
  },
116
133
 
134
+ rememberDeletedClaudeSettingsImport(config) {
135
+ const fingerprint = buildDeletedClaudeSettingsFingerprint(config);
136
+ if (!fingerprint) return;
137
+ try {
138
+ const raw = localStorage.getItem(DELETED_CLAUDE_SETTINGS_IMPORTS_KEY);
139
+ const parsed = raw ? JSON.parse(raw) : [];
140
+ const entries = Array.isArray(parsed) ? parsed : [];
141
+ const deduped = entries.filter((entry) => {
142
+ if (!entry || typeof entry !== 'object') return false;
143
+ return normalizeClaudeBaseUrl(entry.baseUrl) !== fingerprint.baseUrl
144
+ || normalizeClaudeText(entry.model) !== fingerprint.model;
145
+ });
146
+ deduped.push(fingerprint);
147
+ localStorage.setItem(DELETED_CLAUDE_SETTINGS_IMPORTS_KEY, JSON.stringify(deduped.slice(-50)));
148
+ } catch (_) {}
149
+ },
150
+
151
+ async applyCurrentClaudeConfigSilently() {
152
+ const name = normalizeClaudeText(this.currentClaudeConfig);
153
+ if (!name || !this.claudeConfigs || !this.claudeConfigs[name]) return false;
154
+ if (typeof this.applyClaudeConfig !== 'function') return false;
155
+ return await this.applyClaudeConfig(name, { silent: true });
156
+ },
157
+
158
+ selectClaudeFallbackConfigName(excludedNames = []) {
159
+ const configs = this.claudeConfigs && typeof this.claudeConfigs === 'object' ? this.claudeConfigs : {};
160
+ const excluded = new Set((Array.isArray(excludedNames) ? excludedNames : [excludedNames])
161
+ .map((name) => normalizeClaudeText(name))
162
+ .filter(Boolean));
163
+ const names = Object.keys(configs).filter((name) => name && !excluded.has(name));
164
+ const applyable = names.find((name) => {
165
+ const config = configs[name] || {};
166
+ return !!(config.apiKey
167
+ || config.providerCacheRef
168
+ || config.externalCredentialType
169
+ || config.targetApi === 'ollama');
170
+ });
171
+ return applyable || names[0] || '';
172
+ },
173
+
117
174
  async hydrateClaudeConfigsFromProviderCache(options = {}) {
118
175
  const silent = options && options.silent === true;
119
176
  try {
@@ -123,10 +180,10 @@ export function createClaudeConfigMethods(options = {}) {
123
180
  return false;
124
181
  }
125
182
  const providers = Array.isArray(res.providers) ? res.providers : [];
126
- if (providers.length === 0) return true;
127
183
  const configs = this.claudeConfigs && typeof this.claudeConfigs === 'object' ? this.claudeConfigs : {};
128
184
  let changed = false;
129
185
  let firstCachedName = '';
186
+ const liveCacheRefs = new Set();
130
187
  for (const provider of providers) {
131
188
  if (!provider || typeof provider !== 'object') continue;
132
189
  const name = normalizeClaudeText(provider.name);
@@ -134,12 +191,15 @@ export function createClaudeConfigMethods(options = {}) {
134
191
  const model = normalizeClaudeText(provider.model);
135
192
  if (!name || !baseUrl || !model) continue;
136
193
  if (!firstCachedName) firstCachedName = name;
194
+ const providerCacheRef = normalizeClaudeText(provider.providerCacheRef) || name;
195
+ liveCacheRefs.add(name);
196
+ liveCacheRefs.add(providerCacheRef);
137
197
  const cachedConfig = {
138
198
  apiKey: '',
139
199
  baseUrl,
140
200
  model,
141
201
  hasKey: provider.hasKey === true,
142
- providerCacheRef: normalizeClaudeText(provider.providerCacheRef) || name,
202
+ providerCacheRef,
143
203
  source: 'provider-cache',
144
204
  targetApi: normalizeClaudeText(provider.targetApi) || 'responses'
145
205
  };
@@ -152,11 +212,26 @@ export function createClaudeConfigMethods(options = {}) {
152
212
  changed = true;
153
213
  }
154
214
  }
215
+ for (const [name, existing] of Object.entries(configs)) {
216
+ if (!existing || typeof existing !== 'object') continue;
217
+ const providerCacheRef = normalizeClaudeText(existing.providerCacheRef);
218
+ const source = normalizeClaudeText(existing.source);
219
+ const cacheBacked = source === 'provider-cache' || !!providerCacheRef;
220
+ if (!cacheBacked) continue;
221
+ const ref = providerCacheRef || normalizeClaudeText(name);
222
+ if (liveCacheRefs.has(ref) || liveCacheRefs.has(normalizeClaudeText(name))) continue;
223
+ delete configs[name];
224
+ changed = true;
225
+ }
155
226
  this.claudeConfigs = configs;
156
- if (firstCachedName) {
227
+ const current = normalizeClaudeText(this.currentClaudeConfig);
228
+ if (current && !configs[current]) {
229
+ this.currentClaudeConfig = firstCachedName || Object.keys(configs)[0] || '';
230
+ try { localStorage.setItem('currentClaudeConfig', this.currentClaudeConfig); } catch (_) {}
231
+ changed = true;
232
+ } else if (firstCachedName) {
157
233
  let savedCurrent = '';
158
234
  try { savedCurrent = localStorage.getItem('currentClaudeConfig') || ''; } catch (_) {}
159
- const current = normalizeClaudeText(this.currentClaudeConfig);
160
235
  const currentConfig = current && configs[current] ? configs[current] : null;
161
236
  if (!savedCurrent && (!current || (currentConfig && currentConfig.hasKey === false && !currentConfig.providerCacheRef))) {
162
237
  this.currentClaudeConfig = firstCachedName;
@@ -323,6 +398,28 @@ export function createClaudeConfigMethods(options = {}) {
323
398
  await this.applyClaudeConfig(name);
324
399
  },
325
400
 
401
+ async deleteClaudeProviderCacheRef(configOrName) {
402
+ const config = configOrName && typeof configOrName === 'object'
403
+ ? configOrName
404
+ : (this.claudeConfigs && this.claudeConfigs[configOrName] ? this.claudeConfigs[configOrName] : null);
405
+ const ref = normalizeClaudeText(config && config.providerCacheRef);
406
+ const source = normalizeClaudeText(config && config.source);
407
+ if (!ref && source !== 'provider-cache') return true;
408
+ const name = ref || normalizeClaudeText(config && config.name) || normalizeClaudeText(configOrName);
409
+ if (!name) return true;
410
+ try {
411
+ const res = await api('delete-provider-cache-record', { name, group: 'claude' });
412
+ if (res && res.error) {
413
+ this.showMessage(res.error, 'error');
414
+ return false;
415
+ }
416
+ return true;
417
+ } catch (e) {
418
+ this.showMessage(e && e.message ? e.message : this.t('toast.operation.fail'), 'error');
419
+ return false;
420
+ }
421
+ },
422
+
326
423
  async deleteClaudeConfig(name) {
327
424
  if (Object.keys(this.claudeConfigs).length <= 1) {
328
425
  return this.showMessage(this.t('toast.claude.keepOne'), 'error');
@@ -336,16 +433,30 @@ export function createClaudeConfigMethods(options = {}) {
336
433
  });
337
434
  if (!confirmed) return;
338
435
 
436
+ const config = this.claudeConfigs[name];
437
+ const cacheDeleted = await this.deleteClaudeProviderCacheRef(config || name);
438
+ if (!cacheDeleted) return;
439
+
440
+ if (typeof this.rememberDeletedClaudeSettingsImport === 'function') {
441
+ this.rememberDeletedClaudeSettingsImport(config);
442
+ }
339
443
  delete this.claudeConfigs[name];
340
444
  if (this.currentClaudeConfig === name) {
341
- this.currentClaudeConfig = Object.keys(this.claudeConfigs)[0];
445
+ this.currentClaudeConfig = this.selectClaudeFallbackConfigName();
342
446
  }
343
447
  this.saveClaudeConfigs();
344
448
  this.showMessage(this.t('toast.operation.success'), 'success');
345
- this.refreshClaudeModelContext();
449
+ if (this.currentClaudeConfig) {
450
+ await this.applyCurrentClaudeConfigSilently();
451
+ } else {
452
+ this.refreshClaudeModelContext();
453
+ }
346
454
  },
347
455
 
348
- async applyClaudeConfig(name) {
456
+ async applyClaudeConfig(name, options = {}) {
457
+ const silent = !!(options && options.silent);
458
+ const silentSuccess = silent || !!(options && options.silentSuccess);
459
+ const silentError = silent || !!(options && options.silentError);
349
460
  this.currentClaudeConfig = name;
350
461
  try { localStorage.setItem('currentClaudeConfig', name || ''); } catch (_) {}
351
462
  this.refreshClaudeModelContext();
@@ -353,8 +464,10 @@ export function createClaudeConfigMethods(options = {}) {
353
464
 
354
465
  if (!config.apiKey && !config.providerCacheRef && config.targetApi !== 'ollama') {
355
466
  if (config.externalCredentialType) {
467
+ if (silentError) return false;
356
468
  return this.showMessage(this.t('toast.claude.externalAuth'), 'info');
357
469
  }
470
+ if (silentError) return false;
358
471
  return this.showMessage(this.t('toast.claude.apiKeyRequired'), 'error');
359
472
  }
360
473
 
@@ -362,15 +475,18 @@ export function createClaudeConfigMethods(options = {}) {
362
475
  try {
363
476
  const res = await api('apply-claude-config', { config: { ...config, name } });
364
477
  if (res.error || res.success === false) {
365
- this.showMessage(res.error || this.t('toast.apply.fail'), 'error');
478
+ if (!silentError) this.showMessage(res.error || this.t('toast.apply.fail'), 'error');
479
+ return false;
366
480
  } else {
367
- if (this._lastAppliedClaudeKey !== _claudeKey2) {
481
+ if (!silentSuccess && this._lastAppliedClaudeKey !== _claudeKey2) {
368
482
  this.showMessage(this.t('toast.apply.success'), 'success');
369
- this._lastAppliedClaudeKey = _claudeKey2;
370
483
  }
484
+ this._lastAppliedClaudeKey = _claudeKey2;
485
+ return true;
371
486
  }
372
487
  } catch (_) {
373
- this.showMessage(this.t('toast.apply.fail'), 'error');
488
+ if (!silentError) this.showMessage(this.t('toast.apply.fail'), 'error');
489
+ return false;
374
490
  }
375
491
  },
376
492
 
@@ -378,7 +494,7 @@ export function createClaudeConfigMethods(options = {}) {
378
494
  this.showClaudeConfigModal = false;
379
495
  this.showAddClaudeConfigKey = false;
380
496
  this.newClaudeConfig = {
381
- name: '',
497
+ name: nextClaudeConfigName(this.claudeConfigs),
382
498
  apiKey: '',
383
499
  externalCredentialType: '',
384
500
  baseUrl: '',