codexmate 0.0.28 → 0.0.30

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 (50) hide show
  1. package/cli/builtin-proxy.js +107 -2
  2. package/cli/config-bootstrap.js +30 -12
  3. package/cli/config-health.js +117 -1
  4. package/cli/local-bridge.js +324 -0
  5. package/cli/openai-bridge.js +195 -31
  6. package/cli.js +245 -28
  7. package/lib/cli-webhook.js +126 -0
  8. package/package.json +1 -1
  9. package/web-ui/app.js +28 -8
  10. package/web-ui/index.html +1 -0
  11. package/web-ui/logic.codex.mjs +13 -0
  12. package/web-ui/modules/app.computed.dashboard.mjs +25 -2
  13. package/web-ui/modules/app.computed.session.mjs +22 -17
  14. package/web-ui/modules/app.methods.claude-config.mjs +12 -2
  15. package/web-ui/modules/app.methods.codex-config.mjs +25 -0
  16. package/web-ui/modules/app.methods.index.mjs +2 -0
  17. package/web-ui/modules/app.methods.navigation.mjs +39 -8
  18. package/web-ui/modules/app.methods.providers.mjs +125 -8
  19. package/web-ui/modules/app.methods.session-actions.mjs +1 -1
  20. package/web-ui/modules/app.methods.session-browser.mjs +1 -1
  21. package/web-ui/modules/app.methods.session-trash.mjs +3 -4
  22. package/web-ui/modules/app.methods.startup-claude.mjs +1 -0
  23. package/web-ui/modules/app.methods.webhook.mjs +79 -0
  24. package/web-ui/modules/i18n.dict.mjs +1109 -72
  25. package/web-ui/modules/i18n.mjs +9 -3
  26. package/web-ui/modules/skills.methods.mjs +1 -0
  27. package/web-ui/partials/index/layout-header.html +25 -0
  28. package/web-ui/partials/index/modals-basic.html +0 -3
  29. package/web-ui/partials/index/panel-config-claude.html +8 -2
  30. package/web-ui/partials/index/panel-config-codex.html +28 -3
  31. package/web-ui/partials/index/panel-dashboard.html +33 -0
  32. package/web-ui/partials/index/panel-market.html +3 -3
  33. package/web-ui/partials/index/panel-plugins.html +2 -2
  34. package/web-ui/partials/index/panel-sessions.html +1 -9
  35. package/web-ui/partials/index/panel-settings.html +71 -134
  36. package/web-ui/partials/index/panel-trash.html +88 -0
  37. package/web-ui/session-helpers.mjs +20 -2
  38. package/web-ui/styles/dashboard.css +132 -0
  39. package/web-ui/styles/docs-panel.css +63 -39
  40. package/web-ui/styles/layout-shell.css +54 -34
  41. package/web-ui/styles/plugins-panel.css +121 -80
  42. package/web-ui/styles/sessions-list.css +41 -43
  43. package/web-ui/styles/sessions-preview.css +34 -38
  44. package/web-ui/styles/sessions-toolbar-trash.css +31 -27
  45. package/web-ui/styles/settings-panel.css +197 -33
  46. package/web-ui/styles/skills-list.css +12 -10
  47. package/web-ui/styles/skills-market.css +67 -44
  48. package/web-ui/styles/trash-panel.css +90 -0
  49. package/web-ui/styles/webhook.css +81 -0
  50. package/web-ui/styles.css +2 -0
