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
package/cli.js CHANGED
@@ -83,7 +83,14 @@ const {
83
83
  dispatchAutomationNotifiers,
84
84
  formatTaskRunNotificationPayload
85
85
  } = require('./lib/automation');
86
- const { buildConfigHealthReport: buildConfigHealthReportCore } = require('./cli/config-health');
86
+ const {
87
+ ALLOWED_EVENTS: WEBHOOK_ALLOWED_EVENTS,
88
+ defaultConfigPath: defaultWebhookConfigPath,
89
+ loadWebhookConfig,
90
+ saveWebhookConfig,
91
+ notifyWebhook
92
+ } = require('./lib/cli-webhook');
93
+ const { buildConfigHealthReport: buildConfigHealthReportCore, buildAllProvidersHealthReport: buildAllProvidersHealthReportCore } = require('./cli/config-health');
87
94
  const { buildDoctorReport, buildDoctorLegacyPayload, renderDoctorMarkdown } = require('./cli/doctor-core');
88
95
  const {
89
96
  createAuthProfileController
@@ -100,6 +107,9 @@ const {
100
107
  readOpenaiBridgeSettings,
101
108
  resolveOpenaiBridgeUpstream
102
109
  } = require('./cli/openai-bridge');
110
+ const {
111
+ createLocalBridgeHttpHandler
112
+ } = require('./cli/local-bridge');
103
113
  const {
104
114
  createOpenclawConfigController
105
115
  } = require('./cli/openclaw-config');
@@ -181,6 +191,7 @@ const INIT_MARK_FILE = path.join(CONFIG_DIR, 'codexmate-init.json');
181
191
  const BUILTIN_PROXY_SETTINGS_FILE = path.join(CONFIG_DIR, 'codexmate-proxy.json');
182
192
  const BUILTIN_CLAUDE_PROXY_SETTINGS_FILE = path.join(CONFIG_DIR, 'codexmate-claude-proxy.json');
183
193
  const OPENAI_BRIDGE_SETTINGS_FILE = path.join(CONFIG_DIR, 'codexmate-openai-bridge.json');
194
+ const LOCAL_BRIDGE_SETTINGS_FILE = path.join(CONFIG_DIR, 'codexmate-local-bridge.json');
184
195
  const CODEX_SESSIONS_DIR = path.join(CONFIG_DIR, 'sessions');
185
196
  const SESSION_TRASH_DIR = path.join(CONFIG_DIR, 'codexmate-session-trash');
186
197
  const SESSION_TRASH_FILES_DIR = path.join(SESSION_TRASH_DIR, 'files');
@@ -261,6 +272,7 @@ const DEFAULT_EXTRACT_SUFFIXES = Object.freeze(['.json']);
261
272
  const g_taskRunControllers = new Map();
262
273
  let g_taskQueueProcessor = null;
263
274
  const BUILTIN_PROXY_PROVIDER_NAME = 'codexmate-proxy';
275
+ const BUILTIN_LOCAL_PROVIDER_NAME = 'local';
264
276
  const DEFAULT_BUILTIN_PROXY_SETTINGS = Object.freeze({
265
277
  enabled: false,
266
278
  host: '127.0.0.1',
@@ -304,8 +316,16 @@ const CLI_INSTALL_TARGETS = Object.freeze([
304
316
  }
305
317
  ]);
306
318
 
307
- const HTTP_KEEP_ALIVE_AGENT = new http.Agent({ keepAlive: true });
308
- const HTTPS_KEEP_ALIVE_AGENT = new https.Agent({ keepAlive: true });
319
+ const HTTP_KEEP_ALIVE_AGENT = new http.Agent({
320
+ keepAlive: true,
321
+ keepAliveMsecs: 1000,
322
+ maxFreeSockets: 4
323
+ });
324
+ const HTTPS_KEEP_ALIVE_AGENT = new https.Agent({
325
+ keepAlive: true,
326
+ keepAliveMsecs: 1000,
327
+ maxFreeSockets: 4
328
+ });
309
329
 
310
330
  const openaiBridgeHandler = createOpenaiBridgeHttpHandler({
311
331
  settingsFile: OPENAI_BRIDGE_SETTINGS_FILE,
@@ -315,6 +335,16 @@ const openaiBridgeHandler = createOpenaiBridgeHttpHandler({
315
335
  httpsAgent: HTTPS_KEEP_ALIVE_AGENT
316
336
  });
317
337
 
338
+ const localBridgeHandler = createLocalBridgeHttpHandler({
339
+ readConfigFn: readConfig,
340
+ openaiBridgeFile: OPENAI_BRIDGE_SETTINGS_FILE,
341
+ localBridgeSettingsFile: LOCAL_BRIDGE_SETTINGS_FILE,
342
+ expectedToken: typeof process.env.CODEXMATE_HTTP_TOKEN === 'string' ? process.env.CODEXMATE_HTTP_TOKEN.trim() : '',
343
+ maxBodySize: MAX_API_BODY_SIZE,
344
+ httpAgent: HTTP_KEEP_ALIVE_AGENT,
345
+ httpsAgent: HTTPS_KEEP_ALIVE_AGENT
346
+ });
347
+
318
348
  function resolveWebPort() {
319
349
  const raw = process.env.CODEXMATE_PORT;
320
350
  if (!raw) return DEFAULT_WEB_PORT;
@@ -574,16 +604,17 @@ model_auto_compact_token_limit = ${DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT}
574
604
  disable_response_storage = true
575
605
  approval_policy = "never"
576
606
  sandbox_mode = "danger-full-access"
577
- model_provider = "maxx"
607
+ model_provider = "local"
578
608
  personality = "pragmatic"
579
609
  web_search = "live"
580
610
 
581
- [model_providers.maxx]
582
- name = "maxx"
583
- base_url = "https://maxx-direct.cloverstd.com"
611
+ [model_providers.local]
612
+ name = "local"
613
+ base_url = "http://127.0.0.1:3737/bridge/local/v1"
584
614
  wire_api = "responses"
585
- requires_openai_auth = false
586
- preferred_auth_method = "sk-"
615
+ requires_openai_auth = true
616
+ preferred_auth_method = "codexmate"
617
+ codexmate_bridge = "local"
587
618
  request_max_retries = 4
588
619
  stream_max_retries = 10
589
620
  stream_idle_timeout_ms = 300000
@@ -605,12 +636,16 @@ function isBuiltinProxyProvider(providerName) {
605
636
  return typeof providerName === 'string' && providerName.trim().toLowerCase() === BUILTIN_PROXY_PROVIDER_NAME.toLowerCase();
606
637
  }
607
638
 
639
+ function isLocalProvider(providerName) {
640
+ return typeof providerName === 'string' && providerName.trim().toLowerCase() === BUILTIN_LOCAL_PROVIDER_NAME.toLowerCase();
641
+ }
642
+
608
643
  function isReservedProviderNameForCreation(providerName) {
609
- return false;
644
+ return isLocalProvider(providerName);
610
645
  }
611
646
 
612
647
  function isBuiltinManagedProvider(providerName) {
613
- return isBuiltinProxyProvider(providerName);
648
+ return isBuiltinProxyProvider(providerName) || isLocalProvider(providerName);
614
649
  }
615
650
 
616
651
  function isNonDeletableProvider(providerName) {
@@ -1646,6 +1681,7 @@ const {
1646
1681
  DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT,
1647
1682
  CODEXMATE_MANAGED_MARKER,
1648
1683
  BUILTIN_PROXY_PROVIDER_NAME,
1684
+ BUILTIN_LOCAL_PROVIDER_NAME,
1649
1685
  EMPTY_CONFIG_FALLBACK_TEMPLATE
1650
1686
  });
1651
1687
 
@@ -1711,6 +1747,14 @@ async function buildConfigHealthReport(params = {}) {
1711
1747
  });
1712
1748
  }
1713
1749
 
1750
+ async function buildAllProvidersHealthReport(params = {}) {
1751
+ return buildAllProvidersHealthReportCore(params, {
1752
+ readConfigOrVirtualDefault,
1753
+ readCurrentModels,
1754
+ probeJsonPost
1755
+ });
1756
+ }
1757
+
1714
1758
  function hasConfigLoadError(result) {
1715
1759
  return !!(result
1716
1760
  && result.isVirtual
@@ -2044,7 +2088,7 @@ function addProviderToConfig(params = {}) {
2044
2088
  return { error: '提供商名称不可用' };
2045
2089
  }
2046
2090
  if (isBuiltinProxyProvider(name) && !allowManaged) {
2047
- return { error: 'codexmate-proxy 为保留名称,不可手动添加' };
2091
+ return { error: `${"codexmate-proxy"} 为保留名称,不可手动添加` }; // keep literal for codexmate-proxy
2048
2092
  }
2049
2093
 
2050
2094
  ensureConfigDir();
@@ -2144,7 +2188,7 @@ function updateProviderInConfig(params = {}) {
2144
2188
  return { error: 'URL 仅支持 http/https' };
2145
2189
  }
2146
2190
  if (isNonEditableProvider(name) && !allowManaged) {
2147
- return { error: 'codexmate-proxy 为保留名称,不可编辑' };
2191
+ return { error: `${name} 为保留名称,不可编辑` };
2148
2192
  }
2149
2193
 
2150
2194
  try {
@@ -2159,7 +2203,7 @@ function deleteProviderFromConfig(params = {}) {
2159
2203
  const name = typeof params.name === 'string' ? params.name.trim() : '';
2160
2204
  if (!name) return { error: '名称不能为空' };
2161
2205
  if (isNonDeletableProvider(name)) {
2162
- return { error: 'codexmate-proxy 为保留名称,不可删除' };
2206
+ return { error: `${name} 为保留名称,不可删除` };
2163
2207
  }
2164
2208
  if (!fs.existsSync(CONFIG_FILE)) {
2165
2209
  return { error: 'config.toml 不存在' };
@@ -2187,7 +2231,7 @@ function deleteProviderFromConfig(params = {}) {
2187
2231
  function performProviderDeletion(name, options = {}) {
2188
2232
  const silent = !!options.silent;
2189
2233
  if (isNonDeletableProvider(name)) {
2190
- const msg = 'codexmate-proxy 为保留名称,不可删除';
2234
+ const msg = `${name} 为保留名称,不可删除`;
2191
2235
  if (!silent) console.error('错误:', msg);
2192
2236
  return { error: msg };
2193
2237
  }
@@ -3639,12 +3683,19 @@ function readTotalTokensFromUsage(usage) {
3639
3683
  return explicitTotal;
3640
3684
  }
3641
3685
  const inputTokens = readNonNegativeInteger(usage.input_tokens ?? usage.inputTokens);
3686
+ const cachedInputTokens = readNonNegativeInteger(
3687
+ usage.cached_input_tokens ?? usage.cachedInputTokens
3688
+ ?? usage.cache_read_input_tokens ?? usage.cacheReadInputTokens
3689
+ );
3690
+ const cacheCreationInputTokens = readNonNegativeInteger(
3691
+ usage.cache_creation_input_tokens ?? usage.cacheCreationInputTokens
3692
+ );
3642
3693
  const outputTokens = readNonNegativeInteger(usage.output_tokens ?? usage.outputTokens);
3643
3694
  const reasoningOutputTokens = readNonNegativeInteger(usage.reasoning_output_tokens ?? usage.reasoningOutputTokens);
3644
- if (inputTokens === null && outputTokens === null && reasoningOutputTokens === null) {
3695
+ if (inputTokens === null && cachedInputTokens === null && cacheCreationInputTokens === null && outputTokens === null && reasoningOutputTokens === null) {
3645
3696
  return null;
3646
3697
  }
3647
- return (inputTokens || 0) + (outputTokens || 0) + (reasoningOutputTokens || 0);
3698
+ return (inputTokens || 0) + (cachedInputTokens || 0) + (cacheCreationInputTokens || 0) + (outputTokens || 0) + (reasoningOutputTokens || 0);
3648
3699
  }
3649
3700
 
3650
3701
  function readUsageTotalsFromUsage(usage) {
@@ -3652,19 +3703,26 @@ function readUsageTotalsFromUsage(usage) {
3652
3703
  return null;
3653
3704
  }
3654
3705
  const inputTokens = readNonNegativeInteger(usage.input_tokens ?? usage.inputTokens);
3655
- const cachedInputTokens = readNonNegativeInteger(usage.cached_input_tokens ?? usage.cachedInputTokens);
3706
+ const cachedInputTokens = readNonNegativeInteger(
3707
+ usage.cached_input_tokens ?? usage.cachedInputTokens
3708
+ ?? usage.cache_read_input_tokens ?? usage.cacheReadInputTokens
3709
+ );
3710
+ const cacheCreationInputTokens = readNonNegativeInteger(
3711
+ usage.cache_creation_input_tokens ?? usage.cacheCreationInputTokens
3712
+ );
3656
3713
  const outputTokens = readNonNegativeInteger(usage.output_tokens ?? usage.outputTokens);
3657
3714
  const reasoningOutputTokens = readNonNegativeInteger(usage.reasoning_output_tokens ?? usage.reasoningOutputTokens);
3658
3715
  const totalTokens = readNonNegativeInteger(usage.total_tokens ?? usage.totalTokens)
3659
- ?? ((inputTokens === null && cachedInputTokens === null && outputTokens === null && reasoningOutputTokens === null)
3716
+ ?? ((inputTokens === null && cachedInputTokens === null && cacheCreationInputTokens === null && outputTokens === null && reasoningOutputTokens === null)
3660
3717
  ? null
3661
- : ((inputTokens || 0) + (outputTokens || 0) + (reasoningOutputTokens || 0)));
3662
- if (inputTokens === null && cachedInputTokens === null && outputTokens === null && reasoningOutputTokens === null && totalTokens === null) {
3718
+ : ((inputTokens || 0) + (cachedInputTokens || 0) + (cacheCreationInputTokens || 0) + (outputTokens || 0) + (reasoningOutputTokens || 0)));
3719
+ if (inputTokens === null && cachedInputTokens === null && cacheCreationInputTokens === null && outputTokens === null && reasoningOutputTokens === null && totalTokens === null) {
3663
3720
  return null;
3664
3721
  }
3665
3722
  return {
3666
3723
  inputTokens,
3667
3724
  cachedInputTokens,
3725
+ cacheCreationInputTokens,
3668
3726
  outputTokens,
3669
3727
  reasoningOutputTokens,
3670
3728
  totalTokens
@@ -3690,6 +3748,7 @@ function applyUsageTotalsToState(state, usageTotals) {
3690
3748
  const pairs = [
3691
3749
  ['inputTokens', usageTotals.inputTokens],
3692
3750
  ['cachedInputTokens', usageTotals.cachedInputTokens],
3751
+ ['cacheCreationInputTokens', usageTotals.cacheCreationInputTokens],
3693
3752
  ['outputTokens', usageTotals.outputTokens],
3694
3753
  ['reasoningOutputTokens', usageTotals.reasoningOutputTokens],
3695
3754
  ['totalTokens', usageTotals.totalTokens]
@@ -3940,12 +3999,13 @@ function parseCodexSessionSummary(filePath, options = {}) {
3940
3999
  let contextWindow = 0;
3941
4000
  let inputTokens = 0;
3942
4001
  let cachedInputTokens = 0;
4002
+ let cacheCreationInputTokens = 0;
3943
4003
  let outputTokens = 0;
3944
4004
  let reasoningOutputTokens = 0;
3945
4005
  let provider = 'codex';
3946
4006
  let model = '';
3947
4007
  const models = [];
3948
- const usageState = { totalTokens, contextWindow, inputTokens, cachedInputTokens, outputTokens, reasoningOutputTokens };
4008
+ const usageState = { totalTokens, contextWindow, inputTokens, cachedInputTokens, cacheCreationInputTokens, outputTokens, reasoningOutputTokens };
3949
4009
  const previewMessages = [];
3950
4010
 
3951
4011
  for (const record of records) {
@@ -3958,6 +4018,7 @@ function parseCodexSessionSummary(filePath, options = {}) {
3958
4018
  contextWindow = usageState.contextWindow || 0;
3959
4019
  inputTokens = usageState.inputTokens || 0;
3960
4020
  cachedInputTokens = usageState.cachedInputTokens || 0;
4021
+ cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
3961
4022
  outputTokens = usageState.outputTokens || 0;
3962
4023
  reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
3963
4024
 
@@ -3992,6 +4053,7 @@ function parseCodexSessionSummary(filePath, options = {}) {
3992
4053
  contextWindow = usageState.contextWindow || 0;
3993
4054
  inputTokens = usageState.inputTokens || 0;
3994
4055
  cachedInputTokens = usageState.cachedInputTokens || 0;
4056
+ cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
3995
4057
  outputTokens = usageState.outputTokens || 0;
3996
4058
  reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
3997
4059
  provider = readExplicitSessionProviderFromRecord(record) || provider;
@@ -4051,6 +4113,7 @@ function parseCodexSessionSummary(filePath, options = {}) {
4051
4113
  contextWindow,
4052
4114
  inputTokens,
4053
4115
  cachedInputTokens,
4116
+ cacheCreationInputTokens,
4054
4117
  outputTokens,
4055
4118
  reasoningOutputTokens,
4056
4119
  __messageCountExact: isSessionSummaryMessageCountExact(stat, summaryReadBytes),
@@ -4087,12 +4150,13 @@ function parseClaudeSessionSummary(filePath, options = {}) {
4087
4150
  let contextWindow = 0;
4088
4151
  let inputTokens = 0;
4089
4152
  let cachedInputTokens = 0;
4153
+ let cacheCreationInputTokens = 0;
4090
4154
  let outputTokens = 0;
4091
4155
  let reasoningOutputTokens = 0;
4092
4156
  let provider = 'claude';
4093
4157
  let model = '';
4094
4158
  const models = [];
4095
- const usageState = { totalTokens, contextWindow, inputTokens, cachedInputTokens, outputTokens, reasoningOutputTokens };
4159
+ const usageState = { totalTokens, contextWindow, inputTokens, cachedInputTokens, cacheCreationInputTokens, outputTokens, reasoningOutputTokens };
4096
4160
  const previewMessages = [];
4097
4161
  let createdAt = '';
4098
4162
  let updatedAt = stat.mtime.toISOString();
@@ -4110,6 +4174,7 @@ function parseClaudeSessionSummary(filePath, options = {}) {
4110
4174
  contextWindow = usageState.contextWindow || 0;
4111
4175
  inputTokens = usageState.inputTokens || 0;
4112
4176
  cachedInputTokens = usageState.cachedInputTokens || 0;
4177
+ cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
4113
4178
  outputTokens = usageState.outputTokens || 0;
4114
4179
  reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
4115
4180
 
@@ -4143,6 +4208,7 @@ function parseClaudeSessionSummary(filePath, options = {}) {
4143
4208
  contextWindow = usageState.contextWindow || 0;
4144
4209
  inputTokens = usageState.inputTokens || 0;
4145
4210
  cachedInputTokens = usageState.cachedInputTokens || 0;
4211
+ cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
4146
4212
  outputTokens = usageState.outputTokens || 0;
4147
4213
  reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
4148
4214
  provider = readExplicitSessionProviderFromRecord(record) || provider;
@@ -4201,6 +4267,7 @@ function parseClaudeSessionSummary(filePath, options = {}) {
4201
4267
  contextWindow,
4202
4268
  inputTokens,
4203
4269
  cachedInputTokens,
4270
+ cacheCreationInputTokens,
4204
4271
  outputTokens,
4205
4272
  reasoningOutputTokens,
4206
4273
  __messageCountExact: isSessionSummaryMessageCountExact(stat, summaryReadBytes),
@@ -4237,12 +4304,13 @@ function parseCodeBuddySessionSummary(filePath, options = {}) {
4237
4304
  let contextWindow = 0;
4238
4305
  let inputTokens = 0;
4239
4306
  let cachedInputTokens = 0;
4307
+ let cacheCreationInputTokens = 0;
4240
4308
  let outputTokens = 0;
4241
4309
  let reasoningOutputTokens = 0;
4242
4310
  let provider = 'codebuddy';
4243
4311
  let model = '';
4244
4312
  const models = [];
4245
- const usageState = { totalTokens, contextWindow, inputTokens, cachedInputTokens, outputTokens, reasoningOutputTokens };
4313
+ const usageState = { totalTokens, contextWindow, inputTokens, cachedInputTokens, cacheCreationInputTokens, outputTokens, reasoningOutputTokens };
4246
4314
  const previewMessages = [];
4247
4315
  let createdAt = '';
4248
4316
  let updatedAt = stat.mtime.toISOString();
@@ -4260,6 +4328,7 @@ function parseCodeBuddySessionSummary(filePath, options = {}) {
4260
4328
  contextWindow = usageState.contextWindow || 0;
4261
4329
  inputTokens = usageState.inputTokens || 0;
4262
4330
  cachedInputTokens = usageState.cachedInputTokens || 0;
4331
+ cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
4263
4332
  outputTokens = usageState.outputTokens || 0;
4264
4333
  reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
4265
4334
 
@@ -4298,6 +4367,7 @@ function parseCodeBuddySessionSummary(filePath, options = {}) {
4298
4367
  contextWindow = usageState.contextWindow || 0;
4299
4368
  inputTokens = usageState.inputTokens || 0;
4300
4369
  cachedInputTokens = usageState.cachedInputTokens || 0;
4370
+ cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
4301
4371
  outputTokens = usageState.outputTokens || 0;
4302
4372
  reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
4303
4373
  provider = readExplicitSessionProviderFromRecord(record) || provider;
@@ -4358,6 +4428,7 @@ function parseCodeBuddySessionSummary(filePath, options = {}) {
4358
4428
  contextWindow,
4359
4429
  inputTokens,
4360
4430
  cachedInputTokens,
4431
+ cacheCreationInputTokens,
4361
4432
  outputTokens,
4362
4433
  reasoningOutputTokens,
4363
4434
  __messageCountExact: isSessionSummaryMessageCountExact(stat, summaryReadBytes),
@@ -4647,17 +4718,19 @@ function listClaudeSessions(limit, options = {}) {
4647
4718
  let contextWindow = 0;
4648
4719
  let inputTokens = 0;
4649
4720
  let cachedInputTokens = 0;
4721
+ let cacheCreationInputTokens = 0;
4650
4722
  let outputTokens = 0;
4651
4723
  let reasoningOutputTokens = 0;
4652
4724
  let model = typeof entry.model === 'string' ? entry.model.trim() : '';
4653
4725
  const models = model ? [model] : [];
4654
4726
 
4655
- const usageState = { totalTokens, contextWindow, inputTokens, cachedInputTokens, outputTokens, reasoningOutputTokens };
4727
+ const usageState = { totalTokens, contextWindow, inputTokens, cachedInputTokens, cacheCreationInputTokens, outputTokens, reasoningOutputTokens };
4656
4728
  applySessionUsageSummaryFromIndexEntry(usageState, entry);
4657
4729
  totalTokens = usageState.totalTokens || 0;
4658
4730
  contextWindow = usageState.contextWindow || 0;
4659
4731
  inputTokens = usageState.inputTokens || 0;
4660
4732
  cachedInputTokens = usageState.cachedInputTokens || 0;
4733
+ cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
4661
4734
  outputTokens = usageState.outputTokens || 0;
4662
4735
  reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
4663
4736
 
@@ -4688,6 +4761,7 @@ function listClaudeSessions(limit, options = {}) {
4688
4761
  contextWindow = usageState.contextWindow || 0;
4689
4762
  inputTokens = usageState.inputTokens || 0;
4690
4763
  cachedInputTokens = usageState.cachedInputTokens || 0;
4764
+ cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
4691
4765
  outputTokens = usageState.outputTokens || 0;
4692
4766
  reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
4693
4767
  const filteredQuickMessages = removeLeadingSystemMessage(quickMessages);
@@ -4712,6 +4786,7 @@ function listClaudeSessions(limit, options = {}) {
4712
4786
  contextWindow = usageState.contextWindow || 0;
4713
4787
  inputTokens = usageState.inputTokens || 0;
4714
4788
  cachedInputTokens = usageState.cachedInputTokens || 0;
4789
+ cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
4715
4790
  outputTokens = usageState.outputTokens || 0;
4716
4791
  reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
4717
4792
 
@@ -4735,6 +4810,7 @@ function listClaudeSessions(limit, options = {}) {
4735
4810
  contextWindow,
4736
4811
  inputTokens,
4737
4812
  cachedInputTokens,
4813
+ cacheCreationInputTokens,
4738
4814
  outputTokens,
4739
4815
  reasoningOutputTokens,
4740
4816
  model,
@@ -5376,6 +5452,100 @@ async function ensureBuiltinProxyForCodexDefault(params = {}) {
5376
5452
  return { error: '该功能已移除' };
5377
5453
  }
5378
5454
 
5455
+ function readLocalBridgeSettings() {
5456
+ const defaults = { enabled: false, lastActiveProvider: '', lastModel: '', excludedProviders: [] };
5457
+ try {
5458
+ if (!fs.existsSync(LOCAL_BRIDGE_SETTINGS_FILE)) return defaults;
5459
+ const raw = JSON.parse(fs.readFileSync(LOCAL_BRIDGE_SETTINGS_FILE, 'utf-8'));
5460
+ return {
5461
+ enabled: !!raw.enabled,
5462
+ lastActiveProvider: typeof raw.lastActiveProvider === 'string' ? raw.lastActiveProvider.trim() : '',
5463
+ lastModel: typeof raw.lastModel === 'string' ? raw.lastModel.trim() : '',
5464
+ excludedProviders: Array.isArray(raw.excludedProviders) ? raw.excludedProviders.filter(p => typeof p === 'string') : []
5465
+ };
5466
+ } catch (e) {
5467
+ return defaults;
5468
+ }
5469
+ }
5470
+
5471
+ function writeLocalBridgeSettings(settings) {
5472
+ fs.writeFileSync(LOCAL_BRIDGE_SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf-8');
5473
+ }
5474
+
5475
+ function toggleLocalBridgeProvider(params = {}) {
5476
+ const enable = !!params.enable;
5477
+ const settings = readLocalBridgeSettings();
5478
+ try {
5479
+ const config = readConfig();
5480
+ const currentProvider = typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
5481
+ const currentModel = typeof config.model === 'string' ? config.model.trim() : '';
5482
+
5483
+ if (enable) {
5484
+ if (currentProvider === 'local') return { success: true, enabled: true, notice: '已启用 local 转换' };
5485
+ settings.lastActiveProvider = currentProvider;
5486
+ settings.lastModel = currentModel;
5487
+ settings.enabled = true;
5488
+ writeLocalBridgeSettings(settings);
5489
+ let content = fs.readFileSync(CONFIG_FILE, 'utf-8');
5490
+ content = content.replace(/^(model_provider\s*=\s*)(["']).*?(["'])/m, `$1$2local$3`);
5491
+ writeConfig(content);
5492
+ return { success: true, enabled: true, previousProvider: currentProvider };
5493
+ } else {
5494
+ if (currentProvider !== 'local') {
5495
+ settings.enabled = false;
5496
+ writeLocalBridgeSettings(settings);
5497
+ return { success: true, enabled: false, notice: 'local 转换未启用' };
5498
+ }
5499
+ const restoreProvider = settings.lastActiveProvider || '';
5500
+ if (!restoreProvider) {
5501
+ settings.enabled = false;
5502
+ writeLocalBridgeSettings(settings);
5503
+ return { success: true, enabled: false, notice: '已关闭 local 转换(无历史 provider 可恢复)' };
5504
+ }
5505
+ let content = fs.readFileSync(CONFIG_FILE, 'utf-8');
5506
+ content = content.replace(/^(model_provider\s*=\s*)(["']).*?(["'])/m, `$1$2${restoreProvider}$3`);
5507
+ if (settings.lastModel) {
5508
+ content = content.replace(/^(model\s*=\s*)(["']).*?(["'])/m, `$1$2${settings.lastModel}$3`);
5509
+ }
5510
+ writeConfig(content);
5511
+ settings.enabled = false;
5512
+ writeLocalBridgeSettings(settings);
5513
+ return { success: true, enabled: false, restoredProvider: restoreProvider, restoredModel: settings.lastModel };
5514
+ }
5515
+ } catch (e) {
5516
+ return { error: e && e.message ? e.message : '操作失败' };
5517
+ }
5518
+ }
5519
+
5520
+ function getLocalBridgeStatus() {
5521
+ const settings = readLocalBridgeSettings();
5522
+ let currentProvider = '';
5523
+ try {
5524
+ const config = readConfig();
5525
+ currentProvider = typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
5526
+ } catch (e) { /* ignore */ }
5527
+ return {
5528
+ enabled: settings.enabled,
5529
+ active: currentProvider === 'local',
5530
+ excludedProviders: settings.excludedProviders,
5531
+ lastActiveProvider: settings.lastActiveProvider,
5532
+ lastModel: settings.lastModel
5533
+ };
5534
+ }
5535
+
5536
+ function setLocalBridgeExcludedProviders(params = {}) {
5537
+ const names = Array.isArray(params.names) ? params.names.filter(n => typeof n === 'string' && n.trim()) : [];
5538
+ const settings = readLocalBridgeSettings();
5539
+ settings.excludedProviders = names;
5540
+ writeLocalBridgeSettings(settings);
5541
+ return { success: true, excludedProviders: names };
5542
+ }
5543
+
5544
+ function getLocalBridgeExcludedProviders() {
5545
+ const settings = readLocalBridgeSettings();
5546
+ return { excludedProviders: settings.excludedProviders };
5547
+ }
5548
+
5379
5549
  function removeClaudeSessionIndexEntry(indexPath, sessionFilePath, sessionId) {
5380
5550
  if (!indexPath || !fs.existsSync(indexPath)) {
5381
5551
  return { removed: false, entry: null };
@@ -8085,8 +8255,8 @@ function cmdAdd(name, baseUrl, apiKey, silent = false, options = {}) {
8085
8255
  throw new Error('提供商名称不可用');
8086
8256
  }
8087
8257
  if (isBuiltinProxyProvider(providerName)) {
8088
- if (!silent) console.error('错误: codexmate-proxy 为保留名称,不可手动添加');
8089
- throw new Error('codexmate-proxy 为保留名称,不可手动添加');
8258
+ if (!silent) console.error(`错误: ${providerName} 为保留名称,不可手动添加`);
8259
+ throw new Error(`${providerName} 为保留名称,不可手动添加`);
8090
8260
  }
8091
8261
  if (!isValidHttpUrl(providerBaseUrl)) {
8092
8262
  if (!silent) console.error('错误: URL 仅支持 http/https');
@@ -8182,7 +8352,7 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
8182
8352
  throw new Error('提供商名称必填');
8183
8353
  }
8184
8354
  if (isNonEditableProvider(name) && !allowManaged) {
8185
- const msg = 'codexmate-proxy 为保留名称,不可编辑';
8355
+ const msg = `${name} 为保留名称,不可编辑`;
8186
8356
  if (!silent) console.error(`错误: ${msg}`);
8187
8357
  throw new Error(msg);
8188
8358
  }
@@ -9914,6 +10084,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
9914
10084
  });
9915
10085
  res.end(body, 'utf-8');
9916
10086
  };
10087
+ if (typeof localBridgeHandler === 'function' && localBridgeHandler(req, res)) {
10088
+ return;
10089
+ }
9917
10090
  if (typeof openaiBridgeHandler === 'function' && openaiBridgeHandler(req, res)) {
9918
10091
  return;
9919
10092
  }
@@ -10108,6 +10281,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
10108
10281
  case 'config-health-check':
10109
10282
  result = await buildConfigHealthReport(params || {});
10110
10283
  break;
10284
+ case 'providers-health':
10285
+ result = await buildAllProvidersHealthReport(params || {});
10286
+ break;
10111
10287
  case 'doctor':
10112
10288
  {
10113
10289
  const doctorParams = isPlainObject(params) ? params : {};
@@ -10134,6 +10310,10 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
10134
10310
  break;
10135
10311
  case 'apply-claude-md-file':
10136
10312
  result = applyClaudeMdFile(params || {});
10313
+ if (result && !result.error) {
10314
+ const mdTarget = (params && params.targetPath) ? String(params.targetPath) : 'CLAUDE.md';
10315
+ notifyWebhook('claude-md-edit', 'CLAUDE.md modified: ' + mdTarget, { targetPath: mdTarget }).catch(function () {});
10316
+ }
10137
10317
  break;
10138
10318
  case 'preview-agents-diff':
10139
10319
  result = buildAgentsDiff(params || {});
@@ -10209,7 +10389,32 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
10209
10389
  break;
10210
10390
  case 'apply-claude-config':
10211
10391
  result = applyToClaudeSettings(params.config);
10392
+ if (result && !result.error) {
10393
+ const cfgName = (params && params.config && typeof params.config.name === 'string') ? params.config.name : '';
10394
+ const cfgFrom = (params && typeof params.previousName === 'string') ? params.previousName : '';
10395
+ const summary = cfgFrom
10396
+ ? ('Provider switched: ' + cfgFrom + ' -> ' + cfgName)
10397
+ : ('Provider applied: ' + cfgName);
10398
+ notifyWebhook('provider-switch', summary, { name: cfgName, previousName: cfgFrom }).catch(function () {});
10399
+ }
10400
+ break;
10401
+ case 'get-webhook-config':
10402
+ result = loadWebhookConfig();
10403
+ break;
10404
+ case 'set-webhook-config':
10405
+ result = saveWebhookConfig(params && params.config ? params.config : {});
10212
10406
  break;
10407
+ case 'test-webhook': {
10408
+ const overrideCfg = params && params.config ? params.config : null;
10409
+ const probe = await notifyWebhook(
10410
+ 'provider-switch',
10411
+ 'codexmate webhook test ping',
10412
+ { test: true },
10413
+ overrideCfg ? { config: overrideCfg } : {}
10414
+ );
10415
+ result = probe;
10416
+ break;
10417
+ }
10213
10418
  case 'export-claude-share':
10214
10419
  result = buildClaudeSharePayload(params && params.config ? params.config : {});
10215
10420
  break;
@@ -10421,6 +10626,18 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
10421
10626
  case 'proxy-apply-provider':
10422
10627
  result = applyBuiltinProxyProvider(params || {});
10423
10628
  break;
10629
+ case 'local-bridge-toggle':
10630
+ result = toggleLocalBridgeProvider(params || {});
10631
+ break;
10632
+ case 'local-bridge-status':
10633
+ result = getLocalBridgeStatus();
10634
+ break;
10635
+ case 'local-bridge-set-excluded':
10636
+ result = setLocalBridgeExcludedProviders(params || {});
10637
+ break;
10638
+ case 'local-bridge-get-excluded':
10639
+ result = getLocalBridgeExcludedProviders();
10640
+ break;
10424
10641
  case 'workflow-list':
10425
10642
  result = listWorkflowDefinitions();
10426
10643
  break;