codexmate 0.1.1 → 0.1.2

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.
@@ -1,14 +1,20 @@
1
- const DEFAULT_BRIDGE_MAX_RETRIES = 2;
2
- const MIN_BRIDGE_MAX_RETRIES = 2;
3
- const MAX_BRIDGE_MAX_RETRIES = 10;
1
+ const DEFAULT_BRIDGE_MAX_RETRIES = Infinity;
4
2
  const BASE_TRANSIENT_RETRY_DELAY_MS = 200;
5
3
  const MAX_TRANSIENT_RETRY_DELAY_MS = 5000;
6
4
 
7
5
  function normalizeBridgeMaxRetries(value, fallback = DEFAULT_BRIDGE_MAX_RETRIES) {
8
6
  const raw = Number(value);
9
7
  const fallbackRaw = Number(fallback);
10
- const base = Number.isFinite(raw) ? raw : (Number.isFinite(fallbackRaw) ? fallbackRaw : DEFAULT_BRIDGE_MAX_RETRIES);
11
- return Math.min(MAX_BRIDGE_MAX_RETRIES, Math.max(MIN_BRIDGE_MAX_RETRIES, Math.floor(base)));
8
+ if (Number.isFinite(raw) && raw >= 0) return Math.floor(raw);
9
+ if (Number.isFinite(fallbackRaw) && fallbackRaw >= 0) return Math.floor(fallbackRaw);
10
+ return DEFAULT_BRIDGE_MAX_RETRIES;
11
+ }
12
+
13
+
14
+ function isTransientHttpStatus(status) {
15
+ const code = Number(status);
16
+ if (code === 408 || code === 409 || code === 425 || code === 429) return true;
17
+ return code >= 500 && code <= 599;
12
18
  }
13
19
 