@@ -0,0 +1,126 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const http = require('http');
4
+ const https = require('https');
5
+ const os = require('os');
6
+
7
+ const ALLOWED_EVENTS = ['provider-switch', 'claude-md-edit'];
8
+ const DEFAULT_TIMEOUT_MS = 5000;
9
+
10
+ function defaultConfigPath() {
11
+ return path.join(os.homedir(), '.codex', 'codexmate-webhook.json');
12
+ }
13
+
14
+ function normalizeConfig(cfg) {
15
+ const out = { enabled: false, url: '', events: ALLOWED_EVENTS.slice() };
16
+ if (!cfg || typeof cfg !== 'object') return out;
17
+ out.enabled = !!cfg.enabled;
18
+ out.url = typeof cfg.url === 'string' ? cfg.url.trim() : '';
19
+ if (Array.isArray(cfg.events)) {
20
+ const filtered = cfg.events.filter(function (e) { return ALLOWED_EVENTS.indexOf(e) !== -1; });
21
+ out.events = filtered.length ? filtered : ALLOWED_EVENTS.slice();
22
+ }
23
+ return out;
24
+ }
25
+
26
+ function loadWebhookConfig(filePath) {
27
+ const target = filePath || defaultConfigPath();
28
+ try {
29
+ if (!fs.existsSync(target)) {
30
+ return normalizeConfig({});
31
+ }
32
+ const raw = fs.readFileSync(target, 'utf-8');
33
+ return normalizeConfig(JSON.parse(raw));
34
+ } catch (_) {
35
+ return normalizeConfig({});
36
+ }
37
+ }
38
+
39
+ function saveWebhookConfig(cfg, filePath) {
40
+ const target = filePath || defaultConfigPath();
41
+ const normalized = normalizeConfig(cfg);
42
+ try {
43
+ fs.mkdirSync(path.dirname(target), { recursive: true });
44
+ } catch (_) {}
45
+ fs.writeFileSync(target, JSON.stringify(normalized, null, 2), 'utf-8');
46
+ return normalized;
47
+ }
48
+
49
+ function postJson(targetUrl, payload, timeoutMs) {
50
+ return new Promise(function (resolve) {
51
+ let parsed;
52
+ try {
53
+ parsed = new URL(targetUrl);
54
+ } catch (_) {
55
+ resolve({ ok: false, error: 'invalid-url' });
56
+ return;
57
+ }
58
+ const transport = parsed.protocol === 'https:' ? https : http;
59
+ const body = JSON.stringify(payload || {});
60
+ let req;
61
+ try {
62
+ req = transport.request({
63
+ method: 'POST',
64
+ protocol: parsed.protocol,
65
+ hostname: parsed.hostname,
66
+ port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
67
+ path: (parsed.pathname || '/') + (parsed.search || ''),
68
+ headers: {
69
+ 'Content-Type': 'application/json; charset=utf-8',
70
+ 'Content-Length': Buffer.byteLength(body, 'utf-8'),
71
+ 'User-Agent': 'codexmate-webhook/1'
72
+ }
73
+ }, function (res) {
74
+ let raw = '';
75
+ res.on('data', function (chunk) {
76
+ if (raw.length < 1024) raw += chunk.toString('utf-8');
77
+ });
78
+ res.on('end', function () {
79
+ const status = res.statusCode || 0;
80
+ resolve({ ok: status >= 200 && status < 300, status: status, body: raw.slice(0, 200) });
81
+ });
82
+ });
83
+ } catch (e) {
84
+ resolve({ ok: false, error: e && e.message ? e.message : String(e) });
85
+ return;
86
+ }
87
+ const wait = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
88
+ req.setTimeout(wait, function () { req.destroy(new Error('timeout')); });
89
+ req.on('error', function (err) { resolve({ ok: false, error: err && err.message ? err.message : String(err) }); });
90
+ req.write(body);
91
+ req.end();
92
+ });
93
+ }
94
+
95
+ function buildPayload(event, summary, details) {
96
+ return {
97
+ event: String(event || ''),
98
+ summary: String(summary || ''),
99
+ operator: process.env.USER || process.env.USERNAME || (os.userInfo && os.userInfo().username) || '',
100
+ timestamp: new Date().toISOString(),
101
+ details: details && typeof details === 'object' ? details : {}
102
+ };
103
+ }
104
+
105
+ function notifyWebhook(event, summary, details, options) {
106
+ const opts = options || {};
107
+ const cfg = opts.config ? normalizeConfig(opts.config) : loadWebhookConfig(opts.filePath);
108
+ if (!cfg.enabled || !cfg.url) {
109
+ return Promise.resolve({ ok: false, skipped: true, reason: 'disabled' });
110
+ }
111
+ if (cfg.events.indexOf(event) === -1) {
112
+ return Promise.resolve({ ok: false, skipped: true, reason: 'event-filtered' });
113
+ }
114
+ return postJson(cfg.url, buildPayload(event, summary, details), opts.timeoutMs);
115
+ }
116
+
117
+ module.exports = {
118
+ ALLOWED_EVENTS,
119
+ defaultConfigPath,
120
+ normalizeConfig,
121
+ loadWebhookConfig,
122
+ saveWebhookConfig,
123
+ notifyWebhook,
124
+ buildPayload,
125
+ postJson
126
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/web-ui/app.js CHANGED
@@ -41,6 +41,7 @@ document.addEventListener('DOMContentLoaded', () => {
41
41
  modelAutoCompactTokenLimitInput: String(DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT),
42
42
  editingCodexBudgetField: '',
43
43
  providersList: [],
44
+ localBridgeExcluded: [],
44
45
  models: [],
45
46
  codexModelsLoading: false,
46
47
  modelsSource: 'remote',
@@ -202,8 +203,8 @@ document.addEventListener('DOMContentLoaded', () => {
202
203
  activeSessionDetailClipped: false,
203
204
  sessionDetailLoading: false,
204
205
  sessionDetailRequestSeq: 0,
205
- sessionDetailInitialMessageLimit: 80,
206
- sessionDetailFetchStep: 80,
206
+ sessionDetailInitialMessageLimit: 300,
207
+ sessionDetailFetchStep: 300,
207
208
  sessionDetailMessageLimit: 80,
208
209
  sessionDetailMessageLimitCap: 1000,
209
210
  sessionTimelineActiveKey: '',
@@ -221,8 +222,8 @@ document.addEventListener('DOMContentLoaded', () => {
221
222
  sessionPreviewHeaderResizeObserver: null,
222
223
  sessionListRenderEnabled: false,
223
224
  sessionListVisibleCount: 0,
224
- sessionListInitialBatchSize: 20,
225
- sessionListLoadStep: 40,
225
+ sessionListInitialBatchSize: 40,
226
+ sessionListLoadStep: 80,
226
227
  sessionPreviewRenderEnabled: false,
227
228
  sessionTabRenderTicket: 0,
228
229
  sessionPreviewVisibleCount: 0,
@@ -274,8 +275,8 @@ document.addEventListener('DOMContentLoaded', () => {
274
275
  newClaudeConfig: {
275
276
  name: '',
276
277
  apiKey: '',
277
- baseUrl: 'https://open.bigmodel.cn/api/anthropic',
278
- model: 'glm-4.7'
278
+ baseUrl: '',
279
+ model: ''
279
280
  },
280
281
  currentOpenclawConfig: '',
281
282
  openclawConfigs: {
@@ -336,6 +337,8 @@ document.addEventListener('DOMContentLoaded', () => {
336
337
  healthCheckLoading: false,
337
338
  healthCheckResult: null,
338
339
  healthCheckRemote: false,
340
+ providersHealthLoading: false,
341
+ providersHealthResult: null,
339
342
  claudeDownloadLoading: false,
340
343
  claudeDownloadProgress: 0,
341
344
  claudeDownloadTimer: null,
@@ -398,7 +401,12 @@ document.addEventListener('DOMContentLoaded', () => {
398
401
  lastLoadedAt: '',
399
402
  lastError: ''
400
403
  },
401
- _taskOrchestrationPollTimer: 0
404
+ _taskOrchestrationPollTimer: 0,
405
+ webhookConfig: { enabled: false, url: '', events: ['provider-switch', 'claude-md-edit'] },
406
+ webhookEventOptions: ['provider-switch', 'claude-md-edit'],
407
+ webhookSaving: false,
408
+ webhookTestResult: null,
409
+ webhookTesting: false,
402
410
  };
403
411
  },
404
412
 
@@ -406,6 +414,9 @@ document.addEventListener('DOMContentLoaded', () => {
406
414
  if (typeof this.initI18n === 'function') {
407
415
  this.initI18n();
408
416
  }
417
+ if (typeof this.loadWebhookSettings === 'function') {
418
+ this.loadWebhookSettings();
419
+ }
409
420
  if (typeof this.t === 'function') {
410
421
  this.confirmDialogConfirmText = this.t('confirm.ok');
411
422
  this.confirmDialogCancelText = this.t('confirm.cancel');
@@ -414,7 +425,7 @@ document.addEventListener('DOMContentLoaded', () => {
414
425
  }
415
426
  {
416
427
  const NAV_STATE_STORAGE_KEY = 'codexmateNavState.v1';
417
- const mainTabSet = new Set(['dashboard', 'config', 'sessions', 'usage', 'orchestration', 'market', 'plugins', 'docs', 'settings']);
428
+ const mainTabSet = new Set(['dashboard', 'config', 'sessions', 'usage', 'orchestration', 'market', 'plugins', 'docs', 'settings', 'trash']);
418
429
  let restored = null;
419
430
  try {
420
431
  const raw = localStorage.getItem(NAV_STATE_STORAGE_KEY) || '';
@@ -428,6 +439,9 @@ document.addEventListener('DOMContentLoaded', () => {
428
439
  const nextConfigMode = restored && typeof restored.configMode === 'string'
429
440
  ? restored.configMode.trim().toLowerCase()
430
441
  : '';
442
+ const nextSettingsTab = restored && typeof restored.settingsTab === 'string'
443
+ ? restored.settingsTab.trim().toLowerCase()
444
+ : '';
431
445
  let urlMainTab = '';
432
446
  try {
433
447
  const url = new URL(window.location.href);
@@ -440,6 +454,9 @@ document.addEventListener('DOMContentLoaded', () => {
440
454
  const resolvedMainTab = urlMainTab && mainTabSet.has(urlMainTab)
441
455
  ? urlMainTab
442
456
  : nextMainTab;
457
+ if (nextSettingsTab && (nextSettingsTab === 'general' || nextSettingsTab === 'data')) {
458
+ this.settingsTab = nextSettingsTab;
459
+ }
443
460
  if (nextConfigMode && typeof this.switchConfigMode === 'function') {
444
461
  this.__navStateRestoring = true;
445
462
  try {
@@ -554,6 +571,9 @@ document.addEventListener('DOMContentLoaded', () => {
554
571
  if (typeof this.runHealthCheck === 'function') {
555
572
  void this.runHealthCheck({ doctor: true, silent: true });
556
573
  }
574
+ if (typeof this.runProvidersHealthCheck === 'function') {
575
+ void this.runProvidersHealthCheck({ remote: true });
576
+ }
557
577
  }
558
578
  }
559
579
  void this.refreshClaudeSelectionFromSettings({ silent: true });
package/web-ui/index.html CHANGED
@@ -21,6 +21,7 @@
21
21
  <!-- @include ./partials/index/panel-orchestration.html -->
22
22
  <!-- @include ./partials/index/panel-docs.html -->
23
23
  <!-- @include ./partials/index/panel-settings.html -->
24
+ <!-- @include ./partials/index/panel-trash.html -->
24
25
  <!-- @include ./partials/index/panel-market.html -->
25
26
  <!-- @include ./partials/index/panel-plugins.html -->
26
27
  <!-- @include ./partials/index/layout-footer.html -->
@@ -52,5 +52,18 @@ export const CODEX_PROVIDER_TEMPLATES = Object.freeze([
52
52
  url: 'https://ai.muapi.cn/v1',
53
53
  model: 'mimo-v2-pro',
54
54
  useTransform: true
55
+ },
56
+ {
57
+ label: 'Telepub',
58
+ name: 'telepub',
59
+ url: 'https://voyage.prod.telepub.cn/voyage/api',
60
+ model: 'DeepSeek-V4-pro',
61
+ useTransform: true
62
+ },
63
+ {
64
+ label: 'AnyRouter',
65
+ name: 'anyrouter',
66
+ url: 'https://anyrouter.top/v1',
67
+ model: 'gpt-5.5'
55
68
  }
56
69
  ]);
@@ -35,6 +35,7 @@ export function createDashboardComputed() {
35
35
  return (name) => {
36
36
  const target = String(name || '').trim();
37
37
  if (!target) return '';
38
+ if (target === 'local') return '';
38
39
  const dict = this.currentModels && typeof this.currentModels === 'object' ? this.currentModels : {};
39
40
  const fromDict = typeof dict[target] === 'string' ? dict[target].trim() : '';
40
41
  if (fromDict) return fromDict;
@@ -82,12 +83,20 @@ export function createDashboardComputed() {
82
83
  return current;
83
84
  },
84
85
  displayProvidersList() {
85
- const list = Array.isArray(this.providersList) ? this.providersList : [];
86
+ const list = Array.isArray(this.providersList) ? [...this.providersList] : [];
87
+ list.sort((a, b) => {
88
+ if (a.name === 'local') return -1;
89
+ if (b.name === 'local') return 1;
90
+ return 0;
91
+ });
86
92
  return list;
87
93
  },
88
94
 
89
95
  displayProviderUrl() {
90
- return (provider) => getProviderDisplayUrl(provider);
96
+ return (provider) => {
97
+ if (provider && provider.name === 'local') return '';
98
+ return getProviderDisplayUrl(provider);
99
+ };
91
100
  },
92
101
 
93
102
  isTransformProvider() {
@@ -220,6 +229,20 @@ export function createDashboardComputed() {
220
229
  this.t('docs.tip.unix.2'),
221
230
  this.t('docs.tip.unix.3')
222
231
  ];
232
+ },
233
+ providersHealthSummary() {
234
+ if (!this.providersHealthResult || !this.providersHealthResult.summary) {
235
+ return { total: 0, green: 0, yellow: 0, red: 0 };
236
+ }
237
+ return this.providersHealthResult.summary;
238
+ },
239
+ providersHealthTone() {
240
+ if (!this.providersHealthResult) return '';
241
+ const s = this.providersHealthResult.summary;
242
+ if (!s) return '';
243
+ if (s.red > 0) return 'error';
244
+ if (s.yellow > 0) return 'warn';
245
+ return 'ok';
223
246
  }
224
247
  };
225
248
  }
@@ -153,7 +153,14 @@ const KNOWN_USAGE_MODEL_PRICING = Object.freeze({
153
153
  'gpt-5.4': Object.freeze({ input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 }),
154
154
  'gpt-5.4-mini': Object.freeze({ input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 }),
155
155
  'gpt-5.3-codex': Object.freeze({ input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 }),
156
- 'gpt-5.2-codex': Object.freeze({ input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 })
156
+ 'gpt-5.2-codex': Object.freeze({ input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 }),
157
+ 'claude-opus-4-6': Object.freeze({ input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75, reasoningOutput: 75 }),
158
+ 'claude-opus-4-7': Object.freeze({ input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75, reasoningOutput: 75 }),
159
+ 'claude-sonnet-4-6': Object.freeze({ input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75, reasoningOutput: 15 }),
160
+ 'claude-haiku-4-5': Object.freeze({ input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1, reasoningOutput: 4 }),
161
+ 'claude-3-5-sonnet': Object.freeze({ input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75, reasoningOutput: 15 }),
162
+ 'claude-3-5-haiku': Object.freeze({ input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1, reasoningOutput: 4 }),
163
+ 'claude-3-opus': Object.freeze({ input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75, reasoningOutput: 75 })
157
164
  });
158
165
 
159
166
  function createUsagePricingEntry(pricing, source) {
@@ -234,22 +241,17 @@ function resolveUsagePricingForSession(session, pricingIndex, fallbackProvider =
234
241
  if (knownPricing) {
235
242
  return knownPricing;
236
243
  }
244
+ const strippedModel = model.replace(/-\d{8}(?=[-_]|$)/, '');
245
+ if (strippedModel !== model) {
246
+ const strippedKnown = pricingIndex.knownByModel instanceof Map ? pricingIndex.knownByModel.get(strippedModel) : null;
247
+ if (strippedKnown) {
248
+ return strippedKnown;
249
+ }
250
+ }
237
251
  return null;
238
252
  }
239
253
 
240
- function shouldEstimateUsageCostForSession(session) {
241
- if (!session || typeof session !== 'object') {
242
- return false;
243
- }
244
- const source = typeof session.source === 'string' ? session.source.trim().toLowerCase() : '';
245
- const provider = typeof session.provider === 'string' ? session.provider.trim().toLowerCase() : '';
246
- const model = typeof session.model === 'string' ? session.model.trim().toLowerCase() : '';
247
- if (source === 'claude' || provider === 'claude') {
248
- return false;
249
- }
250
- if (/^claude(?:[-_]|$)/.test(model)) {
251
- return false;
252
- }
254
+ function shouldEstimateUsageCostForSession() {
253
255
  return true;
254
256
  }
255
257
 
@@ -313,10 +315,11 @@ function estimateUsageCostSummary(sessions, providersList, currentProvider) {
313
315
  function estimateUsageCostForSession(session, pricingIndex, currentProvider) {
314
316
  const inputTokens = Number.isFinite(Number(session.inputTokens)) ? Math.max(0, Math.floor(Number(session.inputTokens))) : null;
315
317
  const cachedInputTokens = Number.isFinite(Number(session.cachedInputTokens)) ? Math.max(0, Math.floor(Number(session.cachedInputTokens))) : 0;
318
+ const cacheCreationInputTokens = Number.isFinite(Number(session.cacheCreationInputTokens)) ? Math.max(0, Math.floor(Number(session.cacheCreationInputTokens))) : 0;
316
319
  const outputTokens = Number.isFinite(Number(session.outputTokens)) ? Math.max(0, Math.floor(Number(session.outputTokens))) : null;
317
320
  const reasoningOutputTokens = Number.isFinite(Number(session.reasoningOutputTokens)) ? Math.max(0, Math.floor(Number(session.reasoningOutputTokens))) : 0;
318
- const billableInputTokens = Math.max(0, (inputTokens || 0) - cachedInputTokens);
319
- const fallbackSessionTokens = billableInputTokens + cachedInputTokens + (outputTokens || 0) + reasoningOutputTokens;
321
+ const billableInputTokens = Math.max(0, (inputTokens || 0) - cachedInputTokens - cacheCreationInputTokens);
322
+ const fallbackSessionTokens = billableInputTokens + cachedInputTokens + cacheCreationInputTokens + (outputTokens || 0) + reasoningOutputTokens;
320
323
  const totalSessionTokens = Number.isFinite(Number(session.totalTokens))
321
324
  ? Math.max(0, Math.floor(Number(session.totalTokens)))
322
325
  : fallbackSessionTokens;
@@ -325,12 +328,14 @@ function estimateUsageCostForSession(session, pricingIndex, currentProvider) {
325
328
  const reasoningRate = pricing
326
329
  ? ((pricing.reasoningOutput != null ? pricing.reasoningOutput : pricing.output) || 0)
327
330
  : 0;
331
+ const visibleOutputTokens = Math.max(0, (outputTokens || 0) - reasoningOutputTokens);
328
332
  const estimatedUsd = pricing && hasTokenBreakdown
329
333
  ? (
330
334
  ((pricing.input || 0) * billableInputTokens)
331
335
  + ((pricing.cacheRead || 0) * cachedInputTokens)
336
+ + ((pricing.cacheWrite || 0) * cacheCreationInputTokens)
332
337
  + (reasoningRate * reasoningOutputTokens)
333
- + ((pricing.output || 0) * (outputTokens || 0))
338
+ + ((pricing.output || 0) * visibleOutputTokens)
334
339
  ) / 1000000
335
340
  : 0;
336
341
  return {
@@ -43,6 +43,16 @@ export function createClaudeConfigMethods(options = {}) {
43
43
  }
44
44
  },
45
45
 
46
+ openCloneClaudeConfigModal(name, config) {
47
+ this.newClaudeConfig = {
48
+ name: '',
49
+ apiKey: config.apiKey || '',
50
+ baseUrl: config.baseUrl || '',
51
+ model: config.model || ''
52
+ };
53
+ this.showClaudeConfigModal = true;
54
+ },
55
+
46
56
  openEditConfigModal(name) {
47
57
  const config = this.claudeConfigs[name];
48
58
  this.editingConfig = {
@@ -182,8 +192,8 @@ export function createClaudeConfigMethods(options = {}) {
182
192
  this.newClaudeConfig = {
183
193
  name: '',
184
194
  apiKey: '',
185
- baseUrl: 'https://open.bigmodel.cn/api/anthropic',
186
- model: 'glm-4.7'
195
+ baseUrl: '',
196
+ model: ''
187
197
  };
188
198
  }
189
199
  };
@@ -236,6 +236,10 @@ export function createCodexConfigMethods(options = {}) {
236
236
  }
237
237
  }
238
238
  }
239
+
240
+ if (typeof this.loadLocalBridgeExcluded === 'function') {
241
+ this.loadLocalBridgeExcluded();
242
+ }
239
243
  },
240
244
 
241
245
  async switchProvider(name) {
@@ -557,6 +561,27 @@ export function createCodexConfigMethods(options = {}) {
557
561
  this.healthCheckBatchTotal = this.healthCheckBatchTotal || 0;
558
562
  this.healthCheckBatchDone = Math.min(this.healthCheckBatchDone || 0, this.healthCheckBatchTotal || 0);
559
563
  this.healthCheckLoading = false;
564
+ if (typeof this.runProvidersHealthCheck === 'function' && this.configMode === 'codex') {
565
+ void this.runProvidersHealthCheck({ remote: true });
566
+ }
567
+ }
568
+ },
569
+
570
+ async runProvidersHealthCheck(options = {}) {
571
+ this.providersHealthLoading = true;
572
+ try {
573
+ const res = await api('providers-health', {
574
+ remote: options.remote !== false
575
+ });
576
+ if (res && typeof res === 'object' && !res.error) {
577
+ this.providersHealthResult = res;
578
+ } else {
579
+ this.providersHealthResult = null;
580
+ }
581
+ } catch (e) {
582
+ this.providersHealthResult = null;
583
+ } finally {
584
+ this.providersHealthLoading = false;
560
585
  }
561
586
  },
562
587
 
@@ -29,6 +29,7 @@ import { createStartupClaudeMethods } from './app.methods.startup-claude.mjs';
29
29
  import { createSkillsMethods } from './skills.methods.mjs';
30
30
  import { createPluginsMethods } from './plugins.methods.mjs';
31
31
  import { createI18nMethods } from './i18n.mjs';
32
+ import { createWebhookMethods } from './app.methods.webhook.mjs';
32
33
  import {
33
34
  CONFIG_MODE_SET,
34
35
  getProviderConfigModeMeta
@@ -43,6 +44,7 @@ import {
43
44
  export function createAppMethods() {
44
45
  return {
45
46
  ...createI18nMethods(),
47
+ ...createWebhookMethods(),
46
48
  ...createStartupClaudeMethods({
47
49
  api,
48
50
  defaultModelContextWindow: DEFAULT_MODEL_CONTEXT_WINDOW,
@@ -1,4 +1,4 @@
1
- export function createNavigationMethods(options = {}) {
1
+ export function createNavigationMethods(options = {}) {
2
2
  const {
3
3
  configModeSet,
4
4
  switchMainTabHelper,
@@ -14,7 +14,8 @@ export function createNavigationMethods(options = {}) {
14
14
  'market',
15
15
  'plugins',
16
16
  'docs',
17
- 'settings'
17
+ 'settings',
18
+ 'trash'
18
19
  ]);
19
20
  const loadDoctorOverview = async (vm, options = {}) => {
20
21
  if (!vm || typeof vm !== 'object') return false;
@@ -67,9 +68,15 @@ export function createNavigationMethods(options = {}) {
67
68
  : vm.configMode;
68
69
  const mainTab = typeof mainTabSource === 'string' ? mainTabSource.trim().toLowerCase() : '';
69
70
  const configMode = typeof configModeSource === 'string' ? configModeSource.trim().toLowerCase() : '';
71
+ const settingsTab = typeof vm.settingsTab === 'string' ? vm.settingsTab.trim().toLowerCase() : 'general';
72
+ const skillsTargetApp = typeof vm.skillsTargetApp === 'string' && (vm.skillsTargetApp === 'codex' || vm.skillsTargetApp === 'claude') ? vm.skillsTargetApp : 'codex';
73
+ const promptTemplatesMode = typeof vm.promptTemplatesMode === 'string' && (vm.promptTemplatesMode === 'compose' || vm.promptTemplatesMode === 'manage') ? vm.promptTemplatesMode : 'compose';
70
74
  const snapshot = {
75
+ settingsTab: settingsTab === 'data' ? 'data' : 'general',
71
76
  mainTab: MAIN_TAB_SET.has(mainTab) ? mainTab : 'dashboard',
72
- configMode: configModeSet && configModeSet.has(configMode) ? configMode : 'codex'
77
+ configMode: configModeSet && configModeSet.has(configMode) ? configMode : 'codex',
78
+ skillsTargetApp,
79
+ promptTemplatesMode
73
80
  };
74
81
  try {
75
82
  localStorage.setItem(NAV_STATE_STORAGE_KEY, JSON.stringify(snapshot));
@@ -77,6 +84,9 @@ export function createNavigationMethods(options = {}) {
77
84
  };
78
85
 
79
86
  return {
87
+ saveNavState() {
88
+ persistNavState(this);
89
+ },
80
90
  restoreNavStateFromStorage() {
81
91
  if (this.__navStateRestoring) return false;
82
92
  const restored = readNavState();
@@ -89,7 +99,17 @@ export function createNavigationMethods(options = {}) {
89
99
  : '';
90
100
  const shouldUpdateConfigMode = !!(nextConfigMode && configModeSet && configModeSet.has(nextConfigMode));
91
101
  const shouldUpdateMainTab = !!(nextMainTab && MAIN_TAB_SET.has(nextMainTab) && nextMainTab !== this.mainTab);
92
- if (!shouldUpdateConfigMode && !shouldUpdateMainTab) {
102
+ const nextSettingsTab = restored && typeof restored.settingsTab === 'string'
103
+ ? restored.settingsTab.trim().toLowerCase()
104
+ : '';
105
+ const shouldUpdateSettingsTab = !!(nextSettingsTab && (nextSettingsTab === 'general' || nextSettingsTab === 'data') && nextSettingsTab !== this.settingsTab);
106
+ const nextSkillsTargetApp = restored && typeof restored.skillsTargetApp === 'string' && (restored.skillsTargetApp === 'codex' || restored.skillsTargetApp === 'claude')
107
+ ? restored.skillsTargetApp : '';
108
+ const shouldUpdateSkillsTargetApp = !!(nextSkillsTargetApp && nextSkillsTargetApp !== this.skillsTargetApp);
109
+ const nextPromptTemplatesMode = restored && typeof restored.promptTemplatesMode === 'string' && (restored.promptTemplatesMode === 'compose' || restored.promptTemplatesMode === 'manage')
110
+ ? restored.promptTemplatesMode : '';
111
+ const shouldUpdatePromptTemplatesMode = !!(nextPromptTemplatesMode && nextPromptTemplatesMode !== this.promptTemplatesMode);
112
+ if (!shouldUpdateConfigMode && !shouldUpdateMainTab && !shouldUpdateSettingsTab && !shouldUpdateSkillsTargetApp && !shouldUpdatePromptTemplatesMode) {
93
113
  return false;
94
114
  }
95
115
  this.__navStateRestoring = true;
@@ -97,9 +117,18 @@ export function createNavigationMethods(options = {}) {
97
117
  if (shouldUpdateConfigMode) {
98
118
  this.configMode = nextConfigMode;
99
119
  }
120
+ if (shouldUpdateSettingsTab) {
121
+ this.settingsTab = nextSettingsTab;
122
+ }
100
123
  if (shouldUpdateMainTab) {
101
124
  this.switchMainTab(nextMainTab);
102
125
  }
126
+ if (shouldUpdateSkillsTargetApp) {
127
+ this.skillsTargetApp = nextSkillsTargetApp;
128
+ }
129
+ if (shouldUpdatePromptTemplatesMode) {
130
+ this.promptTemplatesMode = nextPromptTemplatesMode;
131
+ }
103
132
  } finally {
104
133
  this.__navStateRestoring = false;
105
134
  }
@@ -411,6 +440,11 @@ export function createNavigationMethods(options = {}) {
411
440
  switchState.ticket += 1;
412
441
  switchState.pendingTarget = '';
413
442
  if (targetTab === 'dashboard' && !this.__doctorLoadedOnce) {
443
+ if (targetTab === 'trash' && !this.sessionTrashLoadedOnce) {
444
+ if (typeof this.loadSessionTrash === 'function') {
445
+ void this.loadSessionTrash({ forceRefresh: false });
446
+ }
447
+ }
414
448
  void loadDoctorOverview(this);
415
449
  }
416
450
  if (
@@ -656,10 +690,7 @@ export function createNavigationMethods(options = {}) {
656
690
  ? this.activeSessionMessages.length
657
691
  : 0;
658
692
  if (total <= 0) return;
659
- const baseSize = Number.isFinite(this.sessionPreviewInitialBatchSize)
660
- ? Math.max(1, Math.floor(this.sessionPreviewInitialBatchSize))
661
- : 40;
662
- this.sessionPreviewVisibleCount = Math.min(baseSize, total);
693
+ this.sessionPreviewVisibleCount = total;
663
694
  this.invalidateSessionTimelineMeasurementCache();
664
695
  },
665
696