codexmate 0.0.20 → 0.0.22
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.
- package/README.md +289 -152
- package/README.zh.md +321 -0
- package/cli/agents-files.js +224 -0
- package/cli/archive-helpers.js +446 -0
- package/cli/auth-profiles.js +359 -0
- package/cli/builtin-proxy.js +1044 -0
- package/cli/claude-proxy.js +998 -0
- package/cli/config-bootstrap.js +384 -0
- package/cli/openai-bridge.js +950 -0
- package/cli/openclaw-config.js +629 -0
- package/cli/session-usage.concurrent.js +28 -0
- package/cli/session-usage.js +112 -0
- package/cli/session-usage.models.js +176 -0
- package/cli/skills.js +1141 -0
- package/cli/zip-commands.js +510 -0
- package/cli.js +9408 -9719
- package/lib/cli-models-utils.js +109 -1
- package/lib/cli-path-utils.js +69 -0
- package/lib/cli-sessions.js +386 -0
- package/lib/download-artifacts.js +77 -0
- package/lib/task-orchestrator.js +869 -0
- package/package.json +14 -10
- package/res/logo.png +0 -0
- package/res/vue.global.prod.js +13 -0
- package/web-ui/app.js +193 -15
- package/web-ui/index.html +5 -1
- package/web-ui/logic.agents-diff.mjs +1 -1
- package/web-ui/logic.claude.mjs +60 -0
- package/web-ui/logic.runtime.mjs +11 -7
- package/web-ui/logic.sessions.mjs +372 -21
- package/web-ui/modules/api.mjs +22 -1
- package/web-ui/modules/app.computed.dashboard.mjs +23 -10
- package/web-ui/modules/app.computed.index.mjs +4 -0
- package/web-ui/modules/app.computed.main-tabs.mjs +198 -0
- package/web-ui/modules/app.computed.session.mjs +521 -9
- package/web-ui/modules/app.methods.agents.mjs +62 -11
- package/web-ui/modules/app.methods.codex-config.mjs +189 -34
- package/web-ui/modules/app.methods.index.mjs +7 -1
- package/web-ui/modules/app.methods.install.mjs +24 -20
- package/web-ui/modules/app.methods.navigation.mjs +142 -1
- package/web-ui/modules/app.methods.openclaw-core.mjs +339 -39
- package/web-ui/modules/app.methods.openclaw-editing.mjs +39 -4
- package/web-ui/modules/app.methods.openclaw-persist.mjs +122 -4
- package/web-ui/modules/app.methods.providers.mjs +192 -53
- package/web-ui/modules/app.methods.session-actions.mjs +99 -19
- package/web-ui/modules/app.methods.session-browser.mjs +196 -5
- package/web-ui/modules/app.methods.session-timeline.mjs +22 -15
- package/web-ui/modules/app.methods.session-trash.mjs +3 -0
- package/web-ui/modules/app.methods.startup-claude.mjs +70 -71
- package/web-ui/modules/app.methods.task-orchestration.mjs +471 -0
- package/web-ui/modules/config-mode.computed.mjs +2 -0
- package/web-ui/modules/config-template-confirm-pref.mjs +33 -0
- package/web-ui/modules/i18n.mjs +1609 -0
- package/web-ui/modules/plugins.computed.mjs +220 -0
- package/web-ui/modules/plugins.methods.mjs +620 -0
- package/web-ui/modules/plugins.storage.mjs +37 -0
- package/web-ui/partials/index/layout-footer.html +1 -57
- package/web-ui/partials/index/layout-header.html +299 -175
- package/web-ui/partials/index/modal-config-template-agents.html +79 -29
- package/web-ui/partials/index/modal-confirm-toast.html +1 -1
- package/web-ui/partials/index/modal-health-check.html +14 -14
- package/web-ui/partials/index/modal-openclaw-config.html +47 -42
- package/web-ui/partials/index/modal-skills.html +130 -114
- package/web-ui/partials/index/modals-basic.html +71 -102
- package/web-ui/partials/index/panel-config-claude.html +50 -12
- package/web-ui/partials/index/panel-config-codex.html +34 -37
- package/web-ui/partials/index/panel-config-openclaw.html +10 -16
- package/web-ui/partials/index/panel-docs.html +147 -0
- package/web-ui/partials/index/panel-market.html +38 -38
- package/web-ui/partials/index/panel-orchestration.html +397 -0
- package/web-ui/partials/index/panel-plugins.html +243 -0
- package/web-ui/partials/index/panel-sessions.html +51 -146
- package/web-ui/partials/index/panel-settings.html +188 -96
- package/web-ui/partials/index/panel-usage.html +353 -0
- package/web-ui/session-helpers.mjs +221 -10
- package/web-ui/styles/base-theme.css +120 -229
- package/web-ui/styles/controls-forms.css +59 -51
- package/web-ui/styles/docs-panel.css +247 -0
- package/web-ui/styles/layout-shell.css +394 -128
- package/web-ui/styles/modals-core.css +18 -3
- package/web-ui/styles/navigation-panels.css +184 -183
- package/web-ui/styles/plugins-panel.css +518 -0
- package/web-ui/styles/responsive.css +102 -62
- package/web-ui/styles/sessions-list.css +13 -27
- package/web-ui/styles/sessions-preview.css +13 -7
- package/web-ui/styles/sessions-toolbar-trash.css +25 -0
- package/web-ui/styles/sessions-usage.css +581 -6
- package/web-ui/styles/settings-panel.css +166 -0
- package/web-ui/styles/skills-list.css +16 -11
- package/web-ui/styles/skills-market.css +63 -2
- package/web-ui/styles/task-orchestration.css +776 -0
- package/web-ui/styles/titles-cards.css +67 -66
- package/web-ui/styles.css +4 -0
- package/README.en.md +0 -259
- package/res/screenshot.png +0 -0
- package/res/vue.global.js +0 -18552
|
@@ -139,7 +139,9 @@ export function createOpenclawEditingMethods() {
|
|
|
139
139
|
this.openclawEditing.content = this.stringifyOpenclawConfig(config);
|
|
140
140
|
this.refreshOpenclawProviders(config);
|
|
141
141
|
this.refreshOpenclawAgentsList(config);
|
|
142
|
-
this.fillOpenclawQuickFromConfig(config
|
|
142
|
+
this.fillOpenclawQuickFromConfig(config, {
|
|
143
|
+
authProfilesByProvider: this.openclawAuthProfilesByProvider
|
|
144
|
+
});
|
|
143
145
|
this.showMessage('已写入', 'success');
|
|
144
146
|
},
|
|
145
147
|
|
|
@@ -170,7 +172,9 @@ export function createOpenclawEditingMethods() {
|
|
|
170
172
|
const models = ensureObject(config.models);
|
|
171
173
|
const providers = ensureObject(models.providers);
|
|
172
174
|
const provider = ensureObject(providers[providerName]);
|
|
173
|
-
const baseUrl =
|
|
175
|
+
const baseUrl = this.openclawQuick.baseUrlReadOnly
|
|
176
|
+
? ''
|
|
177
|
+
: (this.openclawQuick.baseUrl || '').trim();
|
|
174
178
|
if (!baseUrl && !provider.baseUrl) {
|
|
175
179
|
this.showMessage('请填写 URL', 'error');
|
|
176
180
|
return;
|
|
@@ -188,7 +192,21 @@ export function createOpenclawEditingMethods() {
|
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
const shouldOverrideProvider = !!this.openclawQuick.overrideProvider;
|
|
191
|
-
const apiKey =
|
|
195
|
+
const apiKey = this.openclawQuick.apiKeyReadOnly
|
|
196
|
+
? ''
|
|
197
|
+
: (this.openclawQuick.apiKey || '').trim();
|
|
198
|
+
const apiKeySourceKind = typeof this.openclawQuick.apiKeySourceKind === 'string'
|
|
199
|
+
? this.openclawQuick.apiKeySourceKind.trim()
|
|
200
|
+
: '';
|
|
201
|
+
const apiKeySourceProfileId = typeof this.openclawQuick.apiKeySourceProfileId === 'string'
|
|
202
|
+
? this.openclawQuick.apiKeySourceProfileId.trim()
|
|
203
|
+
: '';
|
|
204
|
+
const apiKeySourceWriteField = typeof this.openclawQuick.apiKeySourceWriteField === 'string'
|
|
205
|
+
? this.openclawQuick.apiKeySourceWriteField.trim()
|
|
206
|
+
: '';
|
|
207
|
+
const apiKeySourceOriginalValue = typeof this.openclawQuick.apiKeySourceOriginalValue === 'string'
|
|
208
|
+
? this.openclawQuick.apiKeySourceOriginalValue.trim()
|
|
209
|
+
: '';
|
|
192
210
|
const apiType = (this.openclawQuick.apiType || '').trim();
|
|
193
211
|
const setProviderField = (key, value) => {
|
|
194
212
|
if (!value) return;
|
|
@@ -198,7 +216,24 @@ export function createOpenclawEditingMethods() {
|
|
|
198
216
|
};
|
|
199
217
|
setProviderField('baseUrl', baseUrl);
|
|
200
218
|
setProviderField('api', apiType);
|
|
201
|
-
if (
|
|
219
|
+
if (apiKeySourceKind === 'auth-profile' && apiKeySourceProfileId && apiKeySourceWriteField) {
|
|
220
|
+
const pending = this.openclawPendingAuthProfileUpdates
|
|
221
|
+
&& typeof this.openclawPendingAuthProfileUpdates === 'object'
|
|
222
|
+
&& !Array.isArray(this.openclawPendingAuthProfileUpdates)
|
|
223
|
+
? { ...this.openclawPendingAuthProfileUpdates }
|
|
224
|
+
: {};
|
|
225
|
+
if (apiKey && apiKey !== apiKeySourceOriginalValue) {
|
|
226
|
+
pending[apiKeySourceProfileId] = {
|
|
227
|
+
profileId: apiKeySourceProfileId,
|
|
228
|
+
provider: providerName,
|
|
229
|
+
field: apiKeySourceWriteField,
|
|
230
|
+
value: apiKey
|
|
231
|
+
};
|
|
232
|
+
} else {
|
|
233
|
+
delete pending[apiKeySourceProfileId];
|
|
234
|
+
}
|
|
235
|
+
this.openclawPendingAuthProfileUpdates = pending;
|
|
236
|
+
} else if (apiKey) {
|
|
202
237
|
setProviderField('apiKey', apiKey);
|
|
203
238
|
}
|
|
204
239
|
|
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
const DEFAULT_OPENCLAW_CONFIG_NAME = '默认配置';
|
|
2
|
+
|
|
3
|
+
function buildNormalizedOpenclawConfigs(configs, defaultContent = '') {
|
|
4
|
+
const source = configs && typeof configs === 'object' && !Array.isArray(configs)
|
|
5
|
+
? configs
|
|
6
|
+
: {};
|
|
7
|
+
const defaultEntry = source[DEFAULT_OPENCLAW_CONFIG_NAME]
|
|
8
|
+
&& typeof source[DEFAULT_OPENCLAW_CONFIG_NAME] === 'object'
|
|
9
|
+
&& !Array.isArray(source[DEFAULT_OPENCLAW_CONFIG_NAME])
|
|
10
|
+
? source[DEFAULT_OPENCLAW_CONFIG_NAME]
|
|
11
|
+
: { content: defaultContent };
|
|
12
|
+
const normalized = {
|
|
13
|
+
[DEFAULT_OPENCLAW_CONFIG_NAME]: {
|
|
14
|
+
content: typeof defaultEntry.content === 'string' ? defaultEntry.content : defaultContent
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
for (const [name, value] of Object.entries(source)) {
|
|
18
|
+
if (name === DEFAULT_OPENCLAW_CONFIG_NAME) continue;
|
|
19
|
+
normalized[name] = value;
|
|
20
|
+
}
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function syncDefaultOpenclawConfigState(vm, content, options = {}) {
|
|
25
|
+
const nextContent = typeof content === 'string' ? content : '';
|
|
26
|
+
vm.openclawConfigs = buildNormalizedOpenclawConfigs(vm.openclawConfigs, nextContent);
|
|
27
|
+
vm.openclawConfigs[DEFAULT_OPENCLAW_CONFIG_NAME] = {
|
|
28
|
+
content: nextContent
|
|
29
|
+
};
|
|
30
|
+
if (typeof options.path === 'string') {
|
|
31
|
+
vm.openclawConfigPath = options.path;
|
|
32
|
+
}
|
|
33
|
+
if (typeof options.exists === 'boolean') {
|
|
34
|
+
vm.openclawConfigExists = options.exists;
|
|
35
|
+
}
|
|
36
|
+
if (options.lineEnding === '\r\n' || options.lineEnding === '\n') {
|
|
37
|
+
vm.openclawLineEnding = options.lineEnding;
|
|
38
|
+
}
|
|
39
|
+
if (!vm.currentOpenclawConfig || !Object.prototype.hasOwnProperty.call(vm.openclawConfigs, vm.currentOpenclawConfig)) {
|
|
40
|
+
vm.currentOpenclawConfig = DEFAULT_OPENCLAW_CONFIG_NAME;
|
|
41
|
+
}
|
|
42
|
+
if (options.persist !== false && typeof vm.saveOpenclawConfigs === 'function') {
|
|
43
|
+
vm.saveOpenclawConfigs();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
1
47
|
export function createOpenclawPersistMethods(options = {}) {
|
|
2
48
|
const {
|
|
3
49
|
api,
|
|
@@ -5,10 +51,39 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
5
51
|
} = options;
|
|
6
52
|
|
|
7
53
|
return {
|
|
54
|
+
syncDefaultOpenclawConfigEntry(options = {}) {
|
|
55
|
+
const silent = !!options.silent;
|
|
56
|
+
return api('get-openclaw-config')
|
|
57
|
+
.then((res) => {
|
|
58
|
+
if (res && !res.error) {
|
|
59
|
+
this.openclawAuthProfilesByProvider = res && res.authProfilesByProvider && typeof res.authProfilesByProvider === 'object' && !Array.isArray(res.authProfilesByProvider)
|
|
60
|
+
? res.authProfilesByProvider
|
|
61
|
+
: {};
|
|
62
|
+
const nextContent = res.exists && typeof res.content === 'string' && res.content.trim()
|
|
63
|
+
? res.content
|
|
64
|
+
: defaultOpenclawTemplate;
|
|
65
|
+
syncDefaultOpenclawConfigState(this, nextContent, {
|
|
66
|
+
path: res.path || this.openclawConfigPath,
|
|
67
|
+
exists: !!res.exists,
|
|
68
|
+
lineEnding: res.lineEnding === '\r\n' ? '\r\n' : '\n',
|
|
69
|
+
persist: true
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return res;
|
|
73
|
+
})
|
|
74
|
+
.catch((e) => {
|
|
75
|
+
if (!silent) {
|
|
76
|
+
this.showMessage('加载 OpenClaw 默认配置失败', 'error');
|
|
77
|
+
}
|
|
78
|
+
return { error: e && e.message ? e.message : String(e) };
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
|
|
8
82
|
openOpenclawAddModal() {
|
|
9
83
|
const modalToken = (Number(this.openclawModalLoadToken || 0) + 1);
|
|
10
84
|
this.openclawModalLoadToken = modalToken;
|
|
11
85
|
this.openclawEditorTitle = '添加 OpenClaw 配置';
|
|
86
|
+
this.openclawPendingAuthProfileUpdates = {};
|
|
12
87
|
this.openclawEditing = {
|
|
13
88
|
name: '',
|
|
14
89
|
content: '',
|
|
@@ -29,9 +104,11 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
29
104
|
|
|
30
105
|
openOpenclawEditModal(name) {
|
|
31
106
|
const existing = this.openclawConfigs[name];
|
|
107
|
+
const isDefaultConfig = name === DEFAULT_OPENCLAW_CONFIG_NAME;
|
|
32
108
|
const modalToken = (Number(this.openclawModalLoadToken || 0) + 1);
|
|
33
109
|
this.openclawModalLoadToken = modalToken;
|
|
34
110
|
this.openclawEditorTitle = `编辑 OpenClaw 配置: ${name}`;
|
|
111
|
+
this.openclawPendingAuthProfileUpdates = {};
|
|
35
112
|
this.openclawEditing = {
|
|
36
113
|
name,
|
|
37
114
|
content: this.openclawHasContent(existing) ? existing.content : '',
|
|
@@ -41,8 +118,9 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
41
118
|
this.showOpenclawConfigModal = true;
|
|
42
119
|
void this.loadOpenclawConfigFromFile({
|
|
43
120
|
silent: true,
|
|
44
|
-
force:
|
|
45
|
-
fallbackToTemplate:
|
|
121
|
+
force: isDefaultConfig,
|
|
122
|
+
fallbackToTemplate: isDefaultConfig,
|
|
123
|
+
syncDefaultEntry: isDefaultConfig,
|
|
46
124
|
modalToken,
|
|
47
125
|
expectedEditorContent: this.openclawEditing.content
|
|
48
126
|
});
|
|
@@ -58,6 +136,7 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
58
136
|
this.openclawEditing = { name: '', content: '', lockName: false };
|
|
59
137
|
this.openclawSaving = false;
|
|
60
138
|
this.openclawApplying = false;
|
|
139
|
+
this.openclawPendingAuthProfileUpdates = {};
|
|
61
140
|
this.resetOpenclawStructured();
|
|
62
141
|
this.resetOpenclawQuick();
|
|
63
142
|
},
|
|
@@ -66,6 +145,8 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
66
145
|
const silent = !!options.silent;
|
|
67
146
|
const force = !!options.force;
|
|
68
147
|
const fallbackToTemplate = options.fallbackToTemplate !== false;
|
|
148
|
+
const syncDefaultEntry = options.syncDefaultEntry === true
|
|
149
|
+
|| (this.openclawEditing && this.openclawEditing.lockName && this.openclawEditing.name === DEFAULT_OPENCLAW_CONFIG_NAME);
|
|
69
150
|
const modalToken = Number(options.modalToken || this.openclawModalLoadToken || 0);
|
|
70
151
|
const expectedEditorContent = typeof options.expectedEditorContent === 'string'
|
|
71
152
|
? options.expectedEditorContent
|
|
@@ -90,6 +171,10 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
90
171
|
this.openclawConfigPath = res.path || '';
|
|
91
172
|
this.openclawConfigExists = !!res.exists;
|
|
92
173
|
this.openclawLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
174
|
+
this.openclawAuthProfilesByProvider = res && res.authProfilesByProvider && typeof res.authProfilesByProvider === 'object' && !Array.isArray(res.authProfilesByProvider)
|
|
175
|
+
? res.authProfilesByProvider
|
|
176
|
+
: {};
|
|
177
|
+
this.openclawPendingAuthProfileUpdates = {};
|
|
93
178
|
const hasContent = !!(res.content && res.content.trim());
|
|
94
179
|
const currentContent = typeof this.openclawEditing.content === 'string'
|
|
95
180
|
? this.openclawEditing.content
|
|
@@ -98,11 +183,21 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
98
183
|
const shouldOverride = force
|
|
99
184
|
? (!currentContent.trim() || !editorChangedSinceRequest)
|
|
100
185
|
: (!currentContent || !currentContent.trim());
|
|
186
|
+
const fallbackContent = fallbackToTemplate ? defaultOpenclawTemplate : '';
|
|
187
|
+
const nextContent = hasContent ? res.content : fallbackContent;
|
|
101
188
|
if (hasContent && shouldOverride) {
|
|
102
189
|
this.openclawEditing.content = res.content;
|
|
103
190
|
} else if (!hasContent && shouldOverride && fallbackToTemplate) {
|
|
104
191
|
this.openclawEditing.content = defaultOpenclawTemplate;
|
|
105
192
|
}
|
|
193
|
+
if (syncDefaultEntry) {
|
|
194
|
+
syncDefaultOpenclawConfigState(this, nextContent, {
|
|
195
|
+
path: res.path || '',
|
|
196
|
+
exists: !!res.exists,
|
|
197
|
+
lineEnding: this.openclawLineEnding,
|
|
198
|
+
persist: true
|
|
199
|
+
});
|
|
200
|
+
}
|
|
106
201
|
this.syncOpenclawStructuredFromText({ silent: true });
|
|
107
202
|
if (!silent) {
|
|
108
203
|
this.showMessage('加载完成', 'success');
|
|
@@ -165,6 +260,10 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
165
260
|
if (this.openclawSaving || this.openclawApplying) {
|
|
166
261
|
return;
|
|
167
262
|
}
|
|
263
|
+
if (this.openclawEditing && this.openclawEditing.lockName && this.openclawEditing.name === DEFAULT_OPENCLAW_CONFIG_NAME) {
|
|
264
|
+
this.showMessage('默认配置代表当前系统配置,请使用“保存并应用”', 'info');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
168
267
|
this.openclawSaving = true;
|
|
169
268
|
try {
|
|
170
269
|
const name = this.persistOpenclawConfig();
|
|
@@ -186,7 +285,8 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
186
285
|
const config = this.openclawConfigs[name];
|
|
187
286
|
const res = await api('apply-openclaw-config', {
|
|
188
287
|
content: config.content,
|
|
189
|
-
lineEnding: this.openclawLineEnding
|
|
288
|
+
lineEnding: this.openclawLineEnding,
|
|
289
|
+
authProfileUpdates: Object.values(this.openclawPendingAuthProfileUpdates || {})
|
|
190
290
|
});
|
|
191
291
|
if (res.error || res.success === false) {
|
|
192
292
|
this.showMessage(res.error || '应用配置失败', 'error');
|
|
@@ -194,6 +294,13 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
194
294
|
}
|
|
195
295
|
this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
|
|
196
296
|
this.openclawConfigExists = true;
|
|
297
|
+
syncDefaultOpenclawConfigState(this, config.content, {
|
|
298
|
+
path: this.openclawConfigPath,
|
|
299
|
+
exists: true,
|
|
300
|
+
lineEnding: this.openclawLineEnding,
|
|
301
|
+
persist: true
|
|
302
|
+
});
|
|
303
|
+
this.openclawPendingAuthProfileUpdates = {};
|
|
197
304
|
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
198
305
|
this.showMessage(`已保存并应用 OpenClaw 配置${targetTip}`, 'success');
|
|
199
306
|
this.closeOpenclawConfigModal({ force: true });
|
|
@@ -205,6 +312,9 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
205
312
|
},
|
|
206
313
|
|
|
207
314
|
async deleteOpenclawConfig(name) {
|
|
315
|
+
if (name === DEFAULT_OPENCLAW_CONFIG_NAME) {
|
|
316
|
+
return this.showMessage('默认配置始终映射当前系统配置,不可删除', 'info');
|
|
317
|
+
}
|
|
208
318
|
if (Object.keys(this.openclawConfigs).length <= 1) {
|
|
209
319
|
return this.showMessage('至少保留一项', 'error');
|
|
210
320
|
}
|
|
@@ -233,13 +343,21 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
233
343
|
try {
|
|
234
344
|
const res = await api('apply-openclaw-config', {
|
|
235
345
|
content: config.content,
|
|
236
|
-
lineEnding: this.openclawLineEnding
|
|
346
|
+
lineEnding: this.openclawLineEnding,
|
|
347
|
+
authProfileUpdates: Object.values(this.openclawPendingAuthProfileUpdates || {})
|
|
237
348
|
});
|
|
238
349
|
if (res.error || res.success === false) {
|
|
239
350
|
this.showMessage(res.error || '应用配置失败', 'error');
|
|
240
351
|
} else {
|
|
241
352
|
this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
|
|
242
353
|
this.openclawConfigExists = true;
|
|
354
|
+
syncDefaultOpenclawConfigState(this, config.content, {
|
|
355
|
+
path: this.openclawConfigPath,
|
|
356
|
+
exists: true,
|
|
357
|
+
lineEnding: this.openclawLineEnding,
|
|
358
|
+
persist: true
|
|
359
|
+
});
|
|
360
|
+
this.openclawPendingAuthProfileUpdates = {};
|
|
243
361
|
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
244
362
|
this.showMessage(`已应用 OpenClaw 配置: ${name}${targetTip}`, 'success');
|
|
245
363
|
}
|
|
@@ -1,30 +1,155 @@
|
|
|
1
|
+
const PROVIDER_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
2
|
+
const RESERVED_PROXY_PROVIDER_NAME = 'codexmate-proxy';
|
|
3
|
+
|
|
4
|
+
function normalizeText(value) {
|
|
5
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function normalizeProviderUrl(value) {
|
|
9
|
+
return normalizeText(value).replace(/\/+$/g, '');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isValidHttpUrl(value) {
|
|
13
|
+
if (!value) return false;
|
|
14
|
+
try {
|
|
15
|
+
const parsed = new URL(value);
|
|
16
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
17
|
+
} catch (_) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isReservedProviderCreationNameInput(name) {
|
|
23
|
+
const normalized = normalizeText(name).toLowerCase();
|
|
24
|
+
return normalized === RESERVED_PROXY_PROVIDER_NAME;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isValidProviderNameInputValue(name) {
|
|
28
|
+
return PROVIDER_NAME_PATTERN.test(normalizeText(name));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isValidProviderUrlInputValue(url) {
|
|
32
|
+
return isValidHttpUrl(normalizeProviderUrl(url));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function findProviderByName(list, name) {
|
|
36
|
+
const target = normalizeText(name);
|
|
37
|
+
if (!target) return null;
|
|
38
|
+
return (Array.isArray(list) ? list : []).find((item) => item && normalizeText(item.name) === target) || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeProviderDraftState(target) {
|
|
42
|
+
if (!target || typeof target !== 'object') return;
|
|
43
|
+
if (typeof target.name === 'string') {
|
|
44
|
+
target.name = target.name.trim();
|
|
45
|
+
}
|
|
46
|
+
if (typeof target.url === 'string') {
|
|
47
|
+
target.url = normalizeProviderUrl(target.url);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getProviderValidationForContext(vm, mode = 'add') {
|
|
52
|
+
const draft = mode === 'edit' ? vm.editingProvider : vm.newProvider;
|
|
53
|
+
const editingName = mode === 'edit' ? normalizeText(draft && draft.name) : '';
|
|
54
|
+
const name = normalizeText(draft && draft.name);
|
|
55
|
+
const url = normalizeProviderUrl(draft && draft.url);
|
|
56
|
+
const errors = {
|
|
57
|
+
name: '',
|
|
58
|
+
url: ''
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (mode === 'add') {
|
|
62
|
+
if (!name) {
|
|
63
|
+
errors.name = '名称不能为空';
|
|
64
|
+
} else if (!isValidProviderNameInputValue(name)) {
|
|
65
|
+
errors.name = '名称仅支持字母/数字/._-';
|
|
66
|
+
} else if (isReservedProviderCreationNameInput(name)) {
|
|
67
|
+
errors.name = 'codexmate-proxy 为保留名称,不可手动添加';
|
|
68
|
+
} else if (findProviderByName(vm.providersList, name)) {
|
|
69
|
+
errors.name = '名称已存在';
|
|
70
|
+
}
|
|
71
|
+
} else if (!editingName) {
|
|
72
|
+
errors.name = '提供商名称不能为空';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!url) {
|
|
76
|
+
errors.url = 'URL 必填';
|
|
77
|
+
} else if (!isValidProviderUrlInputValue(url)) {
|
|
78
|
+
errors.url = 'URL 仅支持 http/https';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
mode,
|
|
83
|
+
name,
|
|
84
|
+
url,
|
|
85
|
+
errors,
|
|
86
|
+
ok: !errors.name && !errors.url
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function canSubmitProviderForContext(vm, mode = 'add') {
|
|
91
|
+
if (mode === 'edit' && vm.editingProvider && (vm.editingProvider.readOnly || vm.editingProvider.nonEditable)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return getProviderValidationForContext(vm, mode).ok;
|
|
95
|
+
}
|
|
96
|
+
|
|
1
97
|
export function createProvidersMethods(options = {}) {
|
|
2
98
|
const { api } = options;
|
|
3
99
|
|
|
4
100
|
return {
|
|
101
|
+
normalizeProviderDraft(mode = 'add') {
|
|
102
|
+
normalizeProviderDraftState(mode === 'edit' ? this.editingProvider : this.newProvider);
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
isReservedProviderCreationName(name) {
|
|
106
|
+
return isReservedProviderCreationNameInput(name);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
isValidProviderNameInput(name) {
|
|
110
|
+
return isValidProviderNameInputValue(name);
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
isValidProviderUrlInput(url) {
|
|
114
|
+
return isValidProviderUrlInputValue(url);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
findProviderByName(name) {
|
|
118
|
+
return findProviderByName(this.providersList, name);
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
getProviderValidation(mode = 'add') {
|
|
122
|
+
return getProviderValidationForContext(this, mode);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
providerFieldError(mode, fieldName) {
|
|
126
|
+
const validation = getProviderValidationForContext(this, mode);
|
|
127
|
+
return validation && validation.errors && typeof validation.errors[fieldName] === 'string'
|
|
128
|
+
? validation.errors[fieldName]
|
|
129
|
+
: '';
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
canSubmitProvider(mode = 'add') {
|
|
133
|
+
return canSubmitProviderForContext(this, mode);
|
|
134
|
+
},
|
|
135
|
+
|
|
5
136
|
async addProvider() {
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
if (!
|
|
9
|
-
return this.showMessage('名称和URL必填', 'error');
|
|
10
|
-
}
|
|
11
|
-
const name = rawName.trim();
|
|
12
|
-
if (!name) {
|
|
13
|
-
return this.showMessage('名称不能为空', 'error');
|
|
14
|
-
}
|
|
15
|
-
if (name.toLowerCase() === 'local') {
|
|
16
|
-
return this.showMessage('local provider 为系统保留名称,不可新增', 'error');
|
|
17
|
-
}
|
|
18
|
-
if (this.providersList.some(item => item.name === name)) {
|
|
19
|
-
return this.showMessage('名称已存在', 'error');
|
|
137
|
+
normalizeProviderDraftState(this.newProvider);
|
|
138
|
+
const validation = getProviderValidationForContext(this, 'add');
|
|
139
|
+
if (!validation.ok) {
|
|
140
|
+
return this.showMessage(validation.errors.name || validation.errors.url || '名称和URL必填', 'error');
|
|
20
141
|
}
|
|
21
142
|
|
|
22
143
|
try {
|
|
23
|
-
const
|
|
24
|
-
name,
|
|
25
|
-
url:
|
|
144
|
+
const payload = {
|
|
145
|
+
name: validation.name,
|
|
146
|
+
url: validation.url,
|
|
26
147
|
key: this.newProvider.key || ''
|
|
27
|
-
}
|
|
148
|
+
};
|
|
149
|
+
if (this.newProvider && this.newProvider.useTransform) {
|
|
150
|
+
payload.useTransform = true;
|
|
151
|
+
}
|
|
152
|
+
const res = await api('add-provider', payload);
|
|
28
153
|
if (res.error) {
|
|
29
154
|
this.showMessage(res.error, 'error');
|
|
30
155
|
return;
|
|
@@ -43,22 +168,7 @@ export function createProvidersMethods(options = {}) {
|
|
|
43
168
|
return list.find((item) => !!(item && item.current)) || null;
|
|
44
169
|
},
|
|
45
170
|
|
|
46
|
-
isLocalLikeProvider(providerOrName) {
|
|
47
|
-
if (!providerOrName) return false;
|
|
48
|
-
const rawName = typeof providerOrName === 'object'
|
|
49
|
-
? String(providerOrName.name || '')
|
|
50
|
-
: String(providerOrName);
|
|
51
|
-
const normalized = rawName.trim().toLowerCase();
|
|
52
|
-
return normalized === 'local';
|
|
53
|
-
},
|
|
54
|
-
|
|
55
171
|
providerPillState(provider) {
|
|
56
|
-
if (this.isLocalLikeProvider(provider)) {
|
|
57
|
-
const currentProfile = this.getCurrentCodexAuthProfile();
|
|
58
|
-
return currentProfile
|
|
59
|
-
? { configured: true, text: '已登录' }
|
|
60
|
-
: { configured: false, text: '未登录' };
|
|
61
|
-
}
|
|
62
172
|
const configured = !!(provider && provider.hasKey);
|
|
63
173
|
return {
|
|
64
174
|
configured,
|
|
@@ -88,18 +198,10 @@ export function createProvidersMethods(options = {}) {
|
|
|
88
198
|
isNonDeletableProvider(providerOrName) {
|
|
89
199
|
if (!providerOrName) return false;
|
|
90
200
|
if (typeof providerOrName === 'object') {
|
|
91
|
-
const directName = String(providerOrName.name || '').trim().toLowerCase();
|
|
92
|
-
if (directName === 'local') {
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
201
|
return !!providerOrName.nonDeletable;
|
|
96
202
|
}
|
|
97
203
|
const name = String(providerOrName).trim();
|
|
98
204
|
if (!name) return false;
|
|
99
|
-
const normalized = name.toLowerCase();
|
|
100
|
-
if (normalized === 'local') {
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
205
|
const target = (this.providersList || []).find((item) => item && item.name === name);
|
|
104
206
|
return !!(target && target.nonDeletable);
|
|
105
207
|
},
|
|
@@ -113,7 +215,7 @@ export function createProvidersMethods(options = {}) {
|
|
|
113
215
|
},
|
|
114
216
|
|
|
115
217
|
shouldAllowProviderShare(provider) {
|
|
116
|
-
return !this.isReadOnlyProvider(provider)
|
|
218
|
+
return !this.isReadOnlyProvider(provider);
|
|
117
219
|
},
|
|
118
220
|
|
|
119
221
|
async deleteProvider(name) {
|
|
@@ -138,19 +240,50 @@ export function createProvidersMethods(options = {}) {
|
|
|
138
240
|
}
|
|
139
241
|
},
|
|
140
242
|
|
|
141
|
-
openEditModal(provider) {
|
|
243
|
+
async openEditModal(provider) {
|
|
244
|
+
const requestId = Symbol('openEditModal');
|
|
245
|
+
this._openEditModalRequestId = requestId;
|
|
142
246
|
if (!this.shouldShowProviderEdit(provider)) {
|
|
143
247
|
this.showMessage('该 provider 为保留项,不可编辑', 'info');
|
|
144
248
|
return;
|
|
145
249
|
}
|
|
250
|
+
const isTransformProvider = (() => {
|
|
251
|
+
if (!provider || typeof provider !== 'object') return false;
|
|
252
|
+
const bridge = typeof provider.codexmate_bridge === 'string' ? provider.codexmate_bridge.trim() : '';
|
|
253
|
+
if (bridge === 'openai') return true;
|
|
254
|
+
const url = String(provider.url || '');
|
|
255
|
+
return url.includes('/bridge/openai/');
|
|
256
|
+
})();
|
|
146
257
|
this.editingProvider = {
|
|
147
258
|
name: provider.name,
|
|
148
|
-
url: provider.url || '',
|
|
259
|
+
url: normalizeProviderUrl(provider.url || ''),
|
|
149
260
|
key: '',
|
|
150
261
|
readOnly: !!provider.readOnly,
|
|
151
|
-
nonEditable:
|
|
262
|
+
nonEditable: typeof provider.nonEditable === 'boolean'
|
|
263
|
+
? provider.nonEditable
|
|
264
|
+
: this.isNonDeletableProvider(provider),
|
|
265
|
+
useTransform: isTransformProvider
|
|
152
266
|
};
|
|
153
267
|
this.showEditModal = true;
|
|
268
|
+
|
|
269
|
+
if (isTransformProvider) {
|
|
270
|
+
try {
|
|
271
|
+
const res = await api('openai-bridge-get-provider', { name: provider.name });
|
|
272
|
+
if (
|
|
273
|
+
this._openEditModalRequestId === requestId
|
|
274
|
+
&& this.showEditModal
|
|
275
|
+
&& this.editingProvider
|
|
276
|
+
&& this.editingProvider.name === provider.name
|
|
277
|
+
&& res && !res.error
|
|
278
|
+
&& typeof res.baseUrl === 'string'
|
|
279
|
+
&& res.baseUrl.trim()
|
|
280
|
+
) {
|
|
281
|
+
this.editingProvider.url = normalizeProviderUrl(res.baseUrl);
|
|
282
|
+
}
|
|
283
|
+
} catch (_) {
|
|
284
|
+
// ignore
|
|
285
|
+
}
|
|
286
|
+
}
|
|
154
287
|
},
|
|
155
288
|
|
|
156
289
|
async updateProvider() {
|
|
@@ -159,13 +292,16 @@ export function createProvidersMethods(options = {}) {
|
|
|
159
292
|
this.closeEditModal();
|
|
160
293
|
return;
|
|
161
294
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
295
|
+
normalizeProviderDraftState(this.editingProvider);
|
|
296
|
+
const validation = getProviderValidationForContext(this, 'edit');
|
|
297
|
+
if (!validation.ok) {
|
|
298
|
+
return this.showMessage(validation.errors.name || validation.errors.url || 'URL 必填', 'error');
|
|
165
299
|
}
|
|
166
300
|
|
|
167
|
-
const
|
|
168
|
-
|
|
301
|
+
const params = { name: validation.name, url: validation.url };
|
|
302
|
+
if (this.editingProvider && this.editingProvider.useTransform) {
|
|
303
|
+
params.useTransform = true;
|
|
304
|
+
}
|
|
169
305
|
if (typeof this.editingProvider.key === 'string' && this.editingProvider.key.trim()) {
|
|
170
306
|
params.key = this.editingProvider.key;
|
|
171
307
|
}
|
|
@@ -185,7 +321,7 @@ export function createProvidersMethods(options = {}) {
|
|
|
185
321
|
|
|
186
322
|
closeEditModal() {
|
|
187
323
|
this.showEditModal = false;
|
|
188
|
-
this.editingProvider = { name: '', url: '', key: '', readOnly: false, nonEditable: false };
|
|
324
|
+
this.editingProvider = { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false };
|
|
189
325
|
},
|
|
190
326
|
|
|
191
327
|
async resetConfig() {
|
|
@@ -241,7 +377,7 @@ export function createProvidersMethods(options = {}) {
|
|
|
241
377
|
|
|
242
378
|
closeAddModal() {
|
|
243
379
|
this.showAddModal = false;
|
|
244
|
-
this.newProvider = { name: '', url: '', key: '' };
|
|
380
|
+
this.newProvider = { name: '', url: '', key: '', useTransform: false };
|
|
245
381
|
},
|
|
246
382
|
|
|
247
383
|
closeModelModal() {
|
|
@@ -258,7 +394,10 @@ export function createProvidersMethods(options = {}) {
|
|
|
258
394
|
},
|
|
259
395
|
|
|
260
396
|
displayApiKey(configName) {
|
|
261
|
-
const
|
|
397
|
+
const config = this.claudeConfigs && this.claudeConfigs[configName]
|
|
398
|
+
? this.claudeConfigs[configName]
|
|
399
|
+
: null;
|
|
400
|
+
const key = config ? config.apiKey : '';
|
|
262
401
|
return this.formatKey(key);
|
|
263
402
|
}
|
|
264
403
|
};
|