codexmate 0.0.23 → 0.0.25
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 +32 -9
- package/README.zh.md +33 -9
- package/cli/auth-profiles.js +23 -7
- package/cli/builtin-proxy.js +35 -0
- package/cli/claude-proxy.js +24 -0
- package/cli/doctor-core.js +903 -0
- package/cli/import-skills-url.js +356 -0
- package/cli/openai-bridge.js +51 -4
- package/cli/session-usage.js +8 -2
- package/cli.js +1921 -399
- package/lib/automation.js +404 -0
- package/lib/cli-models-utils.js +0 -40
- package/lib/cli-network-utils.js +28 -2
- package/lib/cli-path-utils.js +21 -5
- package/lib/cli-sessions.js +32 -1
- package/lib/download-artifacts.js +17 -2
- package/lib/mcp-stdio.js +13 -0
- package/package.json +3 -3
- package/plugins/README.md +20 -0
- package/plugins/README.zh-CN.md +20 -0
- package/plugins/prompt-templates/comment-polish/index.mjs +25 -0
- package/plugins/prompt-templates/computed.mjs +253 -0
- package/plugins/prompt-templates/index.mjs +8 -0
- package/plugins/prompt-templates/manifest.mjs +15 -0
- package/plugins/prompt-templates/methods.mjs +619 -0
- package/plugins/prompt-templates/overview.mjs +90 -0
- package/plugins/prompt-templates/ownership.mjs +19 -0
- package/plugins/prompt-templates/rule-ack/index.mjs +21 -0
- package/plugins/prompt-templates/storage.mjs +64 -0
- package/plugins/registry.mjs +16 -0
- package/web-ui/app.js +21 -35
- package/web-ui/index.html +4 -3
- package/web-ui/logic.sessions.mjs +2 -2
- package/web-ui/modules/app.computed.dashboard.mjs +24 -22
- package/web-ui/modules/app.computed.main-tabs.mjs +3 -0
- package/web-ui/modules/app.computed.session.mjs +17 -0
- package/web-ui/modules/app.methods.agents.mjs +91 -3
- package/web-ui/modules/app.methods.codex-config.mjs +153 -164
- package/web-ui/modules/app.methods.install.mjs +28 -0
- package/web-ui/modules/app.methods.navigation.mjs +34 -1
- package/web-ui/modules/app.methods.runtime.mjs +24 -2
- package/web-ui/modules/app.methods.session-actions.mjs +8 -1
- package/web-ui/modules/app.methods.session-browser.mjs +37 -6
- package/web-ui/modules/app.methods.session-trash.mjs +4 -2
- package/web-ui/modules/config-mode.computed.mjs +1 -3
- package/web-ui/modules/i18n.dict.mjs +2055 -0
- package/web-ui/modules/i18n.mjs +2 -1769
- package/web-ui/partials/index/layout-header.html +48 -34
- package/web-ui/partials/index/modal-config-template-agents.html +3 -4
- package/web-ui/partials/index/modal-health-check.html +33 -60
- package/web-ui/partials/index/panel-config-claude.html +35 -15
- package/web-ui/partials/index/panel-config-codex.html +47 -19
- package/web-ui/partials/index/panel-config-openclaw.html +8 -3
- package/web-ui/partials/index/panel-dashboard.html +186 -0
- package/web-ui/partials/index/panel-docs.html +1 -1
- package/web-ui/partials/index/panel-market.html +3 -0
- package/web-ui/partials/index/panel-orchestration.html +3 -0
- package/web-ui/partials/index/panel-plugins.html +16 -10
- package/web-ui/partials/index/panel-sessions.html +8 -3
- package/web-ui/partials/index/panel-settings.html +1 -1
- package/web-ui/partials/index/panel-usage.html +9 -1
- package/web-ui/res/logo-pack.webp +0 -0
- package/web-ui/styles/controls-forms.css +58 -4
- package/web-ui/styles/dashboard.css +274 -0
- package/web-ui/styles/layout-shell.css +3 -2
- package/web-ui/styles/responsive.css +0 -2
- package/web-ui/styles/sessions-list.css +5 -7
- package/web-ui/styles/sessions-toolbar-trash.css +4 -4
- package/web-ui/styles/sessions-usage.css +33 -0
- package/web-ui/styles.css +1 -0
- package/res/logo.png +0 -0
- /package/{res → web-ui/res}/json5.min.js +0 -0
- /package/{res → web-ui/res}/vue.global.prod.js +0 -0
|
@@ -271,53 +271,164 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
271
271
|
});
|
|
272
272
|
},
|
|
273
273
|
|
|
274
|
-
async runHealthCheck() {
|
|
274
|
+
async runHealthCheck(options = {}) {
|
|
275
275
|
this.healthCheckLoading = true;
|
|
276
276
|
this.healthCheckResult = null;
|
|
277
|
-
|
|
277
|
+
this.healthCheckBatchTotal = 0;
|
|
278
|
+
this.healthCheckBatchDone = 0;
|
|
279
|
+
this.healthCheckBatchFailed = 0;
|
|
278
280
|
try {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
281
|
+
const silent = !!(options && options.silent);
|
|
282
|
+
const forceRefresh = !!(options && options.forceRefresh);
|
|
283
|
+
const useDoctor = !!(options && options.doctor);
|
|
284
|
+
if (useDoctor) {
|
|
285
|
+
const res = await api('doctor', {
|
|
286
|
+
lang: this.lang,
|
|
287
|
+
remote: true,
|
|
288
|
+
range: this.sessionsUsageTimeRange,
|
|
289
|
+
targetApp: this.skillsTargetApp,
|
|
290
|
+
includeUsage: true,
|
|
291
|
+
includeTasks: true,
|
|
292
|
+
includeSkills: true,
|
|
293
|
+
includeInstall: true,
|
|
294
|
+
forceRefresh
|
|
295
|
+
});
|
|
296
|
+
if (hasResponseError(res)) {
|
|
297
|
+
this.healthCheckResult = null;
|
|
298
|
+
if (!silent) {
|
|
299
|
+
this.showMessage(getResponseMessage(res, '检查失败'), 'error');
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (res && typeof res === 'object') {
|
|
304
|
+
this.healthCheckResult = res;
|
|
305
|
+
const report = res.report && typeof res.report === 'object' ? res.report : null;
|
|
306
|
+
const summary = report && report.summary && typeof report.summary === 'object' ? report.summary : null;
|
|
307
|
+
const total = summary && Number.isFinite(Number(summary.total))
|
|
308
|
+
? Math.max(0, Math.floor(Number(summary.total)))
|
|
309
|
+
: (Array.isArray(res.issues) ? res.issues.length : 0);
|
|
310
|
+
const errors = summary && Number.isFinite(Number(summary.error)) ? Math.max(0, Math.floor(Number(summary.error))) : 0;
|
|
311
|
+
const warns = summary && Number.isFinite(Number(summary.warn)) ? Math.max(0, Math.floor(Number(summary.warn))) : 0;
|
|
312
|
+
this.healthCheckBatchTotal = total;
|
|
313
|
+
this.healthCheckBatchDone = total;
|
|
314
|
+
this.healthCheckBatchFailed = errors + warns;
|
|
315
|
+
if (!silent && res.ok) {
|
|
316
|
+
this.showMessage('检查通过', 'success');
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this.healthCheckResult = null;
|
|
321
|
+
if (!silent) {
|
|
322
|
+
this.showMessage('检查失败', 'error');
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (this.configMode === 'claude') {
|
|
328
|
+
const entries = Object.entries(this.claudeConfigs || {});
|
|
329
|
+
this.healthCheckBatchTotal = entries.length;
|
|
330
|
+
|
|
331
|
+
const speedTasks = entries.map(([name, config]) => this.runClaudeSpeedTest(name, config)
|
|
332
|
+
.then((result) => {
|
|
333
|
+
if (!result || result.ok !== true) {
|
|
334
|
+
this.healthCheckBatchFailed += 1;
|
|
335
|
+
}
|
|
336
|
+
return { name, result };
|
|
337
|
+
})
|
|
338
|
+
.catch((err) => {
|
|
339
|
+
this.healthCheckBatchFailed += 1;
|
|
340
|
+
return {
|
|
341
|
+
name,
|
|
342
|
+
result: { ok: false, error: err && err.message ? err.message : 'Speed test failed' }
|
|
343
|
+
};
|
|
344
|
+
})
|
|
345
|
+
.finally(() => {
|
|
346
|
+
this.healthCheckBatchDone += 1;
|
|
347
|
+
})
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const pairs = await Promise.all(speedTasks);
|
|
351
|
+
const results = {};
|
|
352
|
+
const issues = [];
|
|
353
|
+
for (const pair of pairs) {
|
|
354
|
+
results[pair.name] = pair.result || null;
|
|
355
|
+
if (typeof this.buildSpeedTestIssue === 'function') {
|
|
356
|
+
const issue = this.buildSpeedTestIssue(pair.name, pair.result);
|
|
357
|
+
if (issue) issues.push(issue);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const ok = issues.length === 0 && this.healthCheckBatchFailed === 0;
|
|
361
|
+
this.healthCheckResult = {
|
|
362
|
+
ok,
|
|
363
|
+
issues,
|
|
364
|
+
remote: {
|
|
365
|
+
type: 'speed-test',
|
|
366
|
+
speedTests: results
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
if (ok && !silent) {
|
|
370
|
+
this.showMessage('检查通过', 'success');
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const shouldRunSpeedTests = this.configMode === 'codex';
|
|
376
|
+
const speedTimeoutMs = shouldRunSpeedTests ? 3500 : 0;
|
|
377
|
+
const providers = shouldRunSpeedTests
|
|
378
|
+
? (this.providersList || [])
|
|
379
|
+
.map((provider) => typeof provider === 'string'
|
|
380
|
+
? provider.trim()
|
|
381
|
+
: String((provider && provider.name) || '').trim())
|
|
382
|
+
.filter(Boolean)
|
|
383
|
+
: [];
|
|
384
|
+
const currentProvider = String(this.currentProvider || '').trim();
|
|
385
|
+
const orderedProviders = currentProvider && providers.includes(currentProvider)
|
|
386
|
+
? [currentProvider, ...providers.filter((name) => name !== currentProvider)]
|
|
387
|
+
: providers;
|
|
388
|
+
this.healthCheckBatchTotal = orderedProviders.length;
|
|
389
|
+
|
|
390
|
+
const speedTasks = orderedProviders.map((provider) => this.runSpeedTest(provider, { silent: true, timeoutMs: speedTimeoutMs })
|
|
391
|
+
.then((result) => {
|
|
392
|
+
if (!result || result.ok !== true) {
|
|
393
|
+
this.healthCheckBatchFailed += 1;
|
|
394
|
+
}
|
|
395
|
+
return { name: provider, result };
|
|
396
|
+
})
|
|
397
|
+
.catch((err) => {
|
|
398
|
+
this.healthCheckBatchFailed += 1;
|
|
399
|
+
return {
|
|
400
|
+
name: provider,
|
|
401
|
+
result: { ok: false, error: err && err.message ? err.message : 'Speed test failed' }
|
|
402
|
+
};
|
|
403
|
+
})
|
|
404
|
+
.finally(() => {
|
|
405
|
+
this.healthCheckBatchDone += 1;
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const configTask = api('config-health-check', { remote: this.configMode === 'codex' });
|
|
410
|
+
const [res, pairs] = await Promise.all([
|
|
411
|
+
configTask,
|
|
412
|
+
Promise.all(speedTasks)
|
|
413
|
+
]);
|
|
282
414
|
if (hasResponseError(res)) {
|
|
283
415
|
this.healthCheckResult = null;
|
|
284
|
-
|
|
416
|
+
if (!silent) {
|
|
417
|
+
this.showMessage(getResponseMessage(res, '检查失败'), 'error');
|
|
418
|
+
}
|
|
285
419
|
} else if (res && typeof res === 'object') {
|
|
286
|
-
shouldRunClaudeSpeedTests = true;
|
|
287
420
|
const issues = Array.isArray(res.issues) ? [...res.issues] : [];
|
|
288
421
|
let remote = res.remote || null;
|
|
289
|
-
{
|
|
290
|
-
const providers = (this.providersList || [])
|
|
291
|
-
.map((provider) => typeof provider === 'string'
|
|
292
|
-
? provider.trim()
|
|
293
|
-
: String((provider && provider.name) || '').trim())
|
|
294
|
-
.filter(Boolean);
|
|
295
|
-
const tasks = providers.map(provider =>
|
|
296
|
-
this.runSpeedTest(provider, { silent: true })
|
|
297
|
-
.then(result => ({ name: provider, result }))
|
|
298
|
-
.catch(err => ({
|
|
299
|
-
name: provider,
|
|
300
|
-
result: { ok: false, error: err && err.message ? err.message : 'Speed test failed' }
|
|
301
|
-
}))
|
|
302
|
-
);
|
|
303
|
-
const pairs = await Promise.all(tasks);
|
|
422
|
+
if (shouldRunSpeedTests) {
|
|
304
423
|
const results = {};
|
|
305
424
|
for (const pair of pairs) {
|
|
306
425
|
results[pair.name] = pair.result || null;
|
|
307
426
|
const issue = this.buildSpeedTestIssue(pair.name, pair.result);
|
|
308
427
|
if (issue) issues.push(issue);
|
|
309
428
|
}
|
|
310
|
-
|
|
311
|
-
remote
|
|
312
|
-
|
|
313
|
-
speedTests: results
|
|
314
|
-
};
|
|
315
|
-
} else {
|
|
316
|
-
remote = {
|
|
317
|
-
type: 'speed-test',
|
|
318
|
-
speedTests: results
|
|
319
|
-
};
|
|
320
|
-
}
|
|
429
|
+
remote = remote && typeof remote === 'object'
|
|
430
|
+
? { ...remote, speedTests: results }
|
|
431
|
+
: { type: 'speed-test', speedTests: results };
|
|
321
432
|
}
|
|
322
433
|
|
|
323
434
|
const ok = issues.length === 0;
|
|
@@ -327,146 +438,24 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
327
438
|
issues,
|
|
328
439
|
remote
|
|
329
440
|
};
|
|
330
|
-
if (ok) {
|
|
441
|
+
if (ok && !silent) {
|
|
331
442
|
this.showMessage('检查通过', 'success');
|
|
332
443
|
}
|
|
333
444
|
} else {
|
|
334
445
|
this.healthCheckResult = null;
|
|
335
|
-
|
|
446
|
+
if (!silent) {
|
|
447
|
+
this.showMessage('检查失败', 'error');
|
|
448
|
+
}
|
|
336
449
|
}
|
|
337
450
|
} catch (e) {
|
|
338
451
|
this.healthCheckResult = null;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (shouldRunClaudeSpeedTests && this.configMode === 'claude') {
|
|
342
|
-
try {
|
|
343
|
-
const entries = Object.entries(this.claudeConfigs || {});
|
|
344
|
-
await Promise.all(entries.map(([name, config]) => this.runClaudeSpeedTest(name, config)));
|
|
345
|
-
} catch (e) {}
|
|
346
|
-
}
|
|
347
|
-
this.healthCheckLoading = false;
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
|
|
351
|
-
buildDefaultHealthCheckPrompt() {
|
|
352
|
-
return '请简短回复:连接正常。';
|
|
353
|
-
},
|
|
354
|
-
|
|
355
|
-
openHealthCheckDialog(options = {}) {
|
|
356
|
-
const providerName = typeof options.providerName === 'string'
|
|
357
|
-
? options.providerName.trim()
|
|
358
|
-
: '';
|
|
359
|
-
const locked = !!options.locked && !!providerName;
|
|
360
|
-
if (locked && providerName && providerName !== String(this.currentProvider || '').trim()) {
|
|
361
|
-
if (typeof this.showMessage === 'function') {
|
|
362
|
-
this.showMessage('请先切换到该提供商再进行健康聊天测试', 'info');
|
|
363
|
-
}
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
const nextProvider = providerName
|
|
367
|
-
|| String(this.healthCheckDialogSelectedProvider || '').trim()
|
|
368
|
-
|| String(this.currentProvider || '').trim()
|
|
369
|
-
|| String(((this.displayProvidersList || [])[0] || {}).name || '').trim();
|
|
370
|
-
|
|
371
|
-
this.showHealthCheckDialog = true;
|
|
372
|
-
this.healthCheckDialogLockedProvider = locked ? nextProvider : '';
|
|
373
|
-
this.healthCheckDialogSelectedProvider = nextProvider;
|
|
374
|
-
this.healthCheckDialogPrompt = this.buildDefaultHealthCheckPrompt();
|
|
375
|
-
this.healthCheckDialogMessages = [];
|
|
376
|
-
this.healthCheckDialogLastResult = null;
|
|
377
|
-
},
|
|
378
|
-
|
|
379
|
-
closeHealthCheckDialog(options = {}) {
|
|
380
|
-
if (this.healthCheckDialogSending && !options.force) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
this.showHealthCheckDialog = false;
|
|
384
|
-
this.healthCheckDialogLockedProvider = '';
|
|
385
|
-
this.healthCheckDialogSelectedProvider = '';
|
|
386
|
-
this.healthCheckDialogPrompt = this.buildDefaultHealthCheckPrompt();
|
|
387
|
-
this.healthCheckDialogMessages = [];
|
|
388
|
-
this.healthCheckDialogLastResult = null;
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
async sendHealthCheckDialogMessage() {
|
|
392
|
-
if (this.healthCheckDialogSending) {
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const provider = String(
|
|
397
|
-
this.healthCheckDialogLockedProvider || this.healthCheckDialogSelectedProvider || ''
|
|
398
|
-
).trim();
|
|
399
|
-
const prompt = String(this.healthCheckDialogPrompt || '').trim();
|
|
400
|
-
if (!provider) {
|
|
401
|
-
this.showMessage('请先选择提供商', 'error');
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
if (!prompt) {
|
|
405
|
-
this.showMessage('请输入消息内容', 'error');
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
this.healthCheckDialogMessages.push({
|
|
410
|
-
id: `user-${Date.now()}`,
|
|
411
|
-
role: 'user',
|
|
412
|
-
text: prompt
|
|
413
|
-
});
|
|
414
|
-
this.healthCheckDialogSending = true;
|
|
415
|
-
this.healthCheckDialogLastResult = null;
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
const res = await api('provider-chat-check', {
|
|
419
|
-
name: provider,
|
|
420
|
-
prompt
|
|
421
|
-
});
|
|
422
|
-
this.healthCheckDialogLastResult = res;
|
|
423
|
-
|
|
424
|
-
if (hasResponseError(res) || res.ok === false) {
|
|
425
|
-
const message = getResponseMessage(res, '健康聊天测试失败');
|
|
426
|
-
this.healthCheckDialogMessages.push({
|
|
427
|
-
id: `assistant-${Date.now()}`,
|
|
428
|
-
role: 'assistant',
|
|
429
|
-
text: message,
|
|
430
|
-
ok: false,
|
|
431
|
-
status: Number.isFinite(res && res.status) ? res.status : 0,
|
|
432
|
-
durationMs: Number.isFinite(res && res.durationMs) ? res.durationMs : 0,
|
|
433
|
-
model: typeof (res && res.model) === 'string' ? res.model : '',
|
|
434
|
-
rawPreview: typeof (res && res.rawPreview) === 'string' ? res.rawPreview : ''
|
|
435
|
-
});
|
|
436
|
-
this.showMessage(message, 'error');
|
|
437
|
-
return;
|
|
452
|
+
if (!(options && options.silent)) {
|
|
453
|
+
this.showMessage('检查失败', 'error');
|
|
438
454
|
}
|
|
439
|
-
|
|
440
|
-
const reply = typeof res.reply === 'string' && res.reply.trim()
|
|
441
|
-
? res.reply.trim()
|
|
442
|
-
: '已收到回复,但未解析到可展示文本。';
|
|
443
|
-
this.healthCheckDialogMessages.push({
|
|
444
|
-
id: `assistant-${Date.now()}`,
|
|
445
|
-
role: 'assistant',
|
|
446
|
-
text: reply,
|
|
447
|
-
ok: true,
|
|
448
|
-
status: Number.isFinite(res.status) ? res.status : 0,
|
|
449
|
-
durationMs: Number.isFinite(res.durationMs) ? res.durationMs : 0,
|
|
450
|
-
model: typeof res.model === 'string' ? res.model : '',
|
|
451
|
-
rawPreview: typeof res.rawPreview === 'string' ? res.rawPreview : ''
|
|
452
|
-
});
|
|
453
|
-
this.healthCheckDialogPrompt = '';
|
|
454
|
-
} catch (e) {
|
|
455
|
-
const message = e && e.message ? e.message : '健康聊天测试失败';
|
|
456
|
-
this.healthCheckDialogMessages.push({
|
|
457
|
-
id: `assistant-${Date.now()}`,
|
|
458
|
-
role: 'assistant',
|
|
459
|
-
text: message,
|
|
460
|
-
ok: false,
|
|
461
|
-
status: 0,
|
|
462
|
-
durationMs: 0,
|
|
463
|
-
model: '',
|
|
464
|
-
rawPreview: ''
|
|
465
|
-
});
|
|
466
|
-
this.healthCheckDialogLastResult = { ok: false, error: message };
|
|
467
|
-
this.showMessage(message, 'error');
|
|
468
455
|
} finally {
|
|
469
|
-
this.
|
|
456
|
+
this.healthCheckBatchTotal = this.healthCheckBatchTotal || 0;
|
|
457
|
+
this.healthCheckBatchDone = Math.min(this.healthCheckBatchDone || 0, this.healthCheckBatchTotal || 0);
|
|
458
|
+
this.healthCheckLoading = false;
|
|
470
459
|
}
|
|
471
460
|
},
|
|
472
461
|
|
|
@@ -104,6 +104,16 @@ export function createInstallMethods() {
|
|
|
104
104
|
update: '',
|
|
105
105
|
uninstall: ''
|
|
106
106
|
},
|
|
107
|
+
codebuddy: {
|
|
108
|
+
install: '',
|
|
109
|
+
update: '',
|
|
110
|
+
uninstall: ''
|
|
111
|
+
},
|
|
112
|
+
gemini: {
|
|
113
|
+
install: '',
|
|
114
|
+
update: '',
|
|
115
|
+
uninstall: ''
|
|
116
|
+
},
|
|
107
117
|
codex: {
|
|
108
118
|
install: '',
|
|
109
119
|
update: '',
|
|
@@ -114,6 +124,12 @@ export function createInstallMethods() {
|
|
|
114
124
|
matrix.claude.install = 'pnpm add -g @anthropic-ai/claude-code';
|
|
115
125
|
matrix.claude.update = 'pnpm up -g @anthropic-ai/claude-code';
|
|
116
126
|
matrix.claude.uninstall = 'pnpm remove -g @anthropic-ai/claude-code';
|
|
127
|
+
matrix.codebuddy.install = 'pnpm add -g @tencent-ai/codebuddy-code';
|
|
128
|
+
matrix.codebuddy.update = 'pnpm up -g @tencent-ai/codebuddy-code';
|
|
129
|
+
matrix.codebuddy.uninstall = 'pnpm remove -g @tencent-ai/codebuddy-code';
|
|
130
|
+
matrix.gemini.install = 'pnpm add -g @google/gemini-cli';
|
|
131
|
+
matrix.gemini.update = 'pnpm up -g @google/gemini-cli';
|
|
132
|
+
matrix.gemini.uninstall = 'pnpm remove -g @google/gemini-cli';
|
|
117
133
|
matrix.codex.install = `pnpm add -g ${codexInstallPackage}`;
|
|
118
134
|
matrix.codex.update = `pnpm up -g ${codexPackage}`;
|
|
119
135
|
matrix.codex.uninstall = `pnpm remove -g ${codexPackage}`;
|
|
@@ -123,6 +139,12 @@ export function createInstallMethods() {
|
|
|
123
139
|
matrix.claude.install = 'bun add -g @anthropic-ai/claude-code';
|
|
124
140
|
matrix.claude.update = 'bun update -g @anthropic-ai/claude-code';
|
|
125
141
|
matrix.claude.uninstall = 'bun remove -g @anthropic-ai/claude-code';
|
|
142
|
+
matrix.codebuddy.install = 'bun add -g @tencent-ai/codebuddy-code';
|
|
143
|
+
matrix.codebuddy.update = 'bun update -g @tencent-ai/codebuddy-code';
|
|
144
|
+
matrix.codebuddy.uninstall = 'bun remove -g @tencent-ai/codebuddy-code';
|
|
145
|
+
matrix.gemini.install = 'bun add -g @google/gemini-cli';
|
|
146
|
+
matrix.gemini.update = 'bun update -g @google/gemini-cli';
|
|
147
|
+
matrix.gemini.uninstall = 'bun remove -g @google/gemini-cli';
|
|
126
148
|
matrix.codex.install = `bun add -g ${codexInstallPackage}`;
|
|
127
149
|
matrix.codex.update = `bun update -g ${codexPackage}`;
|
|
128
150
|
matrix.codex.uninstall = `bun remove -g ${codexPackage}`;
|
|
@@ -131,6 +153,12 @@ export function createInstallMethods() {
|
|
|
131
153
|
matrix.claude.install = 'npm install -g @anthropic-ai/claude-code';
|
|
132
154
|
matrix.claude.update = 'npm update -g @anthropic-ai/claude-code';
|
|
133
155
|
matrix.claude.uninstall = 'npm uninstall -g @anthropic-ai/claude-code';
|
|
156
|
+
matrix.codebuddy.install = 'npm install -g @tencent-ai/codebuddy-code';
|
|
157
|
+
matrix.codebuddy.update = 'npm update -g @tencent-ai/codebuddy-code';
|
|
158
|
+
matrix.codebuddy.uninstall = 'npm uninstall -g @tencent-ai/codebuddy-code';
|
|
159
|
+
matrix.gemini.install = 'npm install -g @google/gemini-cli';
|
|
160
|
+
matrix.gemini.update = 'npm update -g @google/gemini-cli';
|
|
161
|
+
matrix.gemini.uninstall = 'npm uninstall -g @google/gemini-cli';
|
|
134
162
|
matrix.codex.install = `npm install -g ${codexInstallPackage}`;
|
|
135
163
|
matrix.codex.update = platform === 'termux'
|
|
136
164
|
? `npm install -g ${codexInstallPackage}`
|
|
@@ -6,6 +6,7 @@ export function createNavigationMethods(options = {}) {
|
|
|
6
6
|
} = options;
|
|
7
7
|
const NAV_STATE_STORAGE_KEY = 'codexmateNavState.v1';
|
|
8
8
|
const MAIN_TAB_SET = new Set([
|
|
9
|
+
'dashboard',
|
|
9
10
|
'config',
|
|
10
11
|
'sessions',
|
|
11
12
|
'usage',
|
|
@@ -15,6 +16,29 @@ export function createNavigationMethods(options = {}) {
|
|
|
15
16
|
'docs',
|
|
16
17
|
'settings'
|
|
17
18
|
]);
|
|
19
|
+
const loadDoctorOverview = async (vm, options = {}) => {
|
|
20
|
+
if (!vm || typeof vm !== 'object') return false;
|
|
21
|
+
if (vm.__doctorLoading) return false;
|
|
22
|
+
const forceRefresh = !!(options && options.forceRefresh);
|
|
23
|
+
vm.__doctorLoading = true;
|
|
24
|
+
let ok = true;
|
|
25
|
+
try {
|
|
26
|
+
if (typeof vm.runHealthCheck === 'function') {
|
|
27
|
+
await vm.runHealthCheck({ doctor: true, silent: true, forceRefresh });
|
|
28
|
+
}
|
|
29
|
+
vm.__doctorLoadedOnce = true;
|
|
30
|
+
return true;
|
|
31
|
+
} catch (_) {
|
|
32
|
+
ok = false;
|
|
33
|
+
vm.__doctorLoadedOnce = true;
|
|
34
|
+
return false;
|
|
35
|
+
} finally {
|
|
36
|
+
vm.__doctorLoading = false;
|
|
37
|
+
if (!ok) {
|
|
38
|
+
vm.__doctorLoadedOnce = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
18
42
|
const readNavState = () => {
|
|
19
43
|
if (typeof localStorage === 'undefined') return null;
|
|
20
44
|
let raw = '';
|
|
@@ -37,7 +61,7 @@ export function createNavigationMethods(options = {}) {
|
|
|
37
61
|
const mainTab = typeof vm.mainTab === 'string' ? vm.mainTab.trim().toLowerCase() : '';
|
|
38
62
|
const configMode = typeof vm.configMode === 'string' ? vm.configMode.trim().toLowerCase() : '';
|
|
39
63
|
const snapshot = {
|
|
40
|
-
mainTab: MAIN_TAB_SET.has(mainTab) ? mainTab : '
|
|
64
|
+
mainTab: MAIN_TAB_SET.has(mainTab) ? mainTab : 'dashboard',
|
|
41
65
|
configMode: configModeSet && configModeSet.has(configMode) ? configMode : 'codex'
|
|
42
66
|
};
|
|
43
67
|
try {
|
|
@@ -341,6 +365,9 @@ export function createNavigationMethods(options = {}) {
|
|
|
341
365
|
if (targetTab === previousTab) {
|
|
342
366
|
switchState.ticket += 1;
|
|
343
367
|
switchState.pendingTarget = '';
|
|
368
|
+
if (targetTab === 'dashboard' && !this.__doctorLoadedOnce) {
|
|
369
|
+
void loadDoctorOverview(this);
|
|
370
|
+
}
|
|
344
371
|
if (
|
|
345
372
|
targetTab === 'sessions'
|
|
346
373
|
&& typeof this.prepareSessionTabRender === 'function'
|
|
@@ -366,6 +393,9 @@ export function createNavigationMethods(options = {}) {
|
|
|
366
393
|
switchState.pendingTarget = '';
|
|
367
394
|
const result = switchMainTabHelper.call(this, targetTab);
|
|
368
395
|
persistNavState(this);
|
|
396
|
+
if (targetTab === 'dashboard') {
|
|
397
|
+
void loadDoctorOverview(this);
|
|
398
|
+
}
|
|
369
399
|
this.scheduleAfterFrame(() => {
|
|
370
400
|
this.clearMainTabSwitchIntent(normalizedTab);
|
|
371
401
|
});
|
|
@@ -381,6 +411,9 @@ export function createNavigationMethods(options = {}) {
|
|
|
381
411
|
liveState.pendingTarget = '';
|
|
382
412
|
switchMainTabHelper.call(this, pendingTarget);
|
|
383
413
|
persistNavState(this);
|
|
414
|
+
if (pendingTarget === 'dashboard') {
|
|
415
|
+
void loadDoctorOverview(this);
|
|
416
|
+
}
|
|
384
417
|
this.clearMainTabSwitchIntent(normalizedTab);
|
|
385
418
|
});
|
|
386
419
|
},
|
|
@@ -37,7 +37,12 @@ export function createRuntimeMethods(options = {}) {
|
|
|
37
37
|
const silent = !!options.silent;
|
|
38
38
|
this.speedLoading[name] = true;
|
|
39
39
|
try {
|
|
40
|
-
const
|
|
40
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) ? Math.max(1000, Number(options.timeoutMs)) : 0;
|
|
41
|
+
const payload = { name };
|
|
42
|
+
if (timeoutMs) {
|
|
43
|
+
payload.timeoutMs = timeoutMs;
|
|
44
|
+
}
|
|
45
|
+
const res = await api('speed-test', payload);
|
|
41
46
|
if (res.error) {
|
|
42
47
|
this.speedResults[name] = { ok: false, error: res.error };
|
|
43
48
|
if (!silent) {
|
|
@@ -66,6 +71,8 @@ export function createRuntimeMethods(options = {}) {
|
|
|
66
71
|
async runClaudeSpeedTest(name, config) {
|
|
67
72
|
if (!name || this.claudeSpeedLoading[name]) return null;
|
|
68
73
|
const baseUrl = config && typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
|
|
74
|
+
const apiKey = config && typeof config.apiKey === 'string' ? config.apiKey.trim() : '';
|
|
75
|
+
const model = config && typeof config.model === 'string' ? config.model.trim() : '';
|
|
69
76
|
this.claudeSpeedLoading[name] = true;
|
|
70
77
|
try {
|
|
71
78
|
if (!baseUrl) {
|
|
@@ -73,7 +80,22 @@ export function createRuntimeMethods(options = {}) {
|
|
|
73
80
|
this.claudeSpeedResults[name] = res;
|
|
74
81
|
return res;
|
|
75
82
|
}
|
|
76
|
-
|
|
83
|
+
if (!apiKey) {
|
|
84
|
+
const res = { ok: false, error: 'Missing API key' };
|
|
85
|
+
this.claudeSpeedResults[name] = res;
|
|
86
|
+
return res;
|
|
87
|
+
}
|
|
88
|
+
if (!model) {
|
|
89
|
+
const res = { ok: false, error: 'Missing model' };
|
|
90
|
+
this.claudeSpeedResults[name] = res;
|
|
91
|
+
return res;
|
|
92
|
+
}
|
|
93
|
+
const res = await api('speed-test', {
|
|
94
|
+
kind: 'claude',
|
|
95
|
+
url: baseUrl,
|
|
96
|
+
apiKey,
|
|
97
|
+
model
|
|
98
|
+
});
|
|
77
99
|
if (res.error) {
|
|
78
100
|
this.claudeSpeedResults[name] = { ok: false, error: res.error };
|
|
79
101
|
return { ok: false, error: res.error };
|
|
@@ -117,7 +117,7 @@ export function createSessionActionMethods(options = {}) {
|
|
|
117
117
|
if (!session) return false;
|
|
118
118
|
const source = String(session.source || '').trim().toLowerCase();
|
|
119
119
|
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
120
|
-
return source === 'codex' && !!sessionId;
|
|
120
|
+
return (source === 'codex' || source === 'codebuddy' || source === 'gemini') && !!sessionId;
|
|
121
121
|
},
|
|
122
122
|
|
|
123
123
|
isCloneAvailable(session) {
|
|
@@ -138,8 +138,15 @@ export function createSessionActionMethods(options = {}) {
|
|
|
138
138
|
},
|
|
139
139
|
|
|
140
140
|
buildResumeCommand(session) {
|
|
141
|
+
const source = session && session.source ? String(session.source).trim().toLowerCase() : '';
|
|
141
142
|
const sessionId = session && session.sessionId ? String(session.sessionId).trim() : '';
|
|
142
143
|
const arg = this.quoteResumeArg(sessionId);
|
|
144
|
+
if (source === 'codebuddy') {
|
|
145
|
+
return `codebuddy -r ${arg}`;
|
|
146
|
+
}
|
|
147
|
+
if (source === 'gemini') {
|
|
148
|
+
return `gemini -r ${arg}`;
|
|
149
|
+
}
|
|
143
150
|
if (this.sessionResumeWithYolo) {
|
|
144
151
|
return `codex --yolo resume ${arg}`;
|
|
145
152
|
}
|
|
@@ -127,7 +127,9 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
127
127
|
},
|
|
128
128
|
|
|
129
129
|
syncSessionPathOptionsForSource(source, nextOptions, mergeWithExisting = false) {
|
|
130
|
-
const targetSource = source === 'claude'
|
|
130
|
+
const targetSource = source === 'claude'
|
|
131
|
+
? 'claude'
|
|
132
|
+
: (source === 'gemini' ? 'gemini' : (source === 'all' ? 'all' : 'codex'));
|
|
131
133
|
const current = Array.isArray(this.sessionPathOptionsMap[targetSource])
|
|
132
134
|
? this.sessionPathOptionsMap[targetSource]
|
|
133
135
|
: [];
|
|
@@ -142,7 +144,9 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
142
144
|
},
|
|
143
145
|
|
|
144
146
|
refreshSessionPathOptions(source) {
|
|
145
|
-
const targetSource = source === 'claude'
|
|
147
|
+
const targetSource = source === 'claude'
|
|
148
|
+
? 'claude'
|
|
149
|
+
: (source === 'gemini' ? 'gemini' : (source === 'all' ? 'all' : 'codex'));
|
|
146
150
|
const base = Array.isArray(this.sessionPathOptionsMap[targetSource])
|
|
147
151
|
? [...this.sessionPathOptionsMap[targetSource]]
|
|
148
152
|
: [];
|
|
@@ -164,7 +168,9 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
164
168
|
},
|
|
165
169
|
|
|
166
170
|
async loadSessionPathOptions(options = {}) {
|
|
167
|
-
const source = options.source === 'claude'
|
|
171
|
+
const source = options.source === 'claude'
|
|
172
|
+
? 'claude'
|
|
173
|
+
: (options.source === 'gemini' ? 'gemini' : (options.source === 'all' ? 'all' : 'codex'));
|
|
168
174
|
const forceRefresh = !!options.forceRefresh;
|
|
169
175
|
const loaded = !!this.sessionPathOptionsLoadedMap[source];
|
|
170
176
|
if (!forceRefresh && loaded) {
|
|
@@ -252,6 +258,9 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
252
258
|
const urlState = readSessionsFilterUrlState();
|
|
253
259
|
if (urlState) {
|
|
254
260
|
applySessionsFilterUrlState(this, urlState);
|
|
261
|
+
if (this.mainTab === 'sessions' && typeof this.loadSessions === 'function') {
|
|
262
|
+
void this.loadSessions();
|
|
263
|
+
}
|
|
255
264
|
return;
|
|
256
265
|
}
|
|
257
266
|
const sourceCache = localStorage.getItem('codexmateSessionFilterSource');
|
|
@@ -266,6 +275,16 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
266
275
|
this.sessionRoleFilter = normalizeSessionRoleFilter(roleCache);
|
|
267
276
|
this.sessionTimePreset = normalizeSessionTimePreset(timeCache);
|
|
268
277
|
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
278
|
+
if (this.mainTab === 'sessions' && typeof this.loadSessions === 'function') {
|
|
279
|
+
const shouldReload = cached.source !== 'all'
|
|
280
|
+
|| !!cached.pathFilter
|
|
281
|
+
|| !!(this.sessionQuery && isSessionQueryEnabled(cached.source))
|
|
282
|
+
|| (this.sessionRoleFilter && this.sessionRoleFilter !== 'all')
|
|
283
|
+
|| (this.sessionTimePreset && this.sessionTimePreset !== 'all');
|
|
284
|
+
if (shouldReload) {
|
|
285
|
+
void this.loadSessions();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
269
288
|
},
|
|
270
289
|
|
|
271
290
|
persistSessionFilterCache() {
|
|
@@ -412,7 +431,12 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
412
431
|
this.persistSessionPinnedMap();
|
|
413
432
|
},
|
|
414
433
|
|
|
415
|
-
async onSessionSourceChange() {
|
|
434
|
+
async onSessionSourceChange(event) {
|
|
435
|
+
const rawValue = event && event.target && typeof event.target.value === 'string'
|
|
436
|
+
? event.target.value
|
|
437
|
+
: this.sessionFilterSource;
|
|
438
|
+
const cached = buildSessionFilterCacheState(rawValue, this.sessionPathFilter);
|
|
439
|
+
this.sessionFilterSource = cached.source;
|
|
416
440
|
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
417
441
|
this.persistSessionFilterCache();
|
|
418
442
|
syncSessionsFilterUrl(this);
|
|
@@ -442,12 +466,19 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
442
466
|
await this.onSessionSourceChange();
|
|
443
467
|
},
|
|
444
468
|
|
|
445
|
-
copySessionsFilterShareUrl() {
|
|
469
|
+
async copySessionsFilterShareUrl() {
|
|
446
470
|
const url = buildSessionsFilterShareUrl(this);
|
|
447
471
|
if (!url) {
|
|
448
|
-
this.showMessage('
|
|
472
|
+
this.showMessage(typeof this.t === 'function' ? this.t('sessions.filters.urlBuildFail') : 'Failed to build link', 'error');
|
|
449
473
|
return;
|
|
450
474
|
}
|
|
475
|
+
try {
|
|
476
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
477
|
+
await navigator.clipboard.writeText(url);
|
|
478
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.ok') : 'Copied', 'success');
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
} catch (_) {}
|
|
451
482
|
const ok = typeof this.fallbackCopyText === 'function' ? this.fallbackCopyText(url) : false;
|
|
452
483
|
if (ok) {
|
|
453
484
|
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.ok') : 'Copied', 'success');
|