codexmate 0.0.43 → 0.0.45

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 (42) hide show
  1. package/README.md +2 -0
  2. package/README.zh.md +2 -0
  3. package/cli/claude-proxy.js +611 -14
  4. package/cli/update.js +77 -7
  5. package/cli.js +188 -21
  6. package/package.json +1 -1
  7. package/web-ui/app.js +36 -3
  8. package/web-ui/index.html +1 -0
  9. package/web-ui/logic.claude.mjs +65 -2
  10. package/web-ui/logic.runtime.mjs +0 -7
  11. package/web-ui/modules/app.computed.index.mjs +3 -1
  12. package/web-ui/modules/app.computed.main-tabs.mjs +3 -0
  13. package/web-ui/modules/app.computed.prompts.mjs +28 -0
  14. package/web-ui/modules/app.computed.session.mjs +23 -1
  15. package/web-ui/modules/app.methods.agents.mjs +50 -4
  16. package/web-ui/modules/app.methods.claude-config.mjs +28 -12
  17. package/web-ui/modules/app.methods.index.mjs +1 -1
  18. package/web-ui/modules/app.methods.install.mjs +129 -1
  19. package/web-ui/modules/app.methods.navigation.mjs +2 -1
  20. package/web-ui/modules/app.methods.session-actions.mjs +17 -2
  21. package/web-ui/modules/app.methods.session-timeline.mjs +0 -1
  22. package/web-ui/modules/app.methods.startup-claude.mjs +26 -3
  23. package/web-ui/modules/i18n/locales/en.mjs +42 -5
  24. package/web-ui/modules/i18n/locales/ja.mjs +42 -5
  25. package/web-ui/modules/i18n/locales/vi.mjs +51 -0
  26. package/web-ui/modules/i18n/locales/zh.mjs +42 -5
  27. package/web-ui/partials/index/layout-footer.html +1 -1
  28. package/web-ui/partials/index/layout-header.html +64 -0
  29. package/web-ui/partials/index/modal-config-template-agents.html +12 -13
  30. package/web-ui/partials/index/modals-basic.html +18 -1
  31. package/web-ui/partials/index/panel-config-claude.html +4 -7
  32. package/web-ui/partials/index/panel-config-codex.html +2 -6
  33. package/web-ui/partials/index/panel-prompts.html +100 -0
  34. package/web-ui/partials/index/panel-sessions.html +30 -10
  35. package/web-ui/partials/index/panel-usage.html +34 -18
  36. package/web-ui/res/web-ui-render.precompiled.js +579 -149
  37. package/web-ui/styles/controls-forms.css +5 -5
  38. package/web-ui/styles/layout-shell.css +145 -0
  39. package/web-ui/styles/modals-core.css +162 -0
  40. package/web-ui/styles/responsive.css +77 -5
  41. package/web-ui/styles/sessions-toolbar-trash.css +45 -10
  42. package/web-ui/styles/sessions-usage.css +31 -2