14
20
  function isTransientNetworkError(error) {
@@ -30,7 +36,7 @@ function getTransientRetryDelayMs(attempt) {
30
36
  async function retryTransientRequest(executor, options = {}) {
31
37
  const maxRetries = normalizeBridgeMaxRetries(options && options.maxRetries);
32
38
  let lastResult = null;
33
- for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
39
+ for (let attempt = 0; attempt === 0 || attempt <= maxRetries; attempt += 1) {
34
40
  if (attempt > 0) {
35
41
  const delay = getTransientRetryDelayMs(attempt);
36
42
  // eslint-disable-next-line no-await-in-loop
@@ -43,9 +49,12 @@ async function retryTransientRequest(executor, options = {}) {
43
49
  const result = await executor(attempt);
44
50
  lastResult = result;
45
51
  if (!result) return result;
52
+ if (result.status && result.status > 0) {
53
+ if (!isTransientHttpStatus(result.status)) return result;
54
+ continue;
55
+ }
46
56
  if (result.ok) return result;
47
57
  if (result.retry) return result;
48
- if (result.status && result.status > 0) return result;
49
58
  if (!isTransientNetworkError(result.error)) return result;
50
59
  }
51
60
  return lastResult;
@@ -53,10 +62,9 @@ async function retryTransientRequest(executor, options = {}) {
53
62
 
54
63
  module.exports = {
55
64
  DEFAULT_BRIDGE_MAX_RETRIES,
56
- MIN_BRIDGE_MAX_RETRIES,
57
- MAX_BRIDGE_MAX_RETRIES,
58
65
  normalizeBridgeMaxRetries,
59
66
  isTransientNetworkError,
67
+ isTransientHttpStatus,
60
68
  getTransientRetryDelayMs,
61
69
  retryTransientRequest
62
70
  };
@@ -1472,7 +1472,20 @@ function streamChatCompletionsAsResponsesSse(targetUrl, options = {}) {
1472
1472
  response: {
1473
1473
  id: state.responseId,
1474
1474
  model: state.model,
1475
- created_at: state.createdAt
1475
+ created_at: state.createdAt,
1476
+ status: 'in_progress',
1477
+ output: []
1478
+ }
1479
+ });
1480
+ writeSse(res, 'response.in_progress', {
1481
+ type: 'response.in_progress',
1482
+ sequence_number: state.nextSeq(),
1483
+ response: {
1484
+ id: state.responseId,
1485
+ model: state.model,
1486
+ created_at: state.createdAt,
1487
+ status: 'in_progress',
1488
+ output: []
1476
1489
  }
1477
1490
  });
1478
1491
 
@@ -87,7 +87,6 @@ function normalizeOpenaiUpstreamBaseUrl(rawValue) {
87
87
  }
88
88
 
89
89
  const {
90
- normalizeBridgeMaxRetries,
91
90
  parseJsonOrError,
92
91
  extractChatCompletionResult,
93
92
  convertResponsesRequestToChatCompletions,
@@ -114,11 +113,10 @@ function normalizeUpstreamEntry(entry) {
114
113
  const apiKey = normalizeText(entry.apiKey || entry.api_key || entry.key || '');
115
114
  const headersRaw = entry.headers || entry.extraHeaders || entry.extra_headers || null;
116
115
  const headers = normalizeHeadersMap(headersRaw);
117
- const maxRetries = normalizeBridgeMaxRetries(entry.maxRetries ?? entry.max_retries);
118
116
  if (!baseUrl || !isValidHttpUrl(baseUrl)) {
119
117
  return null;
120
118
  }
121
- return { baseUrl, apiKey, headers, maxRetries };
119
+ return { baseUrl, apiKey, headers };
122
120
  }
123
121
 
124
122
  function normalizeHeadersMap(value) {
@@ -169,7 +167,6 @@ function upsertOpenaiBridgeProvider(filePath, providerName, upstreamBaseUrl, api
169
167
  const baseUrl = normalizeOpenaiUpstreamBaseUrl(upstreamBaseUrl);
170
168
  const key = normalizeText(apiKey);
171
169
  const nextHeaders = normalizeHeadersMap(headers);
172
- const maxRetries = normalizeBridgeMaxRetries(options && options.maxRetries);
173
170
 
174
171
  if (!name) {
175
172
  return { error: 'Provider name is required' };
@@ -191,7 +188,6 @@ function upsertOpenaiBridgeProvider(filePath, providerName, upstreamBaseUrl, api
191
188
  baseUrl,
192
189
  apiKey: key,
193
190
  headers: Object.keys(nextHeaders).length ? nextHeaders : existingHeaders,
194
- maxRetries
195
191
  }
196
192
  }
197
193
  };
@@ -384,7 +380,7 @@ function createOpenaiBridgeHttpHandler(options = {}) {
384
380
  maxBytes: maxUpstreamBytes,
385
381
  httpAgent,
386
382
  httpsAgent
387
- }), { maxRetries: upstream.maxRetries });
383
+ }));
388
384
  if (!result.ok) {
389
385
  res.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' });
390
386
  res.end(JSON.stringify({ error: `Upstream request failed: ${result.error}` }));
@@ -446,7 +442,7 @@ function createOpenaiBridgeHttpHandler(options = {}) {
446
442
  res,
447
443
  model: typeof chatBody.model === 'string' ? chatBody.model : '',
448
444
  toolTypesByName: converted.toolTypesByName || {}
449
- }), { maxRetries: upstream.maxRetries });
445
+ }));
450
446
  if (!streamed.ok) {
451
447
  if (res.writableEnded || res.destroyed) {
452
448
  return;
@@ -471,7 +467,7 @@ function createOpenaiBridgeHttpHandler(options = {}) {
471
467
  maxBytes: maxUpstreamBytes,
472
468
  httpAgent,
473
469
  httpsAgent
474
- }), { maxRetries: upstream.maxRetries });
470
+ }));
475
471
  if (!upstreamResult.ok) {
476
472
  res.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' });
477
473
  res.end(JSON.stringify({ error: `Upstream request failed: ${upstreamResult.error}` }));
@@ -514,8 +510,23 @@ function createOpenaiBridgeHttpHandler(options = {}) {
514
510
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
515
511
  res.end(JSON.stringify(ensureResponseMetadata(responsesPayload)));
516
512
  } catch (e) {
513
+ if (res.writableEnded || res.destroyed) {
514
+ return;
515
+ }
516
+ const message = e && e.message ? e.message : 'Internal Error';
517
+ if (res.headersSent) {
518
+ try {
519
+ res.end();
520
+ } catch (_) {
521
+ // Headers are already committed. Close the socket instead of leaving the client waiting forever.
522
+ if (!res.destroyed && typeof res.destroy === 'function') {
523
+ res.destroy(e);
524
+ }
525
+ }
526
+ return;
527
+ }
517
528
  res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
518
- res.end(JSON.stringify({ error: e && e.message ? e.message : 'Internal Error' }));
529
+ res.end(JSON.stringify({ error: message }));
519
530
  }
520
531
  })();
521
532
 
