codexmate 0.1.1 → 0.1.3

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 (38) hide show
  1. package/README.md +6 -3
  2. package/cli/openai-bridge-retry.js +17 -9
  3. package/cli/openai-bridge-runtime.js +14 -1
  4. package/cli/openai-bridge.js +20 -10
  5. package/cli.js +375 -4
  6. package/package.json +1 -1
  7. package/web-ui/app.js +18 -4
  8. package/web-ui/index.html +1 -0
  9. package/web-ui/modules/app.computed.dashboard.mjs +14 -0
  10. package/web-ui/modules/app.methods.agents.mjs +2 -2
  11. package/web-ui/modules/app.methods.index.mjs +2 -0
  12. package/web-ui/modules/app.methods.install.mjs +14 -0
  13. package/web-ui/modules/app.methods.kilocode-config.mjs +164 -0
  14. package/web-ui/modules/app.methods.navigation.mjs +12 -0
  15. package/web-ui/modules/app.methods.openclaw-core.mjs +87 -0
  16. package/web-ui/modules/app.methods.providers.mjs +5 -32
  17. package/web-ui/modules/app.methods.startup-claude.mjs +2 -1
  18. package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -2
  19. package/web-ui/modules/app.methods.web-ui-preferences.mjs +2 -1
  20. package/web-ui/modules/config-mode.computed.mjs +11 -1
  21. package/web-ui/modules/i18n/locales/en.mjs +31 -4
  22. package/web-ui/modules/i18n/locales/ja.mjs +34 -8
  23. package/web-ui/modules/i18n/locales/vi.mjs +35 -9
  24. package/web-ui/modules/i18n/locales/zh-tw.mjs +36 -9
  25. package/web-ui/modules/i18n/locales/zh.mjs +40 -13
  26. package/web-ui/partials/index/layout-header.html +32 -21
  27. package/web-ui/partials/index/modal-config-template-agents.html +8 -4
  28. package/web-ui/partials/index/modals-basic.html +0 -26
  29. package/web-ui/partials/index/panel-config-kilocode.html +101 -0
  30. package/web-ui/partials/index/panel-config-openclaw.html +42 -14
  31. package/web-ui/partials/index/panel-config-opencode.html +1 -0
  32. package/web-ui/partials/index/panel-docs.html +17 -1
  33. package/web-ui/res/web-ui-render.precompiled.js +400 -123
  34. package/web-ui/styles/controls-forms.css +10 -0
  35. package/web-ui/styles/docs-panel.css +51 -0
  36. package/web-ui/styles/layout-shell.css +12 -0
  37. package/web-ui/styles/modals-core.css +79 -0
  38. package/web-ui/styles/openclaw-structured.css +117 -71