@@ -101,10 +101,6 @@ export function shouldForceCompactLayoutMode(options = {}) {
101
101
  const screenHeight = Number(options.screenHeight || 0);
102
102
  const shortEdge = Number(options.shortEdge || (screenWidth > 0 && screenHeight > 0 ? Math.min(screenWidth, screenHeight) : 0));
103
103
  const maxTouchPoints = Number(options.maxTouchPoints || 0);
104
- const userAgent = typeof options.userAgent === 'string' ? options.userAgent : '';
105
- const isMobileUa = typeof options.isMobileUa === 'boolean'
106
- ? options.isMobileUa
107
- : /(Android|iPhone|iPad|iPod|Mobile)/i.test(userAgent);
108
104
  const coarsePointer = !!options.coarsePointer;
109
105
  const noHover = !!options.noHover;
110
106
  const isSmallPhysicalScreen = shortEdge > 0 && shortEdge <= 920;
@@ -115,9 +111,6 @@ export function shouldForceCompactLayoutMode(options = {}) {
115
111
  if (!isNarrowViewport) {
116
112
  return false;
117
113
  }
118
- if (isMobileUa) {
119
- return true;
120
- }
121
114
  return pointerSuggestsTouchOnly && maxTouchPoints > 0;
122
115
  }
123
116
 
@@ -4,6 +4,7 @@ import { createSessionComputed } from './app.computed.session.mjs';
4
4
  import { createConfigModeComputed } from './config-mode.computed.mjs';
5
5
  import { createSkillsComputed } from './skills.computed.mjs';
6
6
  import { createPluginsComputed } from './plugins.computed.mjs';
7
+ import { createPromptsComputed } from './app.computed.prompts.mjs';
7
8
 
8
9
  export function createAppComputed() {
9
10
  return {
@@ -12,6 +13,7 @@ export function createAppComputed() {
12
13
  ...createMainTabsComputed(),
13
14
  ...createSkillsComputed(),
14
15
  ...createPluginsComputed(),
15
- ...createConfigModeComputed()
16
+ ...createConfigModeComputed(),
17
+ ...createPromptsComputed()
16
18
  };
17
19
  }
@@ -151,6 +151,7 @@ export function createMainTabsComputed() {
151
151
  if (this.mainTab === 'plugins') return this.t('kicker.plugins');
152
152
  if (this.mainTab === 'docs') return this.t('kicker.docs');
153
153
  if (this.mainTab === 'trash') return this.t('kicker.trash');
154
+ if (this.mainTab === 'prompts') return this.t('kicker.prompts');
154
155
  return this.t('kicker.settings');
155
156
  },
156
157
  mainTabTitle() {
@@ -163,6 +164,7 @@ export function createMainTabsComputed() {
163
164
  if (this.mainTab === 'plugins') return this.t('title.plugins');
164
165
  if (this.mainTab === 'docs') return this.t('title.docs');
165
166
  if (this.mainTab === 'trash') return this.t('settings.trash.title');
167
+ if (this.mainTab === 'prompts') return this.t('title.prompts');
166
168
  return this.t('title.settings');
167
169
  },
168
170
  mainTabSubtitle() {
@@ -175,6 +177,7 @@ export function createMainTabsComputed() {
175
177
  if (this.mainTab === 'plugins') return this.t('subtitle.plugins');
176
178
  if (this.mainTab === 'docs') return this.t('subtitle.docs');
177
179
  if (this.mainTab === 'trash') return this.t('settings.trash.meta');
180
+ if (this.mainTab === 'prompts') return this.t('subtitle.prompts');
178
181
  return this.t('subtitle.settings');
179
182
  },
180
183
  taskOrchestrationSelectedRun() {
@@ -0,0 +1,28 @@
1
+ export function createPromptsComputed() {
2
+ return {
3
+ promptsContextHint() {
4
+ if (!this.agentsLoading && !this.agentsDiffVisible && this.hasAgentsContentChanged()) {
5
+ return { text: this.t('modal.agents.unsaved.detectedHint'), warn: true };
6
+ }
7
+ if (this.agentsDiffVisible && (this.agentsDiffLoading || this.agentsSaving)) {
8
+ return { text: this.t('diff.hint.busy'), warn: false };
9
+ }
10
+ if (this.agentsDiffVisible && this.agentsDiffError) {
11
+ return { text: this.t('diff.hint.failedBack'), warn: false };
12
+ }
13
+ if (this.agentsDiffVisible && !this.agentsDiffHasChanges) {
14
+ return { text: this.t('diff.hint.noChangesBack'), warn: false };
15
+ }
16
+ if (this.agentsDiffVisible && this.agentsDiffTruncated) {
17
+ return { text: this.t('diff.viewHint.truncated'), warn: false };
18
+ }
19
+ if (this.agentsDiffVisible) {
20
+ return { text: this.t('diff.viewHint.preview'), warn: false };
21
+ }
22
+ if (!this.agentsLoading) {
23
+ return { text: this.t('modal.agents.hint.twoStepSave'), warn: false };
24
+ }
25
+ return null;
26
+ }
27
+ };
28
+ }
@@ -777,7 +777,7 @@ export function createSessionComputed() {
777
777
  ? this.sessionUsageDaily
778
778
  : null;
779
779
  if (!daily || !Array.isArray(daily.rows) || daily.rows.length === 0) {
780
- return { points: [], labels: [], linePath: '', areaPath: '', width: 800, maxTokens: 0 };
780
+ return { points: [], labels: [], linePath: '', areaPath: '', width: 800, maxTokens: 0, yTicks: [] };
781
781
  }
782
782
 
783
783
  const rows = daily.rows;
@@ -806,6 +806,27 @@ export function createSessionComputed() {
806
806
  const selectedKey = this.sessionsUsageSelectedDayKey;
807
807
  const selectedPoint = points.find(p => p.key === selectedKey) || points[points.length - 1] || null;
808
808
 
809
+ const yTicks = [];
810
+ if (maxTokens > 0) {
811
+ const rawStep = maxTokens / 4;
812
+ const magnitude = Math.pow(10, Math.floor(Math.log10(rawStep)));
813
+ const residual = rawStep / magnitude;
814
+ const niceStep = residual <= 1.5 ? magnitude : (residual <= 3.5 ? 2 * magnitude : 5 * magnitude);
815
+ const tickCount = Math.min(Math.ceil(maxTokens / niceStep), 8);
816
+ for (let i = 0; i <= tickCount; i++) {
817
+ const value = i * niceStep;
818
+ if (value > maxTokens + niceStep * 0.5) break;
819
+ const normalized = value / maxTokens;
820
+ const y = padding.top + chartHeight - (normalized * chartHeight);
821
+ yTicks.push({
822
+ value,
823
+ label: formatCompactUsageSummaryNumber(value),
824
+ y,
825
+ percent: (((height - y) / height) * 100).toFixed(1)
826
+ });
827
+ }
828
+ }
829
+
809
830
  return {
810
831
  points,
811
832
  labels: rows.map((row, index) => ({
@@ -817,6 +838,7 @@ export function createSessionComputed() {
817
838
  width,
818
839
  height,
819
840
  maxTokens,
841
+ yTicks,
820
842
  hoverX: selectedPoint ? selectedPoint.x : 0,
821
843
  hoverY: selectedPoint ? selectedPoint.y : 0
822
844
  };
@@ -635,17 +635,63 @@ export function createAgentsMethods(options = {}) {
635
635
  return;
636
636
  }
637
637
  const successLabel = this.agentsContext === 'openclaw-workspace'
638
- ? `工作区文件已保存${this.agentsWorkspaceFileName ? `: ${this.agentsWorkspaceFileName}` : ''}`
638
+ ? this.t('toast.agents.saved.workspace', { name: this.agentsWorkspaceFileName || '' }).replace(/:\s*$/, '')
639
639
  : (this.agentsContext === 'claude-md'
640
- ? 'CLAUDE.md 已保存'
641
- : (this.agentsContext === 'openclaw' ? 'OpenClaw AGENTS.md 已保存' : 'AGENTS.md 已保存'));
640
+ ? this.t('toast.agents.saved.claudeMd')
641
+ : (this.agentsContext === 'openclaw' ? this.t('toast.agents.saved.openclaw') : this.t('toast.agents.saved.agents')));
642
642
  this.showMessage(successLabel, 'success');
643
- this.closeAgentsModal({ force: true });
643
+ if (this.mainTab === 'prompts') {
644
+ this.loadPromptsContent();
645
+ } else {
646
+ this.closeAgentsModal({ force: true });
647
+ }
644
648
  } catch (e) {
645
649
  this.showMessage(this.t('toast.save.fail'), 'error');
646
650
  } finally {
647
651
  this.agentsSaving = false;
648
652
  }
653
+ },
654
+
655
+ switchPromptsSubTab(subTab) {
656
+ const normalized = subTab === 'claude-md' ? 'claude-md' : 'codex';
657
+ if (this.promptsSubTab === normalized) {
658
+ this.loadPromptsContent();
659
+ return;
660
+ }
661
+ this.promptsSubTab = normalized;
662
+ },
663
+
664
+ async loadPromptsContent() {
665
+ const requestToken = issueLatestRequestToken(this, '_agentsOpenRequestToken');
666
+ this.agentsLoading = true;
667
+ this.resetAgentsDiffState();
668
+ try {
669
+ const isClaude = this.promptsSubTab === 'claude-md';
670
+ const action = isClaude ? 'get-claude-md-file' : 'get-agents-file';
671
+ const res = await api(action);
672
+ if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
673
+ return;
674
+ }
675
+ if (res.error) {
676
+ this.showMessage(res.error, 'error');
677
+ return;
678
+ }
679
+ this.agentsContent = res.content || '';
680
+ this.agentsOriginalContent = this.agentsContent;
681
+ this.agentsPath = res.path || '';
682
+ this.agentsExists = !!res.exists;
683
+ this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
684
+ this.agentsContext = isClaude ? 'claude-md' : 'codex';
685
+ } catch (e) {
686
+ if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
687
+ return;
688
+ }
689
+ this.showMessage(this.t('toast.load.fail'), 'error');
690
+ } finally {
691
+ if (isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
692
+ this.agentsLoading = false;
693
+ }
694
+ }
649
695
  }
650
696
  };
651
697
  }
@@ -23,6 +23,10 @@ function getClaudeConfigValidationForContext(vm, mode = 'add') {
23
23
  const externalCredentialType = normalizeClaudeText(draft && draft.externalCredentialType);
24
24
  const baseUrl = normalizeClaudeBaseUrl(draft && draft.baseUrl);
25
25
  const model = normalizeClaudeText(draft && draft.model);
26
+ const targetApiRaw = normalizeClaudeText(draft && draft.targetApi).toLowerCase();
27
+ const targetApi = targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions'
28
+ ? 'chat_completions'
29
+ : (targetApiRaw === 'ollama' ? 'ollama' : 'responses');
26
30
  const errors = {
27
31
  name: '',
28
32
  apiKey: '',
@@ -36,7 +40,7 @@ function getClaudeConfigValidationForContext(vm, mode = 'add') {
36
40
  errors.name = vm.t('validation.claude.nameExists');
37
41
  }
38
42
 
39
- if (!apiKey && !externalCredentialType) {
43
+ if (!apiKey && !externalCredentialType && targetApi !== 'ollama') {
40
44
  errors.apiKey = vm.t('validation.claude.apiKeyRequired');
41
45
  }
42
46
 
@@ -57,6 +61,7 @@ function getClaudeConfigValidationForContext(vm, mode = 'add') {
57
61
  externalCredentialType,
58
62
  baseUrl,
59
63
  model,
64
+ targetApi,
60
65
  errors,
61
66
  ok: !errors.name && !errors.apiKey && !errors.baseUrl && !errors.model
62
67
  };
@@ -88,7 +93,7 @@ export function createClaudeConfigMethods(options = {}) {
88
93
  this.claudeConfigs[name] = this.mergeClaudeConfig(existing, { model });
89
94
  this.saveClaudeConfigs();
90
95
  this.updateClaudeModelsCurrent();
91
- if (!this.claudeConfigs[name].apiKey && !this.claudeConfigs[name].externalCredentialType) {
96
+ if (!this.claudeConfigs[name].apiKey && !this.claudeConfigs[name].externalCredentialType && this.claudeConfigs[name].targetApi !== 'ollama') {
92
97
  this.showMessage(this.t('toast.claude.apiKeyRequired'), 'error');
93
98
  return;
94
99
  }
@@ -115,8 +120,10 @@ export function createClaudeConfigMethods(options = {}) {
115
120
  this.newClaudeConfig = {
116
121
  name: '',
117
122
  apiKey: config.apiKey || '',
123
+ externalCredentialType: config.externalCredentialType || '',
118
124
  baseUrl: config.baseUrl || '',
119
- model: config.model || ''
125
+ model: config.model || '',
126
+ targetApi: config.targetApi || 'responses'
120
127
  };
121
128
  this.showAddClaudeConfigKey = false;
122
129
  this.showClaudeConfigModal = true;
@@ -144,7 +151,8 @@ export function createClaudeConfigMethods(options = {}) {
144
151
  apiKey: config.apiKey || '',
145
152
  externalCredentialType: config.externalCredentialType || '',
146
153
  baseUrl: config.baseUrl || '',
147
- model: config.model || ''
154
+ model: config.model || '',
155
+ targetApi: config.targetApi || 'responses'
148
156
  };
149
157
  this.showEditClaudeConfigKey = false;
150
158
  this.showEditConfigModal = true;
@@ -157,8 +165,10 @@ export function createClaudeConfigMethods(options = {}) {
157
165
  }
158
166
  const name = validation.name;
159
167
  this.editingConfig.apiKey = validation.apiKey;
168
+ this.editingConfig.externalCredentialType = validation.externalCredentialType;
160
169
  this.editingConfig.baseUrl = validation.baseUrl;
161
170
  this.editingConfig.model = validation.model;
171
+ this.editingConfig.targetApi = validation.targetApi;
162
172
  this.claudeConfigs[name] = this.mergeClaudeConfig(this.claudeConfigs[name], this.editingConfig);
163
173
  this.saveClaudeConfigs();
164
174
  this.showMessage(this.t('toast.operation.success'), 'success');
@@ -171,7 +181,7 @@ export function createClaudeConfigMethods(options = {}) {
171
181
  closeEditConfigModal() {
172
182
  this.showEditConfigModal = false;
173
183
  this.showEditClaudeConfigKey = false;
174
- this.editingConfig = { name: '', apiKey: '', externalCredentialType: '', baseUrl: '', model: '' };
184
+ this.editingConfig = { name: '', apiKey: '', externalCredentialType: '', baseUrl: '', model: '', targetApi: 'responses' };
175
185
  },
176
186
 
177
187
  toggleEditClaudeConfigKey() {
@@ -185,13 +195,15 @@ export function createClaudeConfigMethods(options = {}) {
185
195
  }
186
196
  const name = validation.name;
187
197
  this.editingConfig.apiKey = validation.apiKey;
198
+ this.editingConfig.externalCredentialType = validation.externalCredentialType;
188
199
  this.editingConfig.baseUrl = validation.baseUrl;
189
200
  this.editingConfig.model = validation.model;
201
+ this.editingConfig.targetApi = validation.targetApi;
190
202
  this.claudeConfigs[name] = this.mergeClaudeConfig(this.claudeConfigs[name], this.editingConfig);
191
203
  this.saveClaudeConfigs();
192
204
 
193
205
  const config = this.claudeConfigs[name];
194
- if (!config.apiKey) {
206
+ if (!config.apiKey && config.targetApi !== 'ollama') {
195
207
  this.showMessage(this.t('toast.claude.savedWithoutKey'), 'info');
196
208
  this.closeEditConfigModal();
197
209
  if (name === this.currentClaudeConfig) {
@@ -200,9 +212,9 @@ export function createClaudeConfigMethods(options = {}) {
200
212
  return;
201
213
  }
202
214
 
203
- const _claudeKey = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}`;
215
+ const _claudeKey = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}|${config.targetApi || "responses"}`;
204
216
  try {
205
- const res = await api('apply-claude-config', { config });
217
+ const res = await api('apply-claude-config', { config: { ...config, name } });
206
218
  if (res.error || res.success === false) {
207
219
  this.showMessage(res.error || this.t('toast.apply.fail'), 'error');
208
220
  } else {
@@ -226,8 +238,10 @@ export function createClaudeConfigMethods(options = {}) {
226
238
  }
227
239
  this.newClaudeConfig.name = validation.name;
228
240
  this.newClaudeConfig.apiKey = validation.apiKey;
241
+ this.newClaudeConfig.externalCredentialType = validation.externalCredentialType;
229
242
  this.newClaudeConfig.baseUrl = validation.baseUrl;
230
243
  this.newClaudeConfig.model = validation.model;
244
+ this.newClaudeConfig.targetApi = validation.targetApi;
231
245
  const name = validation.name;
232
246
  const duplicateName = this.findDuplicateClaudeConfigName(this.newClaudeConfig);
233
247
  if (duplicateName) {
@@ -271,16 +285,16 @@ export function createClaudeConfigMethods(options = {}) {
271
285
  this.refreshClaudeModelContext();
272
286
  const config = this.claudeConfigs[name];
273
287
 
274
- if (!config.apiKey) {
288
+ if (!config.apiKey && config.targetApi !== 'ollama') {
275
289
  if (config.externalCredentialType) {
276
290
  return this.showMessage(this.t('toast.claude.externalAuth'), 'info');
277
291
  }
278
292
  return this.showMessage(this.t('toast.claude.apiKeyRequired'), 'error');
279
293
  }
280
294
 
281
- const _claudeKey2 = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}`;
295
+ const _claudeKey2 = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}|${config.targetApi || "responses"}`;
282
296
  try {
283
- const res = await api('apply-claude-config', { config });
297
+ const res = await api('apply-claude-config', { config: { ...config, name } });
284
298
  if (res.error || res.success === false) {
285
299
  this.showMessage(res.error || this.t('toast.apply.fail'), 'error');
286
300
  } else {
@@ -300,8 +314,10 @@ export function createClaudeConfigMethods(options = {}) {
300
314
  this.newClaudeConfig = {
301
315
  name: '',
302
316
  apiKey: '',
317
+ externalCredentialType: '',
303
318
  baseUrl: '',
304
- model: ''
319
+ model: '',
320
+ targetApi: 'responses'
305
321
  };
306
322
  },
307
323
 
@@ -89,7 +89,7 @@ export function createAppMethods() {
89
89
  api,
90
90
  defaultOpenclawTemplate: DEFAULT_OPENCLAW_TEMPLATE
91
91
  }),
92
- ...createInstallMethods(),
92
+ ...createInstallMethods({ api }),
93
93
  ...createRuntimeMethods({ api }),
94
94
  ...createTaskOrchestrationMethods({ api })
95
95
  };
@@ -1,4 +1,5 @@
1
- export function createInstallMethods() {
1
+ export function createInstallMethods(options = {}) {
2
+ const { api } = options;
2
3
  return {
3
4
  normalizeInstallPackageManager(value) {
4
5
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
@@ -16,6 +17,133 @@ export function createInstallMethods() {
16
17
  return 'install';
17
18
  },
18
19
 
20
+ normalizePackageVersion(value) {
21
+ const normalized = typeof value === 'string' ? value.trim().replace(/^v/i, '') : '';
22
+ return /^\d+(?:\.\d+){0,2}(?:[-+][0-9A-Za-z.-]+)?$/.test(normalized) ? normalized : '';
23
+ },
24
+
25
+ comparePackageVersions(left, right) {
26
+ const normalizeParts = (value) => {
27
+ const normalized = this.normalizePackageVersion(value);
28
+ if (!normalized) return null;
29
+ return normalized.split(/[+-]/)[0].split('.').map((part) => Number.parseInt(part, 10) || 0);
30
+ };
31
+ const a = normalizeParts(left);
32
+ const b = normalizeParts(right);
33
+ if (!a || !b) return 0;
34
+ for (let i = 0; i < 3; i += 1) {
35
+ const diff = (a[i] || 0) - (b[i] || 0);
36
+ if (diff < 0) return -1;
37
+ if (diff > 0) return 1;
38
+ }
39
+ return 0;
40
+ },
41
+
42
+ isAppUpdateAvailable() {
43
+ const current = this.normalizePackageVersion(this.appVersion);
44
+ const latest = this.normalizePackageVersion(this.appLatestVersion);
45
+ if (!current || !latest) return false;
46
+ return this.comparePackageVersions(current, latest) < 0;
47
+ },
48
+
49
+ isAppVersionStatusVisible() {
50
+ return !!(this.appVersion || this.appLatestVersion || this.appVersionStatusLoading || this.appVersionStatusChecked || this.appVersionStatusError);
51
+ },
52
+
53
+ appVersionStatusKind() {
54
+ if (this.appVersionStatusLoading) return 'loading';
55
+ if (this.appVersionStatusError) return 'error';
56
+ if (this.isAppUpdateAvailable()) return 'available';
57
+ if (this.appVersionStatusChecked) return 'current';
58
+ return 'idle';
59
+ },
60
+
61
+ appUpdateNoticeText() {
62
+ if (this.appVersionStatusLoading) return this.t('side.update.checking');
63
+ if (this.appVersionStatusError) return this.t('side.update.retry');
64
+ const latest = this.normalizePackageVersion(this.appLatestVersion);
65
+ if (this.isAppUpdateAvailable()) return latest
66
+ ? this.t('side.update.availableWithVersion', { version: latest })
67
+ : this.t('side.update.available');
68
+ if (this.appVersionStatusChecked) return this.t('side.update.upToDate');
69
+ return this.t('side.update.check');
70
+ },
71
+
72
+ appUpdateNoticeMeta() {
73
+ if (this.appVersionStatusLoading) return this.t('side.update.checkingMeta');
74
+ if (this.appVersionStatusError) return this.appVersionStatusError;
75
+ const current = this.normalizePackageVersion(this.appVersion);
76
+ const latest = this.normalizePackageVersion(this.appLatestVersion);
77
+ if (current && latest) {
78
+ return this.t('side.update.metaVersions', { current, latest });
79
+ }
80
+ if (current) {
81
+ return this.t('side.update.currentOnly', { current });
82
+ }
83
+ return this.t('side.update.meta');
84
+ },
85
+
86
+ appVersionStatusTitle() {
87
+ const source = typeof this.appVersionStatusSource === 'string' ? this.appVersionStatusSource.trim() : '';
88
+ const checkedAt = typeof this.appVersionStatusCheckedAt === 'string' ? this.appVersionStatusCheckedAt.trim() : '';
89
+ const suffix = [source, checkedAt].filter(Boolean).join(' · ');
90
+ const meta = this.appUpdateNoticeMeta();
91
+ return suffix ? `${meta} · ${suffix}` : meta;
92
+ },
93
+
94
+ handleAppVersionStatusClick() {
95
+ if (this.isAppUpdateAvailable()) {
96
+ this.openAppUpdateDocs();
97
+ return;
98
+ }
99
+ void this.loadAppVersionStatus({ silent: false, force: true });
100
+ },
101
+
102
+ async loadAppVersionStatus(options = {}) {
103
+ if (typeof api !== 'function') return false;
104
+ if (this.appVersionStatusLoading) return false;
105
+ this.appVersionStatusLoading = true;
106
+ this.appVersionStatusError = '';
107
+ try {
108
+ const res = await api('version-status', options.force ? { force: true } : {});
109
+ if (res && res.currentVersion && !this.appVersion) {
110
+ this.appVersion = this.normalizePackageVersion(res.currentVersion) || String(res.currentVersion || '');
111
+ }
112
+ if (res && res.latestVersion) {
113
+ this.appLatestVersion = this.normalizePackageVersion(res.latestVersion) || String(res.latestVersion || '');
114
+ }
115
+ if (res && typeof res.source === 'string') {
116
+ this.appVersionStatusSource = res.source;
117
+ }
118
+ if (res && typeof res.checkedAt === 'string') {
119
+ this.appVersionStatusCheckedAt = res.checkedAt;
120
+ }
121
+ if (res && res.error) {
122
+ this.appVersionStatusError = res.error;
123
+ this.appVersionStatusChecked = true;
124
+ if (!options.silent) this.showMessage(res.error, 'error');
125
+ return false;
126
+ }
127
+ this.appVersionStatusChecked = true;
128
+ return true;
129
+ } catch (e) {
130
+ const message = e && e.message ? e.message : this.t('side.update.checkFailed');
131
+ this.appVersionStatusError = message;
132
+ this.appVersionStatusChecked = true;
133
+ if (!options.silent) this.showMessage(message, 'error');
134
+ return false;
135
+ } finally {
136
+ this.appVersionStatusLoading = false;
137
+ }
138
+ },
139
+
140
+ openAppUpdateDocs() {
141
+ this.installCommandAction = 'update';
142
+ if (typeof this.switchMainTab === 'function') {
143
+ this.switchMainTab('docs');
144
+ }
145
+ },
146
+
19
147
  normalizeInstallRegistryPreset(value) {
20
148
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
21
149
  if (normalized === 'default' || normalized === 'npmmirror' || normalized === 'tencent' || normalized === 'custom') {
@@ -15,7 +15,8 @@
15
15
  'plugins',
16
16
  'docs',
17
17
  'settings',
18
- 'trash'
18
+ 'trash',
19
+ 'prompts'
19
20
  ]);
20
21
  const loadDoctorOverview = async (vm, options = {}) => {
21
22
  if (!vm || typeof vm !== 'object') return false;
@@ -90,6 +90,10 @@ export function createSessionActionMethods(options = {}) {
90
90
  this.loadSessionStandalonePlain();
91
91
  },
92
92
 
93
+ canBuildStandaloneUrl(session) {
94
+ return !!this.buildSessionStandaloneUrl(session);
95
+ },
96
+
93
97
  buildSessionStandaloneUrl(session) {
94
98
  if (!session) return '';
95
99
  const source = typeof session.source === 'string' ? session.source.trim().toLowerCase() : '';
@@ -129,6 +133,12 @@ export function createSessionActionMethods(options = {}) {
129
133
  this.showMessage(this.t('toast.copy.fail'), 'error');
130
134
  },
131
135
 
136
+ openSessionLink(session) {
137
+ const url = this.buildSessionStandaloneUrl(session);
138
+ if (!url) { this.showMessage(this.t('toast.link.fail'), 'error'); return; }
139
+ window.open(url, '_blank', 'noopener,noreferrer');
140
+ },
141
+
132
142
  getSessionFilePath(session) {
133
143
  const filePath = typeof session?.filePath === 'string' ? session.filePath.trim() : '';
134
144
  return filePath;
@@ -412,11 +422,16 @@ export function createSessionActionMethods(options = {}) {
412
422
  const model = typeof payload.model === 'string' && payload.model.trim()
413
423
  ? payload.model.trim()
414
424
  : 'glm-4.7';
415
- if (!baseUrl || !apiKey) return '';
425
+ const targetApiRaw = typeof payload.targetApi === 'string' ? payload.targetApi.trim().toLowerCase() : '';
426
+ const targetApi = targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions'
427
+ ? 'chat_completions'
428
+ : (targetApiRaw === 'ollama' ? 'ollama' : 'responses');
429
+ if (!baseUrl || (!apiKey && targetApi !== 'ollama')) return '';
416
430
  const urlArg = this.quoteShellArg(baseUrl);
417
431
  const keyArg = this.quoteShellArg(apiKey);
418
432
  const modelArg = this.quoteShellArg(model);
419
- return `${this.getShareCommandPrefixInvocation()} claude ${urlArg} ${keyArg} ${modelArg}`;
433
+ const targetArg = targetApi !== 'responses' ? ` --target-api ${this.quoteShellArg(targetApi)}` : '';
434
+ return `${this.getShareCommandPrefixInvocation()} claude ${urlArg} ${keyArg} ${modelArg}${targetArg}`;
420
435
  },
421
436
 
422
437
  async copyProviderShareCommand(provider) {
@@ -354,7 +354,6 @@ export function createSessionTimelineMethods() {
354
354
  shortEdge,
355
355
  maxTouchPoints: touchPoints,
356
356
  userAgent,
357
- isMobileUa,
358
357
  coarsePointer,
359
358
  noHover
360
359
  });
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  findDuplicateClaudeConfigName,
3
3
  getClaudeModelCatalogForBaseUrl,
4
+ isLikelyBuiltinClaudeProxySettingsEnv,
5
+ matchBuiltinClaudeProxyConfigFromSettings,
4
6
  matchClaudeConfigFromSettings,
5
7
  normalizeClaudeConfig,
6
8
  normalizeClaudeSettingsEnv,
@@ -241,6 +243,14 @@ export function createStartupClaudeMethods(options = {}) {
241
243
  return matchClaudeConfigFromSettings(this.claudeConfigs, env);
242
244
  },
243
245
 
246
+ matchBuiltinClaudeProxyConfigFromSettings(env) {
247
+ return matchBuiltinClaudeProxyConfigFromSettings(this.claudeConfigs, env, this.currentClaudeConfig);
248
+ },
249
+
250
+ shouldSuppressClaudeSettingsImport(env) {
251
+ return isLikelyBuiltinClaudeProxySettingsEnv(env);
252
+ },
253
+
244
254
  findDuplicateClaudeConfigName(config) {
245
255
  return findDuplicateClaudeConfigName(this.claudeConfigs, config);
246
256
  },
@@ -256,7 +266,8 @@ export function createStartupClaudeMethods(options = {}) {
256
266
  baseUrl: next.baseUrl,
257
267
  model: next.model || previous.model || 'glm-4.7',
258
268
  hasKey: !!(next.apiKey || externalCredentialType),
259
- externalCredentialType
269
+ externalCredentialType,
270
+ targetApi: next.targetApi || previous.targetApi || 'responses'
260
271
  };
261
272
  },
262
273
 
@@ -332,7 +343,8 @@ export function createStartupClaudeMethods(options = {}) {
332
343
  }
333
344
  return;
334
345
  }
335
- const matchName = this.matchClaudeConfigFromSettings((res && res.env) || {});
346
+ const settingsEnv = (res && res.env) || {};
347
+ const matchName = this.matchClaudeConfigFromSettings(settingsEnv);
336
348
  if (matchName) {
337
349
  if (this.currentClaudeConfig !== matchName) {
338
350
  this.currentClaudeConfig = matchName;
@@ -341,7 +353,18 @@ export function createStartupClaudeMethods(options = {}) {
341
353
  this.refreshClaudeModelContext({ silentError: silentModelError });
342
354
  return;
343
355
  }
344
- const importedName = this.ensureClaudeConfigFromSettings((res && res.env) || {});
356
+ const builtinProxyMatch = this.matchBuiltinClaudeProxyConfigFromSettings(settingsEnv);
357
+ if (builtinProxyMatch) {
358
+ if (this.currentClaudeConfig !== builtinProxyMatch) {
359
+ this.currentClaudeConfig = builtinProxyMatch;
360
+ try { localStorage.setItem('currentClaudeConfig', builtinProxyMatch); } catch (_) {}
361
+ }
362
+ this.refreshClaudeModelContext({ silentError: silentModelError });
363
+ return;
364
+ }
365
+ const importedName = this.shouldSuppressClaudeSettingsImport(settingsEnv)
366
+ ? ''
367
+ : this.ensureClaudeConfigFromSettings(settingsEnv);
345
368
  if (importedName) {
346
369
  if (this.currentClaudeConfig !== importedName) {
347
370
  this.currentClaudeConfig = importedName;