@@ -543,7 +554,6 @@ module.exports = {
543
554
  extractChatCompletionResult,
544
555
  buildResponsesPayloadFromChatResult,
545
556
  retryTransientRequest,
546
- normalizeBridgeMaxRetries,
547
557
  normalizeOpenaiUpstreamBaseUrl,
548
558
  extractResponsesOutputText,
549
559
  shouldFallbackFromUpstreamResponses,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/web-ui/app.js CHANGED
@@ -290,9 +290,9 @@ document.addEventListener('DOMContentLoaded', () => {
290
290
  appVersionStatusChecked: false,
291
291
  appVersionStatusCheckedAt: '',
292
292
  appVersionStatusSource: '',
293
- newProvider: { name: '', url: '', key: '', model: '', useTransform: false, openaiBridgeMaxRetries: 2 },
293
+ newProvider: { name: '', url: '', key: '', model: '', useTransform: false },
294
294
  resetConfigLoading: false,
295
- editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false, openaiBridgeMaxRetries: 2 },
295
+ editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false },
296
296
  newModelName: '',
297
297
  currentClaudeConfig: '',
298
298
  currentClaudeModel: '',
@@ -206,13 +206,13 @@ export function createAgentsMethods(options = {}) {
206
206
  this.agentsContext = 'openclaw-workspace';
207
207
  this.agentsWorkspaceFileName = fileName;
208
208
  this.agentsModalTitle = tr('modal.agents.title.openclawWorkspaceFile', `OpenClaw 工作区文件: ${fileName}`, { fileName });
209
- this.agentsModalHint = tr('modal.agents.hint.openclawWorkspaceFile', `保存后会写入 OpenClaw Workspace 下的 ${fileName}。`, { fileName });
209
+ this.agentsModalHint = tr('modal.agents.hint.openclawWorkspaceFile', `Workspace / ${fileName}`, { fileName });
210
210
  return;
211
211
  }
212
212
  this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
213
213
  if (this.agentsContext === 'openclaw') {
214
214
  this.agentsModalTitle = tr('modal.agents.title.openclaw', 'OpenClaw AGENTS.md 编辑器');
215
- this.agentsModalHint = tr('modal.agents.hint.openclaw', '保存后会写入 OpenClaw Workspace 下的 AGENTS.md');
215
+ this.agentsModalHint = tr('modal.agents.hint.openclaw', 'Workspace / AGENTS.md');
216
216
  } else {
217
217
  this.agentsModalTitle = tr('modal.agents.title.default', 'AGENTS.md 编辑器');
218
218
  this.agentsModalHint = tr('modal.agents.hint.default', '保存后会写入目标 AGENTS.md(与 config.toml 同级)。');
@@ -224,6 +224,31 @@ function readPreferredProviderModels(records) {
224
224
  return [];
225
225
  }
226
226
 
227
+ function readNestedValue(record, path) {
228
+ if (!isPlainRecord(record)) return undefined;
229
+ let cursor = record;
230
+ for (const key of path) {
231
+ if (!isPlainRecord(cursor) || !Object.prototype.hasOwnProperty.call(cursor, key)) {
232
+ return undefined;
233
+ }
234
+ cursor = cursor[key];
235
+ }
236
+ return cursor;
237
+ }
238
+
239
+ function formatOpenclawSummaryValue(value, fallback = '未配置') {
240
+ if (Array.isArray(value)) {
241
+ const list = value
242
+ .map(item => (typeof item === 'string' ? item.trim() : String(item || '').trim()))
243
+ .filter(Boolean);
244
+ return list.length ? list.join(' / ') : fallback;
245
+ }
246
+ if (value === true) return '启用';
247
+ if (value === false) return '关闭';
248
+ const text = typeof value === 'string' ? value.trim() : String(value ?? '').trim();
249
+ return text || fallback;
250
+ }
251
+
227
252
  export function createOpenclawCoreMethods() {
228
253
  return {
229
254
  getOpenclawParser() {
@@ -472,6 +497,68 @@ export function createOpenclawCoreMethods() {
472
497
  };
473
498
  },
474
499
 
500
+ getOpenclawConfigSummary(config) {
501
+ const content = config && typeof config.content === 'string' ? config.content : '';
502
+ if (!content.trim()) {
503
+ return [];
504
+ }
505
+ const parsed = this.parseOpenclawContent(content, { allowEmpty: true });
506
+ if (!parsed.ok || !isPlainRecord(parsed.data)) {
507
+ return [
508
+ { key: 'parse-error', label: '配置状态', value: '解析失败', tone: 'warning' }
509
+ ];
510
+ }
511
+ const data = parsed.data;
512
+ const agentDefaults = readNestedValue(data, ['agents', 'defaults']);
513
+ const modelConfig = isPlainRecord(agentDefaults) ? agentDefaults.model : undefined;
514
+ const primaryModel = isPlainRecord(modelConfig)
515
+ ? modelConfig.primary
516
+ : (typeof modelConfig === 'string' ? modelConfig : readNestedValue(data, ['agent', 'model']));
517
+ const fallbacks = isPlainRecord(modelConfig) && Array.isArray(modelConfig.fallbacks)
518
+ ? modelConfig.fallbacks
519
+ : [];
520
+ const workspace = isPlainRecord(agentDefaults) ? agentDefaults.workspace : '';
521
+ const browser = isPlainRecord(data.browser) ? data.browser : {};
522
+ const browserMode = browser.enabled === false
523
+ ? '关闭'
524
+ : (browser.headless === true ? 'Headless' : (browser.headless === false ? '可见窗口' : '未声明'));
525
+ const executablePath = typeof browser.executablePath === 'string' ? browser.executablePath.trim() : '';
526
+ const providerNames = collectDistinctProviderKeys(
527
+ readNestedValue(data, ['models', 'providers']),
528
+ data.providers
529
+ );
530
+ return [
531
+ { key: 'primary', label: '默认模型', value: formatOpenclawSummaryValue(primaryModel) },
532
+ { key: 'fallbacks', label: 'Fallback', value: formatOpenclawSummaryValue(fallbacks) },
533
+ { key: 'workspace', label: 'Workspace', value: formatOpenclawSummaryValue(workspace) },
534
+ { key: 'browser', label: 'Browser', value: browserMode },
535
+ { key: 'browser-path', label: 'Chrome', value: formatOpenclawSummaryValue(executablePath) },
536
+ { key: 'providers', label: 'Providers', value: providerNames.length ? providerNames.join(' / ') : '未配置' }
537
+ ];
538
+ },
539
+
540
+ getOpenclawStatusSummaryItems() {
541
+ const current = this.openclawConfigs && this.currentOpenclawConfig
542
+ ? this.openclawConfigs[this.currentOpenclawConfig]
543
+ : null;
544
+ const configPath = this.openclawConfigPath || '~/.openclaw/openclaw.json';
545
+ return [
546
+ { key: 'config-path', label: 'Config', value: configPath, tone: this.openclawConfigExists ? 'ok' : 'warning' },
547
+ ...this.getOpenclawConfigSummary(current || {}).slice(0, 4)
548
+ ];
549
+ },
550
+
551
+ getOpenclawQuickWorkspaceFiles() {
552
+ return ['SOUL.md', 'USER.md', 'TOOLS.md', 'HEARTBEAT.md'];
553
+ },
554
+
555
+ openOpenclawQuickWorkspaceFile(fileName) {
556
+ const normalized = typeof fileName === 'string' ? fileName.trim() : '';
557
+ if (!normalized) return;
558
+ this.openclawWorkspaceFileName = normalized;
559
+ this.openOpenclawWorkspaceEditor();
560
+ },
561
+
475
562
  syncOpenclawQuickFromText(options = {}) {
476
563
  const silent = !!options.silent;
477
564
  const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
@@ -41,13 +41,6 @@ function findProviderByName(list, name) {
41
41
  return (Array.isArray(list) ? list : []).find((item) => item && normalizeText(item.name) === target) || null;
42
42
  }
43
43
 
44
- function normalizeBridgeMaxRetries(value, fallback = 2) {
45
- const raw = Number(value);
46
- const fallbackRaw = Number(fallback);
47
- const base = Number.isFinite(raw) ? raw : (Number.isFinite(fallbackRaw) ? fallbackRaw : 2);
48
- return Math.min(10, Math.max(2, Math.floor(base)));
49
- }
50
-
51
44
  function normalizeProviderDraftState(target) {
52
45
  if (!target || typeof target !== 'object') return;
53
46
  if (typeof target.name === 'string') {
@@ -62,9 +55,6 @@ function normalizeProviderDraftState(target) {
62
55
  if (typeof target.key === 'string') {
63
56
  target.key = target.key.trim();
64
57
  }
65
- if (target.useTransform || target.openaiBridgeMaxRetries !== undefined) {
66
- target.openaiBridgeMaxRetries = normalizeBridgeMaxRetries(target.openaiBridgeMaxRetries);
67
- }
68
58
  }
69
59
 
70
60
  function maskKeyLocal(key) {
@@ -87,13 +77,11 @@ function getProviderValidationForContext(vm, mode = 'add') {
87
77
  const model = normalizeText(draft && draft.model);
88
78
  const key = normalizeText(draft && draft.key);
89
79
  const useTransform = !!(draft && draft.useTransform);
90
- const openaiBridgeMaxRetries = normalizeBridgeMaxRetries(draft && draft.openaiBridgeMaxRetries);
91
80
  const errors = {
92
81
  name: '',
93
82
  url: '',
94
83
  key: '',
95
84
  model: '',
96
- openaiBridgeMaxRetries: ''
97
85
  };
98
86
 
99
87
  if (mode === 'add') {
@@ -124,10 +112,6 @@ function getProviderValidationForContext(vm, mode = 'add') {
124
112
  errors.model = '模型名称必填';
125
113
  }
126
114
 
127
- if (useTransform && openaiBridgeMaxRetries < 2) {
128
- errors.openaiBridgeMaxRetries = '重试次数最小为 2';
129
- }
130
-
131
115
  return {
132
116
  mode,
133
117
  name,
@@ -135,9 +119,8 @@ function getProviderValidationForContext(vm, mode = 'add') {
135
119
  key,
136
120
  model,
137
121
  useTransform,
138
- openaiBridgeMaxRetries,
139
122
  errors,
140
- ok: !errors.name && !errors.url && !errors.key && !errors.model && !errors.openaiBridgeMaxRetries
123
+ ok: !errors.name && !errors.url && !errors.key && !errors.model
141
124
  };
142
125
  }
143
126
 
@@ -191,7 +174,7 @@ export function createProvidersMethods(options = {}) {
191
174
  normalizeProviderDraftState(this.newProvider);
192
175
  const validation = getProviderValidationForContext(this, 'add');
193
176
  if (!validation.ok) {
194
- return this.showMessage(validation.errors.name || validation.errors.url || validation.errors.key || validation.errors.model || validation.errors.openaiBridgeMaxRetries || this.t('toast.provider.fieldsRequired'), 'error');
177
+ return this.showMessage(validation.errors.name || validation.errors.url || validation.errors.key || validation.errors.model || this.t('toast.provider.fieldsRequired'), 'error');
195
178
  }
196
179
 
197
180
  try {
@@ -203,7 +186,6 @@ export function createProvidersMethods(options = {}) {
203
186
  };
204
187
  if (this.newProvider && this.newProvider.useTransform) {
205
188
  payload.useTransform = true;
206
- payload.openaiBridgeMaxRetries = validation.openaiBridgeMaxRetries;
207
189
  }
208
190
  const suggestedModel = validation.model;
209
191
  const res = await api('add-provider', payload);
@@ -218,7 +200,6 @@ export function createProvidersMethods(options = {}) {
218
200
  url: validation.url,
219
201
  upstreamUrl: '',
220
202
  codexmate_bridge: payload.useTransform ? 'openai' : '',
221
- openaiBridgeMaxRetries: payload.useTransform ? validation.openaiBridgeMaxRetries : undefined,
222
203
  key: maskKeyLocal(payload.key),
223
204
  hasKey: !!payload.key,
224
205
  models: suggestedModel ? [{ id: suggestedModel, name: suggestedModel, cost: null, contextWindow: undefined, maxTokens: undefined }] : [],
@@ -345,7 +326,6 @@ export function createProvidersMethods(options = {}) {
345
326
  key: '',
346
327
  model: '',
347
328
  useTransform: isTransform,
348
- openaiBridgeMaxRetries: normalizeBridgeMaxRetries(provider.openaiBridgeMaxRetries)
349
329
  };
350
330
  this.showAddProviderKey = false;
351
331
  this.showAddModal = true;
@@ -358,7 +338,6 @@ export function createProvidersMethods(options = {}) {
358
338
  key: '',
359
339
  model: '',
360
340
  useTransform: false,
361
- openaiBridgeMaxRetries: 2
362
341
  };
363
342
  this.showAddProviderKey = false;
364
343
  this.showAddModal = true;
@@ -387,7 +366,6 @@ export function createProvidersMethods(options = {}) {
387
366
  ? provider.nonEditable
388
367
  : this.isNonDeletableProvider(provider),
389
368
  useTransform: isTransformProvider,
390
- openaiBridgeMaxRetries: normalizeBridgeMaxRetries(provider.openaiBridgeMaxRetries)
391
369
  };
392
370
  this._editProviderOriginalKey = '';
393
371
  this._editProviderRealKeyLoaded = false;
@@ -428,9 +406,6 @@ export function createProvidersMethods(options = {}) {
428
406
  && res.baseUrl.trim()
429
407
  ) {
430
408
  this.editingProvider.url = normalizeProviderUrl(res.baseUrl);
431
- if (res.maxRetries !== undefined) {
432
- this.editingProvider.openaiBridgeMaxRetries = normalizeBridgeMaxRetries(res.maxRetries);
433
- }
434
409
  }
435
410
  } catch (_) {
436
411
  // ignore
@@ -447,13 +422,12 @@ export function createProvidersMethods(options = {}) {
447
422
  normalizeProviderDraftState(this.editingProvider);
448
423
  const validation = getProviderValidationForContext(this, 'edit');
449
424
  if (!validation.ok) {
450
- return this.showMessage(validation.errors.name || validation.errors.url || validation.errors.openaiBridgeMaxRetries || this.t('toast.provider.urlRequired'), 'error');
425
+ return this.showMessage(validation.errors.name || validation.errors.url || this.t('toast.provider.urlRequired'), 'error');
451
426
  }
452
427
 
453
428
  const params = { name: validation.name, url: validation.url };
454
429
  if (this.editingProvider && this.editingProvider.useTransform) {
455
430
  params.useTransform = true;
456
- params.openaiBridgeMaxRetries = validation.openaiBridgeMaxRetries;
457
431
  }
458
432
  if (this._editProviderRealKeyLoaded) {
459
433
  const currentKey = typeof this.editingProvider.key === 'string' ? this.editingProvider.key : '';
@@ -479,7 +453,6 @@ export function createProvidersMethods(options = {}) {
479
453
  key: keyUpdated ? maskKeyLocal(params.key) : p.key,
480
454
  hasKey: keyUpdated ? !!params.key : p.hasKey,
481
455
  codexmate_bridge: params.useTransform ? 'openai' : p.codexmate_bridge,
482
- openaiBridgeMaxRetries: params.useTransform ? validation.openaiBridgeMaxRetries : p.openaiBridgeMaxRetries
483
456
  };
484
457
  }
485
458
  return p;
@@ -497,7 +470,7 @@ export function createProvidersMethods(options = {}) {
497
470
  this.showEditProviderKey = false;
498
471
  this._editProviderOriginalKey = '';
499
472
  this._editProviderRealKeyLoaded = false;
500
- this.editingProvider = { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false, openaiBridgeMaxRetries: 2 };
473
+ this.editingProvider = { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false };
501
474
  },
502
475
 
503
476
  toggleEditProviderKey() {
@@ -580,7 +553,7 @@ export function createProvidersMethods(options = {}) {
580
553
  closeAddModal() {
581
554
  this.showAddModal = false;
582
555
  this.showAddProviderKey = false;
583
- this.newProvider = { name: nextCodexProviderName(this.providersList), url: '', key: '', model: '', useTransform: false, openaiBridgeMaxRetries: 2 };
556
+ this.newProvider = { name: nextCodexProviderName(this.providersList), url: '', key: '', model: '', useTransform: false };
584
557
  },
585
558
 
586
559
  toggleAddProviderKey() {
@@ -517,8 +517,6 @@ const en = Object.freeze({
517
517
  'modal.claudeConfigEdit.title': 'Edit Claude Code config',
518
518
  'field.useBuiltinTransform': 'Use built-in transform (OpenAI compatible)',
519
519
  'hint.useBuiltinTransform': 'When enabled, base_url points to codexmate built-in transform service; Codex token is fixed to codexmate.',
520
- 'field.transformMaxRetries': 'Built-in transform retries',
521
- 'hint.transformMaxRetries': 'Default 2, minimum 2; number of retries after the first failed attempt.',
522
520
 
523
521
  // Config template / agents modals
524
522
  'modal.configTemplate.label': 'config.toml template',
@@ -519,8 +519,6 @@ const ja = Object.freeze({
519
519
  'modal.claudeConfigEdit.title': 'Claude Code 設定編集',
520
520
  'field.useBuiltinTransform': '内蔵変換を使用(OpenAI 形式互換)',
521
521
  'hint.useBuiltinTransform': '有効時:書き込まれる base_url は codexmate 内蔵変換サービスを指します。Codex のトークンは codexmate に固定されます。',
522
- 'field.transformMaxRetries': '内蔵変換の再試行回数',
523
- 'hint.transformMaxRetries': '既定値は 2、最小値も 2。初回失敗後に再試行する最大回数です。',
524
522
 
525
523
  // Config template / agents modals
526
524
  'modal.configTemplate.label': 'config.toml テンプレート',
@@ -737,8 +737,6 @@ const vi = Object.freeze({
737
737
  'modal.claudeConfigEdit.title': 'Chỉnh sửa cấu hình Claude Code',
738
738
  'field.useBuiltinTransform': 'Dùng transform tích hợp (tương thích OpenAI)',
739
739
  'hint.useBuiltinTransform': 'Khi bật, base_url trỏ đến dịch vụ transform tích hợp của codexmate.',
740
- 'field.transformMaxRetries': 'Số lần thử lại transform tích hợp',
741
- 'hint.transformMaxRetries': 'Mặc định 2, tối thiểu 2; số lần thử lại sau lần gọi đầu thất bại.',
742
740
  'modal.configTemplate.title': 'Trình soạn thảo template cấu hình (xác nhận thủ công)',
743
741
  'modal.configTemplate.label': 'Template config.toml',
744
742
  'modal.configTemplate.placeholder': 'Chỉnh sửa template config.toml tại đây',
@@ -517,8 +517,6 @@ const zhTw = Object.freeze({
517
517
  'modal.claudeConfigEdit.title': '編輯 Claude Code 設定',
518
518
  'field.useBuiltinTransform': '使用內建轉換(兼容 OpenAI 格式)',
519
519
  'hint.useBuiltinTransform': '開啟後:寫入的 base_url 會指向 codexmate 內建轉換服務;Codex 使用的令牌固定為 codexmate。',
520
- 'field.transformMaxRetries': '內建轉換重試次數',
521
- 'hint.transformMaxRetries': '預設 2,最小 2;表示失敗後最多重試的次數。',
522
520
 
523
521
  // Config template / agents modals
524
522
  'modal.configTemplate.label': 'config.toml 模板',
@@ -517,8 +517,6 @@ const zh = Object.freeze({
517
517
  'modal.claudeConfigEdit.title': '编辑 Claude Code 配置',
518
518
  'field.useBuiltinTransform': '使用内建转换(兼容 OpenAI 格式)',
519
519
  'hint.useBuiltinTransform': '开启后:写入的 base_url 会指向 codexmate 内建转换服务;Codex 使用的令牌固定为 codexmate。',
520
- 'field.transformMaxRetries': '内建转换重试次数',
521
- 'hint.transformMaxRetries': '默认 2,最小 2;表示失败后最多重试的次数。',
522
520
 
523
521
  // Config template / agents modals
524
522
  'modal.configTemplate.label': 'config.toml 模板',
@@ -944,9 +942,9 @@ const zh = Object.freeze({
944
942
  'modal.agents.hint.claudeProjectGlobal': '保存后会写入 ~/.claude/CLAUDE.md。',
945
943
  'modal.agents.contentLabel.claudeProject': 'CLAUDE.md 内容',
946
944
  'modal.agents.placeholder.claudeProject': '在这里编辑 CLAUDE.md',
947
- 'modal.agents.hint.openclaw': '保存后会写入 OpenClaw Workspace 下的 AGENTS.md',
945
+ 'modal.agents.hint.openclaw': 'Workspace / AGENTS.md',
948
946
  'modal.agents.title.openclawWorkspaceFile': 'OpenClaw 工作区文件: {fileName}',
949
- 'modal.agents.hint.openclawWorkspaceFile': '保存后会写入 OpenClaw Workspace 下的 {fileName}',
947
+ 'modal.agents.hint.openclawWorkspaceFile': 'Workspace / {fileName}',
950
948
  'config.url.unset': '未设 URL',
951
949
  'config.model.unset': '未设置模型',
952
950
  'config.badge.system': '系统',
@@ -1516,11 +1514,11 @@ const zh = Object.freeze({
1516
1514
  'openclaw.applyHint': '写入 ~/.openclaw/openclaw.json,支持 JSON5。',
1517
1515
  'openclaw.workspace.title': 'OpenClaw 工作区',
1518
1516
  'openclaw.configs.hint': '选择常用配置,或进入编辑器维护完整 JSON5。',
1519
- 'openclaw.agents.hint': '读写 Workspace AGENTS.md,默认路径 ~/.openclaw/workspace/AGENTS.md。',
1517
+ 'openclaw.agents.hint': 'Workspace / AGENTS.md',
1520
1518
  'openclaw.agents.open': '打开 AGENTS.md',
1521
1519
  'openclaw.workspaceFile': '工作区文件',
1522
1520
  'openclaw.workspace.placeholder': '例如: SOUL.md',
1523
- 'openclaw.workspace.hint': '仅限 Workspace 内的 .md 文件。',
1521
+ 'openclaw.workspace.hint': 'Workspace .md',
1524
1522
  'openclaw.workspace.open': '打开工作区文件',
1525
1523
  'openclaw.configured': '已配置',
1526
1524
  'openclaw.notConfigured': '未配置',
@@ -117,8 +117,8 @@
117
117
  </div>
118
118
  </div>
119
119
 
120
- <div class="modal-editor-body">
121
- <div class="form-group">
120
+ <div class="modal-editor-body" :class="{ 'modal-editor-body--openclaw-workspace': agentsContext === 'openclaw-workspace' }">
121
+ <div class="form-group agents-target-file-group">
122
122
  <label class="form-label">{{ t('modal.agents.targetFile') }}</label>
123
123
  <div class="form-hint">
124
124
  {{ agentsPath || t('common.notLoaded') }}
@@ -129,8 +129,12 @@
129
129
  </div>
130
130
 
131
131
 
132
- <div class="form-group">
133
- <label class="form-label">{{ t(agentsContext === 'claude-project' ? 'modal.agents.contentLabel.claudeProject' : 'modal.agents.contentLabel') }}</label>
132
+ <div class="form-group agents-content-group">
133
+ <label class="form-label">
134
+ {{ agentsContext === 'openclaw-workspace'
135
+ ? `${agentsWorkspaceFileName || '工作区文件'} 内容`
136
+ : t(agentsContext === 'claude-project' ? 'modal.agents.contentLabel.claudeProject' : 'modal.agents.contentLabel') }}
137
+ </label>
134
138
  <div
135
139
  v-if="!agentsLoading && (hasAgentsContentChanged() || agentsDiffVisible)"
136
140
  class="agents-diff-save-alert">
@@ -53,19 +53,6 @@
53
53
  {{ t('field.useBuiltinTransform') }}
54
54
  </label>
55
55
  </div>
56
- <div class="form-group" v-if="newProvider.useTransform">
57
- <label class="form-label">{{ t('field.transformMaxRetries') }}</label>
58
- <input
59
- v-model.number="newProvider.openaiBridgeMaxRetries"
60
- type="number"
61
- min="2"
62
- max="10"
63
- step="1"
64
- :class="['form-input', { invalid: !!providerFieldError('add', 'openaiBridgeMaxRetries') }]"
65
- @blur="normalizeProviderDraft('add')">
66
- <div class="form-hint">{{ t('hint.transformMaxRetries') }}</div>
67
- <div v-if="providerFieldError('add', 'openaiBridgeMaxRetries')" class="form-hint form-error">{{ providerFieldError('add', 'openaiBridgeMaxRetries') }}</div>
68
- </div>
69
56
 
70
57
  <div class="btn-group">
71
58
  <button class="btn btn-cancel" @click="closeAddModal">{{ t('common.cancel') }}</button>
@@ -104,19 +91,6 @@
104
91
  </button>
105
92
  </div>
106
93
  </div>
107
- <div class="form-group" v-if="editingProvider.useTransform">
108
- <label class="form-label">{{ t('field.transformMaxRetries') }}</label>
109
- <input
110
- v-model.number="editingProvider.openaiBridgeMaxRetries"
111
- type="number"
112
- min="2"
113
- max="10"
114
- step="1"
115
- :class="['form-input', { invalid: !!providerFieldError('edit', 'openaiBridgeMaxRetries') }]"
116
- @blur="normalizeProviderDraft('edit')">
117
- <div class="form-hint">{{ t('hint.transformMaxRetries') }}</div>
118
- <div v-if="providerFieldError('edit', 'openaiBridgeMaxRetries')" class="form-hint form-error">{{ providerFieldError('edit', 'openaiBridgeMaxRetries') }}</div>
119
- </div>
120
94
 
121
95
  <div class="btn-group">
122
96
  <button class="btn btn-cancel" @click="closeEditModal">{{ t('common.cancel') }}</button>