@@ -0,0 +1,164 @@
1
+ function normalizeKilocodeProviderName(value) {
2
+ const name = typeof value === 'string' ? value.trim() : '';
3
+ return /^[a-zA-Z0-9_.-]+$/.test(name) ? name : '';
4
+ }
5
+
6
+ const DEFAULT_KILOCODE_PROVIDER = 'codexmate';
7
+ const DEFAULT_KILOCODE_MODEL = 'gpt-5.3';
8
+
9
+ export function createKilocodeConfigMethods(options = {}) {
10
+ const { api } = options;
11
+ return {
12
+ normalizeKilocodeProviderNameForUi(value) {
13
+ return normalizeKilocodeProviderName(value);
14
+ },
15
+
16
+ refreshKilocodeSelectionFromSummary(res = {}) {
17
+ const providers = Array.isArray(res.providers) ? res.providers : [];
18
+ this.kilocodeProviders = providers;
19
+ this.kilocodeConfigPath = typeof res.targetPath === 'string' ? res.targetPath : '';
20
+ this.kilocodeConfigExists = res.exists === true;
21
+ this.kilocodeContent = typeof res.content === 'string' ? res.content : '{}\n';
22
+ const firstProvider = providers.find(item => item && item.name);
23
+ this.kilocodeProvider = normalizeKilocodeProviderName(res.currentProvider)
24
+ || normalizeKilocodeProviderName(firstProvider && firstProvider.name)
25
+ || DEFAULT_KILOCODE_PROVIDER;
26
+ const selected = providers.find(item => normalizeKilocodeProviderName(item && item.name) === this.kilocodeProvider);
27
+ this.kilocodeBaseUrl = typeof (selected && (selected.baseURL || selected.api)) === 'string'
28
+ ? (selected.baseURL || selected.api)
29
+ : this.kilocodeBaseUrl;
30
+ const model = typeof res.currentModel === 'string' ? res.currentModel.trim() : '';
31
+ this.kilocodeModel = model || (selected && Array.isArray(selected.models) ? (selected.models[0] || '') : this.kilocodeModel) || DEFAULT_KILOCODE_MODEL;
32
+ },
33
+
34
+ async loadKilocodeConfig(options = {}) {
35
+ if (this.kilocodeLoading) return;
36
+ this.kilocodeLoading = true;
37
+ this.kilocodeError = '';
38
+ try {
39
+ const res = await api('get-kilocode-config');
40
+ if (res && res.error) {
41
+ this.kilocodeError = res.error;
42
+ return;
43
+ }
44
+ this.refreshKilocodeSelectionFromSummary(res || {});
45
+ if (options.toast === true) this.showMessage('KiloCode 配置已刷新', 'success');
46
+ } catch (e) {
47
+ this.kilocodeError = e && e.message ? e.message : '读取 KiloCode 配置失败';
48
+ } finally {
49
+ this.kilocodeLoading = false;
50
+ }
51
+ },
52
+
53
+ hasKilocodeStoredKey(provider) {
54
+ const normalizedProvider = normalizeKilocodeProviderName(provider || this.kilocodeProvider);
55
+ return (this.kilocodeProviders || []).some(item => normalizeKilocodeProviderName(item && item.name) === normalizedProvider && item.hasKey === true);
56
+ },
57
+
58
+ kilocodeConfigSignature(provider, url, model, apiKey) {
59
+ const keyMarker = apiKey ? `key:${apiKey}` : (this.hasKilocodeStoredKey(provider) ? 'key:<stored>' : 'key:<empty>');
60
+ return [provider, url, model, keyMarker].join('\u0000');
61
+ },
62
+
63
+ async selectKilocodeProvider(provider = {}) {
64
+ const name = normalizeKilocodeProviderName(provider && provider.name);
65
+ if (!name) return false;
66
+ const url = typeof (provider.baseURL || provider.api) === 'string' ? (provider.baseURL || provider.api).trim() : '';
67
+ const model = Array.isArray(provider.models) && provider.models.length
68
+ ? String(provider.models[0] || '').trim()
69
+ : DEFAULT_KILOCODE_MODEL;
70
+ this.kilocodeProvider = name;
71
+ if (url) this.kilocodeBaseUrl = url;
72
+ this.kilocodeModel = model || DEFAULT_KILOCODE_MODEL;
73
+ this.kilocodeApiKey = '';
74
+ this.kilocodeAutoSaveSignature = '';
75
+ if (!this.isToolConfigWriteAllowed('kilocode') || !url || !this.hasKilocodeStoredKey(name)) return true;
76
+ return this.saveKilocodeConfig({ auto: true });
77
+ },
78
+
79
+ async autoSaveKilocodeConfig() {
80
+ if (!this.isToolConfigWriteAllowed('kilocode') || this.kilocodeSaving || this.kilocodeLoading) return false;
81
+ const provider = normalizeKilocodeProviderName(this.kilocodeProvider) || DEFAULT_KILOCODE_PROVIDER;
82
+ const url = typeof this.kilocodeBaseUrl === 'string' ? this.kilocodeBaseUrl.trim() : '';
83
+ const model = (typeof this.kilocodeModel === 'string' ? this.kilocodeModel.trim() : '') || DEFAULT_KILOCODE_MODEL;
84
+ const apiKey = typeof this.kilocodeApiKey === 'string' ? this.kilocodeApiKey.trim() : '';
85
+ if (!url || (!apiKey && !this.hasKilocodeStoredKey(provider))) return false;
86
+ const signature = this.kilocodeConfigSignature(provider, url, model, apiKey);
87
+ if (signature === this.kilocodeAutoSaveSignature) return false;
88
+ return this.saveKilocodeConfig({ auto: true, signature });
89
+ },
90
+
91
+ async saveKilocodeConfig(options = {}) {
92
+ if (this.kilocodeSaving) return false;
93
+ const auto = options && options.auto === true;
94
+ if (!this.isToolConfigWriteAllowed('kilocode')) {
95
+ if (!auto) this.showMessage(this.t ? this.t('kilocode.writeRequired') : '请先打开 KiloCode 写入开关', 'error');
96
+ return false;
97
+ }
98
+ const provider = normalizeKilocodeProviderName(this.kilocodeProvider) || DEFAULT_KILOCODE_PROVIDER;
99
+ const url = typeof this.kilocodeBaseUrl === 'string' ? this.kilocodeBaseUrl.trim() : '';
100
+ const model = (typeof this.kilocodeModel === 'string' ? this.kilocodeModel.trim() : '') || DEFAULT_KILOCODE_MODEL;
101
+ const apiKey = typeof this.kilocodeApiKey === 'string' ? this.kilocodeApiKey.trim() : '';
102
+ if (!url || (!apiKey && !this.hasKilocodeStoredKey(provider))) {
103
+ if (!auto) this.showMessage(this.t ? this.t('kilocode.fillRequired') : '请填写 KiloCode URL 和 API Key', 'error');
104
+ return false;
105
+ }
106
+ this.kilocodeProvider = provider;
107
+ this.kilocodeModel = model;
108
+ this.kilocodeSaving = true;
109
+ this.kilocodeError = '';
110
+ try {
111
+ const res = await api('apply-kilocode-config', { provider, url, model, apiKey });
112
+ if (res && res.error) {
113
+ this.kilocodeError = res.error;
114
+ this.showMessage(res.error, 'error');
115
+ return false;
116
+ }
117
+ this.kilocodeApiKey = '';
118
+ this.refreshKilocodeSelectionFromSummary(res || {});
119
+ this.kilocodeAutoSaveSignature = options && options.signature ? options.signature : this.kilocodeConfigSignature(provider, url, model, '');
120
+ this.showMessage(this.t ? this.t(auto ? 'kilocode.autoSaved' : 'kilocode.saved') : 'KiloCode 配置已保存', 'success');
121
+ return true;
122
+ } catch (e) {
123
+ this.kilocodeError = e && e.message ? e.message : (this.t ? this.t('kilocode.saveFailed') : '保存 KiloCode 配置失败');
124
+ this.showMessage(this.kilocodeError, 'error');
125
+ return false;
126
+ } finally {
127
+ this.kilocodeSaving = false;
128
+ }
129
+ },
130
+
131
+ async startKilocode(configure = false) {
132
+ if (this.kilocodeStarting) return;
133
+ if (!this.isToolConfigWriteAllowed('kilocode')) {
134
+ this.showMessage('请先打开 KiloCode 写入开关', 'error');
135
+ return;
136
+ }
137
+ this.kilocodeStarting = true;
138
+ this.kilocodeError = '';
139
+ try {
140
+ const payload = configure ? {
141
+ configure: true,
142
+ provider: this.kilocodeProvider,
143
+ url: this.kilocodeBaseUrl,
144
+ model: this.kilocodeModel,
145
+ apiKey: this.kilocodeApiKey
146
+ } : {};
147
+ const res = await api('start-kilocode', payload);
148
+ if (res && res.error) {
149
+ this.kilocodeError = res.error;
150
+ this.showMessage(res.error, 'error');
151
+ return;
152
+ }
153
+ this.kilocodeApiKey = '';
154
+ this.showMessage(`KiloCode 已启动${res && res.pid ? `(PID ${res.pid})` : ''}`, 'success');
155
+ await this.loadKilocodeConfig();
156
+ } catch (e) {
157
+ this.kilocodeError = e && e.message ? e.message : '启动 KiloCode 失败';
158
+ this.showMessage(this.kilocodeError, 'error');
159
+ } finally {
160
+ this.kilocodeStarting = false;
161
+ }
162
+ }
163
+ };
164
+ }
@@ -148,6 +148,9 @@
148
148
  }
149
149
  this.configMode = configModeSet.has(normalizedMode) ? normalizedMode : 'codex';
150
150
  if (this.mainTab === 'config') {
151
+ if (this.configMode === 'kilocode' && typeof this.loadKilocodeConfig === 'function') {
152
+ this.loadKilocodeConfig();
153
+ }
151
154
  if (this.configMode === 'claude') {
152
155
  const expectedMainTab = 'config';
153
156
  const expectedConfigMode = 'claude';
@@ -458,6 +461,9 @@
458
461
  if (targetTab === previousTab) {
459
462
  switchState.ticket += 1;
460
463
  switchState.pendingTarget = '';
464
+ if (targetTab === 'config' && this.configMode === 'kilocode' && typeof this.loadKilocodeConfig === 'function') {
465
+ void this.loadKilocodeConfig();
466
+ }
461
467
  if (targetTab === 'dashboard' && !this.__doctorLoadedOnce) {
462
468
  void loadDoctorOverview(this);
463
469
  }
@@ -490,6 +496,9 @@
490
496
  switchState.pendingTarget = '';
491
497
  const result = switchMainTabHelper.call(this, targetTab);
492
498
  persistNavState(this);
499
+ if (targetTab === 'config' && this.configMode === 'kilocode' && typeof this.loadKilocodeConfig === 'function') {
500
+ void this.loadKilocodeConfig();
501
+ }
493
502
  if (targetTab === 'dashboard') {
494
503
  void loadDoctorOverview(this);
495
504
  }
@@ -508,6 +517,9 @@
508
517
  liveState.pendingTarget = '';
509
518
  switchMainTabHelper.call(this, pendingTarget);
510
519
  persistNavState(this);
520
+ if (pendingTarget === 'config' && this.configMode === 'kilocode' && typeof this.loadKilocodeConfig === 'function') {
521
+ void this.loadKilocodeConfig();
522
+ }
511
523
  if (pendingTarget === 'dashboard') {
512
524
  void loadDoctorOverview(this);
513
525
  }
@@ -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() {
@@ -143,7 +143,8 @@ export function createStartupClaudeMethods(options = {}) {
143
143
  this.toolConfigPermissions = {
144
144
  codex: statusRes.toolConfigPermissions.codex === true,
145
145
  claude: statusRes.toolConfigPermissions.claude === true,
146
- opencode: statusRes.toolConfigPermissions.opencode === true
146
+ opencode: statusRes.toolConfigPermissions.opencode === true,
147
+ kilocode: statusRes.toolConfigPermissions.kilocode === true
147
148
  };
148
149
  }
149
150
  this.providersList = listRes.providers;
@@ -3,7 +3,7 @@ export function createToolConfigPermissionMethods(options = {}) {
3
3
 
4
4
  function normalizeTarget(value) {
5
5
  const target = typeof value === 'string' ? value.trim().toLowerCase() : '';
6
- return target === 'codex' || target === 'claude' || target === 'opencode' ? target : '';
6
+ return target === 'codex' || target === 'claude' || target === 'opencode' || target === 'kilocode' ? target : '';
7
7
  }
8
8
 
9
9
  function normalizePermissions(value) {
@@ -11,7 +11,8 @@ export function createToolConfigPermissionMethods(options = {}) {
11
11
  return {
12
12
  codex: source.codex === true,
13
13
  claude: source.claude === true,
14
- opencode: source.opencode === true
14
+ opencode: source.opencode === true,
15
+ kilocode: source.kilocode === true
15
16
  };
16
17
  }
17
18
 
@@ -438,7 +438,8 @@ export function createWebUiPreferencesMethods(options = {}) {
438
438
  this.toolConfigPermissions = {
439
439
  codex: source.toolConfigPermissions.codex === true,
440
440
  claude: source.toolConfigPermissions.claude === true,
441
- opencode: source.toolConfigPermissions.opencode === true
441
+ opencode: source.toolConfigPermissions.opencode === true,
442
+ kilocode: source.toolConfigPermissions.kilocode === true
442
443
  };
443
444
  }
444
445
  if (Array.isArray(source.deletedClaudeSettingsImports)) {
@@ -11,7 +11,8 @@ export const CONFIG_MODE_SET = new Set([
11
11
  ...Object.keys(PROVIDER_CONFIG_MODE_META),
12
12
  'claude',
13
13
  'openclaw',
14
- 'opencode'
14
+ 'opencode',
15
+ 'kilocode'
15
16
  ]);
16
17
 
17
18
  export function getProviderConfigModeMeta(mode) {
@@ -63,6 +64,7 @@ export function createConfigModeComputed() {
63
64
  if (this.configMode === 'claude') return 'Claude Code';
64
65
  if (this.configMode === 'openclaw') return 'OpenClaw';
65
66
  if (this.configMode === 'opencode') return 'OpenCode';
67
+ if (this.configMode === 'kilocode') return 'KiloCode';
66
68
  return '未选择';
67
69
  },
68
70
  inspectorCurrentConfigLabel() {
@@ -82,6 +84,10 @@ export function createConfigModeComputed() {
82
84
  const provider = typeof this.opencodeProvider === 'string' ? this.opencodeProvider.trim() : '';
83
85
  return provider || '未选择';
84
86
  }
87
+ if (this.configMode === 'kilocode') {
88
+ const provider = typeof this.kilocodeProvider === 'string' ? this.kilocodeProvider.trim() : '';
89
+ return provider || '未选择';
90
+ }
85
91
  return '未选择';
86
92
  },
87
93
  inspectorCurrentModelLabel() {
@@ -103,6 +109,10 @@ export function createConfigModeComputed() {
103
109
  const model = typeof this.opencodeModel === 'string' ? this.opencodeModel.trim() : '';
104
110
  return model || '未选择';
105
111
  }
112
+ if (this.configMode === 'kilocode') {
113
+ const model = typeof this.kilocodeModel === 'string' ? this.kilocodeModel.trim() : '';
114
+ return model || '未选择';
115
+ }
106
116
  return '未选择';
107
117
  },
108
118
  inspectorTemplateStatus() {
@@ -142,6 +142,7 @@ const en = Object.freeze({
142
142
  'side.docs': 'Docs',
143
143
  'side.config': 'Config',
144
144
  'side.sessions': 'Sessions',
145
+ 'side.workspace': 'Workspace',
145
146
  'side.prompts': 'Prompts',
146
147
  'side.prompts.meta': 'Prompt files and presets',
147
148
  'side.plugins': 'Plugins',
@@ -172,7 +173,7 @@ const en = Object.freeze({
172
173
  'side.config.claude': 'Claude Code',
173
174
  'side.config.claude.meta': 'Settings',
174
175
  'side.config.openclaw': 'OpenClaw',
175
- 'side.config.openclaw.meta': 'JSON5 · AGENTS',
176
+ 'side.config.openclaw.meta': 'Config',
176
177
  'side.config.opencode': 'OpenCode',
177
178
  'side.config.opencode.meta': 'Provider',
178
179
  'side.sessions.browser': 'Session Browser',
@@ -517,8 +518,6 @@ const en = Object.freeze({
517
518
  'modal.claudeConfigEdit.title': 'Edit Claude Code config',
518
519
  'field.useBuiltinTransform': 'Use built-in transform (OpenAI compatible)',
519
520
  '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
521
 
523
522
  // Config template / agents modals
524
523
  'modal.configTemplate.label': 'config.toml template',
@@ -620,9 +619,12 @@ const en = Object.freeze({
620
619
 
621
620
  // Docs panel
622
621
  'docs.title': 'CLI Install',
623
- 'docs.subtitle': 'Install commands for Claude Code / Gemini CLI / CodeBuddy Code / Codex CLI.',
622
+ 'docs.subtitle': 'Install commands for Claude Code / Gemini CLI / CodeBuddy Code / Codex CLI / KiloCode CLI.',
624
623
  'docs.section.commands': 'Commands',
625
624
  'docs.section.commandsNote': 'Copy and run directly.',
625
+ 'docs.installed.title': 'Currently installed commands',
626
+ 'docs.installed.count': '{count} installed',
627
+ 'docs.installed.empty': 'No installed commands detected',
626
628
  'docs.section.faq': 'FAQ',
627
629
  'docs.section.faqNote': 'Common issues and tips.',
628
630
  'docs.command.aria': '{name} command',
@@ -1532,6 +1534,31 @@ const en = Object.freeze({
1532
1534
  'openclaw.action.applyAria': 'Apply OpenClaw config: {name}',
1533
1535
  'openclaw.action.editAria': 'Edit OpenClaw config: {name}',
1534
1536
  'openclaw.action.deleteAria': 'Delete OpenClaw config: {name}'
1537
+ ,
1538
+
1539
+ 'tab.config.kilocode': 'KiloCode',
1540
+ 'side.config.kilocode': 'KiloCode',
1541
+ 'side.config.kilocode.meta': 'Provider',
1542
+ 'toolConfig.kilocode.title': 'KiloCode config writes and launch',
1543
+ 'toolConfig.kilocode.desc': 'Read-only by default; enable writes to sync KiloCode config and launch it from the Web UI.',
1544
+ 'toolConfig.kilocode.lockedTitle': 'Read-only mode',
1545
+ 'toolConfig.kilocode.lockedDesc': 'Enable write access to auto-sync config or launch KiloCode.',
1546
+ 'toolConfig.kilocode.confirmMessage': 'When enabled, actions in the KiloCode tab can write ~/.config/kilo/kilo.jsonc or an existing kilo.json, and can launch local KiloCode.',
1547
+ 'kilocode.providerModel.title': 'KiloCode provider / model',
1548
+ 'kilocode.connection.title': 'KiloCode connection',
1549
+ 'kilocode.targetFile': 'Active KiloCode config: {path} · {status}',
1550
+ 'kilocode.saveConfig': 'Save config',
1551
+ 'kilocode.start': 'Launch KiloCode',
1552
+ 'kilocode.starting': 'Launching',
1553
+ 'kilocode.saveAndStart': 'Save and launch',
1554
+ 'kilocode.summary.title': 'Current KiloCode provider',
1555
+ 'kilocode.configPreview.title': 'KiloCode config preview',
1556
+ 'kilocode.configPreview.hint': 'API Key is redacted; auto-sync writes the real key.',
1557
+ 'kilocode.saveFailed': 'Failed to save KiloCode config',
1558
+ 'kilocode.fillRequired': 'Fill KiloCode URL and API Key',
1559
+ 'kilocode.writeRequired': 'Enable KiloCode write access first',
1560
+ 'kilocode.saved': 'KiloCode config saved',
1561
+ 'kilocode.autoSaved': 'KiloCode config synced',
1535
1562
  });
1536
1563
 
1537
1564
  export { en };