codexmate 0.0.54 → 0.0.56

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 +3 -0
  2. package/README.vi.md +172 -0
  3. package/README.zh.md +2 -0
  4. package/cli/local-bridge.js +221 -22
  5. package/cli.js +666 -6
  6. package/package.json +1 -1
  7. package/web-ui/app.js +40 -0
  8. package/web-ui/logic.claude.mjs +8 -0
  9. package/web-ui/modules/app.computed.session.mjs +210 -0
  10. package/web-ui/modules/app.methods.claude-config.mjs +196 -13
  11. package/web-ui/modules/app.methods.codex-config.mjs +294 -65
  12. package/web-ui/modules/app.methods.index.mjs +4 -0
  13. package/web-ui/modules/app.methods.navigation.mjs +7 -1
  14. package/web-ui/modules/app.methods.provider-cache.mjs +223 -0
  15. package/web-ui/modules/app.methods.providers.mjs +15 -1
  16. package/web-ui/modules/app.methods.runtime.mjs +7 -2
  17. package/web-ui/modules/app.methods.session-actions.mjs +36 -0
  18. package/web-ui/modules/app.methods.session-browser.mjs +3 -0
  19. package/web-ui/modules/app.methods.session-trash.mjs +3 -0
  20. package/web-ui/modules/app.methods.startup-claude.mjs +47 -4
  21. package/web-ui/modules/app.methods.web-ui-preferences.mjs +161 -0
  22. package/web-ui/modules/i18n/locales/en.mjs +106 -3
  23. package/web-ui/modules/i18n/locales/ja.mjs +103 -3
  24. package/web-ui/modules/i18n/locales/vi.mjs +950 -0
  25. package/web-ui/modules/i18n/locales/zh-tw.mjs +103 -3
  26. package/web-ui/modules/i18n/locales/zh.mjs +103 -3
  27. package/web-ui/modules/provider-default-names.mjs +25 -0
  28. package/web-ui/partials/index/layout-header.html +18 -5
  29. package/web-ui/partials/index/modal-health-check.html +69 -5
  30. package/web-ui/partials/index/modals-basic.html +182 -0
  31. package/web-ui/partials/index/panel-config-codex.html +2 -2
  32. package/web-ui/partials/index/panel-sessions.html +99 -19
  33. package/web-ui/partials/index/panel-settings.html +20 -0
  34. package/web-ui/res/web-ui-render.precompiled.js +742 -113
  35. package/web-ui/session-helpers.mjs +4 -1
  36. package/web-ui/styles/layout-shell.css +51 -1
  37. package/web-ui/styles/responsive.css +98 -0
  38. package/web-ui/styles/sessions-preview.css +212 -2
  39. package/web-ui/styles/sessions-toolbar-trash.css +61 -18
  40. package/web-ui/styles/settings-panel.css +398 -0
  41. package/web-ui/styles/skills-list.css +122 -0
  42. package/web-ui/styles/titles-cards.css +52 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/web-ui/app.js CHANGED
@@ -79,6 +79,8 @@ document.addEventListener('DOMContentLoaded', () => {
79
79
  projectPathOptionsLoading: false,
80
80
  showSkillsModal: false,
81
81
  showHealthCheckModal: false,
82
+ showProviderCacheModal: false,
83
+ showProviderCacheAnnouncementModal: false,
82
84
  showCodexBridgePoolModal: false,
83
85
  showClaudeBridgePoolModal: false,
84
86
  showWebhookModal: false,
@@ -270,6 +272,8 @@ document.addEventListener('DOMContentLoaded', () => {
270
272
  healthCheckBatchTotal: 0,
271
273
  healthCheckBatchDone: 0,
272
274
  healthCheckBatchFailed: 0,
275
+ healthCheckFailedProviderSelections: {},
276
+ healthCheckFailedProviderDeleting: false,
273
277
  installPackageManager: 'npm',
274
278
  installCommandAction: 'install',
275
279
  installRegistryPreset: 'npmmirror',
@@ -378,6 +382,14 @@ document.addEventListener('DOMContentLoaded', () => {
378
382
  codexDownloadLoading: false,
379
383
  codexDownloadProgress: 0,
380
384
  codexDownloadTimer: null,
385
+ providerCacheRecords: { root: '', generatedAt: '', groups: [] },
386
+ providerCacheLoadedOnce: false,
387
+ providerCacheLoadedAt: '',
388
+ providerCacheLoading: false,
389
+ providerCacheSyncing: false,
390
+ providerCacheSyncMessage: '',
391
+ providerCacheError: '',
392
+ providerCacheRequestSeq: 0,
381
393
  settingsTab: 'general',
382
394
  toolConfigPermissions: (function() {
383
395
  try {
@@ -506,6 +518,18 @@ document.addEventListener('DOMContentLoaded', () => {
506
518
  if (typeof this.loadWebhookSettings === 'function') {
507
519
  this.loadWebhookSettings();
508
520
  }
521
+ if (typeof this.loadWebUiPreferences === 'function') {
522
+ const applyPreferenceNavigation = (() => {
523
+ try {
524
+ const url = new URL(window.location.href);
525
+ if (url.pathname === '/session') return false;
526
+ return !String(url.searchParams.get('tab') || '').trim();
527
+ } catch (_) {
528
+ return true;
529
+ }
530
+ })();
531
+ void this.loadWebUiPreferences({ applyNavigation: applyPreferenceNavigation });
532
+ }
509
533
  if (typeof this.t === 'function') {
510
534
  this.confirmDialogConfirmText = this.t('confirm.ok');
511
535
  this.confirmDialogCancelText = this.t('confirm.cancel');
@@ -693,8 +717,14 @@ document.addEventListener('DOMContentLoaded', () => {
693
717
  if (typeof this.loadAppVersionStatus === 'function') {
694
718
  void this.loadAppVersionStatus({ silent: true });
695
719
  }
720
+ if (typeof this.hydrateClaudeConfigsFromProviderCache === 'function') {
721
+ await this.hydrateClaudeConfigsFromProviderCache({ silent: true });
722
+ }
696
723
  void this.refreshClaudeSelectionFromSettings({ silent: true });
697
724
  void this.syncDefaultOpenclawConfigEntry({ silent: true });
725
+ if (typeof this.loadProviderCacheRecords === 'function') {
726
+ void this.loadProviderCacheRecords({ background: true });
727
+ }
698
728
  };
699
729
  if (typeof requestAnimationFrame === 'function') {
700
730
  this._initialLoadRafId = requestAnimationFrame(() => {
@@ -743,6 +773,10 @@ document.addEventListener('DOMContentLoaded', () => {
743
773
  clearTimeout(this._initialLoadTimer);
744
774
  this._initialLoadTimer = 0;
745
775
  }
776
+ if (this.__webUiPreferencesPersistTimer) {
777
+ clearTimeout(this.__webUiPreferencesPersistTimer);
778
+ this.__webUiPreferencesPersistTimer = 0;
779
+ }
746
780
  window.removeEventListener('resize', this.onWindowResize);
747
781
  window.removeEventListener('keydown', this.handleGlobalKeydown);
748
782
  window.removeEventListener('beforeunload', this.handleBeforeUnload);
@@ -767,6 +801,9 @@ document.addEventListener('DOMContentLoaded', () => {
767
801
  try {
768
802
  localStorage.setItem('codexmate_prompts_sub_tab', newVal);
769
803
  } catch (_) {}
804
+ if (typeof this.persistWebUiPreferences === 'function') {
805
+ this.persistWebUiPreferences({ promptsSubTab: newVal });
806
+ }
770
807
  if (this.mainTab === 'prompts' && typeof this.loadPromptsContent === 'function') {
771
808
  this.loadPromptsContent();
772
809
  }
@@ -779,6 +816,9 @@ document.addEventListener('DOMContentLoaded', () => {
779
816
  localStorage.removeItem('codexmate_project_claude_md_path');
780
817
  }
781
818
  } catch (_) {}
819
+ if (typeof this.persistWebUiPreferences === 'function') {
820
+ this.persistWebUiPreferences({ projectClaudeMdPath: newPath || '' });
821
+ }
782
822
  }
783
823
  },
784
824
 
@@ -191,6 +191,14 @@ export function matchClaudeConfigFromSettings(claudeConfigs = {}, env = {}) {
191
191
  if (normalizedSettings.apiKey && normalizedConfig.apiKey === normalizedSettings.apiKey) {
192
192
  return name;
193
193
  }
194
+ if (normalizedSettings.apiKey
195
+ && normalizedConfig.apiKey === ''
196
+ && config
197
+ && typeof config.providerCacheRef === 'string'
198
+ && config.providerCacheRef.trim()
199
+ && config.hasKey === true) {
200
+ return name;
201
+ }
194
202
  if (!normalizedSettings.apiKey
195
203
  && normalizedConfig.apiKey === ''
196
204
  && normalizedConfig.externalCredentialType
@@ -51,6 +51,190 @@ function formatUsageRangeLabel(range, t) {
51
51
  return '近 7 天';
52
52
  }
53
53
 
54
+ function normalizeWorkspaceText(value) {
55
+ if (value == null) {
56
+ return '';
57
+ }
58
+ return String(value)
59
+ .replace(/<[^>]*>/g, ' ')
60
+ .replace(/&nbsp;/gi, ' ')
61
+ .replace(/&lt;/gi, '<')
62
+ .replace(/&gt;/gi, '>')
63
+ .replace(/&amp;/gi, '&')
64
+ .replace(/\s+/g, ' ')
65
+ .trim();
66
+ }
67
+
68
+ function trimWorkspaceLine(value, maxLength = 160) {
69
+ const text = normalizeWorkspaceText(value);
70
+ if (!text) return '';
71
+ const limit = Number.isFinite(Number(maxLength)) ? Math.max(20, Math.floor(Number(maxLength))) : 160;
72
+ return text.length > limit ? `${text.slice(0, Math.max(0, limit - 1)).trimEnd()}…` : text;
73
+ }
74
+
75
+ function pushUniqueWorkspaceItem(items, value, limit = 6, maxLength = 160) {
76
+ if (!Array.isArray(items) || items.length >= limit) return;
77
+ const text = trimWorkspaceLine(value, maxLength);
78
+ if (!text) return;
79
+ const key = text.toLowerCase();
80
+ if (items.some(item => String(item || '').toLowerCase() === key)) return;
81
+ items.push(text);
82
+ }
83
+
84
+ function extractWorkspaceLines(text) {
85
+ return String(text || '')
86
+ .split(/\r?\n|(?:^|\s)(?:[-*•]|\d+[.)])\s+|[。;;]\s*/g)
87
+ .map(line => normalizeWorkspaceText(line))
88
+ .filter(Boolean);
89
+ }
90
+
91
+ function isWorkspaceCommandLine(line) {
92
+ return /^\$\s+/.test(line)
93
+ || /^(?:npm|pnpm|yarn|bun|node|npx|git|gh|codex|claude|gemini|codebuddy|openclaw|pytest|go test|cargo test|make|docker)\b/i.test(line);
94
+ }
95
+
96
+ function extractInlineWorkspaceCommands(text) {
97
+ const source = normalizeWorkspaceText(text);
98
+ if (!source) return [];
99
+ const matches = source.match(/(?:^|[^A-Za-z0-9_])(?:\$\s*)?(?:(?:npm|pnpm|yarn|bun|node|npx|git|gh|codex|claude|gemini|codebuddy|openclaw|pytest|make|docker)\s+(?:run\s+)?[A-Za-z0-9:_./@-]+(?:\s+&&\s+(?:npm|pnpm|yarn|bun|node|npx|git|gh|codex|claude|gemini|codebuddy|openclaw|pytest|make|docker)\s+(?:run\s+)?[A-Za-z0-9:_./@-]+)?|go\s+test(?:\s+[A-Za-z0-9:_./@-]+)?|cargo\s+test(?:\s+[A-Za-z0-9:_./@-]+)?)(?=$|\s|[,。;,;])/gi) || [];
100
+ return matches.map(match => match.replace(/^[^A-Za-z0-9_$]*\$?\s*/, '').trim());
101
+ }
102
+
103
+ function extractWorkspaceArtifacts(line, target) {
104
+ if (!target || typeof line !== 'string') return;
105
+ const urlMatches = line.match(/https?:\/\/[^\s)\]"'<>,。;、]+/gi) || [];
106
+ for (const url of urlMatches) {
107
+ pushUniqueWorkspaceItem(target.links, url.replace(/[,。;、.,;:!?]+$/g, ''), 5, 140);
108
+ }
109
+ const fileMatches = line.match(/(?:[A-Za-z]:[\\/]|\/)?(?:[A-Za-z0-9_.@+-]+[\\/])*[A-Za-z0-9_.@+-]+\.(?:mjs|js|cjs|ts|tsx|jsx|vue|css|scss|html|json|jsonl|md|yml|yaml|toml|lock|py|go|rs|sh|txt)/g) || [];
110
+ for (const filePath of fileMatches) {
111
+ pushUniqueWorkspaceItem(target.files, filePath, 8, 120);
112
+ }
113
+ }
114
+
115
+ function buildWorkspaceBriefText(summary, labels = {}) {
116
+ if (!summary || !summary.available) return '';
117
+ const label = (key, fallback) => (labels && labels[key]) || fallback;
118
+ const lines = [
119
+ `# ${label('title', 'Session workspace brief')}`,
120
+ '',
121
+ `${label('source', 'Source')}: ${summary.sourceLabel || summary.source || '-'}`,
122
+ `${label('messages', 'Messages')}: ${summary.messageCount}`,
123
+ `${label('path', 'Path')}: ${summary.cwd || '-'}`,
124
+ ''
125
+ ];
126
+ const sections = [
127
+ ['signals', label('signals', 'Signals'), summary.signals],
128
+ ['commands', label('commands', 'Reusable commands'), summary.commands],
129
+ ['files', label('files', 'Files'), summary.files],
130
+ ['links', label('links', 'Links'), summary.links],
131
+ ['risks', label('risks', 'Risks / blockers'), summary.risks],
132
+ ['nextSteps', label('nextSteps', 'Next steps'), summary.nextSteps]
133
+ ];
134
+ for (const [, sectionTitle, items] of sections) {
135
+ if (!Array.isArray(items) || !items.length) continue;
136
+ lines.push(`## ${sectionTitle}`);
137
+ for (const item of items) {
138
+ lines.push(`- ${item}`);
139
+ }
140
+ lines.push('');
141
+ }
142
+ return lines.join('\n').trimEnd();
143
+ }
144
+
145
+ export function buildSessionWorkspaceSummary(session, messages, options = {}) {
146
+ const safeMessages = Array.isArray(messages) ? messages : [];
147
+ const roleCounts = { user: 0, assistant: 0, system: 0, other: 0 };
148
+ const signals = [];
149
+ const commands = [];
150
+ const files = [];
151
+ const links = [];
152
+ const risks = [];
153
+ const nextSteps = [];
154
+ const target = { files, links };
155
+ let firstUser = '';
156
+ let lastUser = '';
157
+ let lastAssistant = '';
158
+ let firstTimestamp = '';
159
+ let lastTimestamp = '';
160
+
161
+ safeMessages.forEach((message) => {
162
+ if (!message || typeof message !== 'object') return;
163
+ const normalizedRole = String(message.normalizedRole || message.role || '').trim().toLowerCase();
164
+ const role = normalizedRole === 'user' || normalizedRole === 'assistant' || normalizedRole === 'system'
165
+ ? normalizedRole
166
+ : 'other';
167
+ roleCounts[role] += 1;
168
+ const timestamp = normalizeWorkspaceText(message.timestamp || '');
169
+ if (timestamp && !firstTimestamp) firstTimestamp = timestamp;
170
+ if (timestamp) lastTimestamp = timestamp;
171
+ const text = normalizeWorkspaceText(message.text || message.content || '');
172
+ if (!text) return;
173
+ if (role === 'user') {
174
+ if (!firstUser) firstUser = text;
175
+ lastUser = text;
176
+ } else if (role === 'assistant') {
177
+ lastAssistant = text;
178
+ }
179
+ const lines = extractWorkspaceLines(message.text || message.content || text);
180
+ for (const command of extractInlineWorkspaceCommands(message.text || message.content || text)) {
181
+ pushUniqueWorkspaceItem(commands, command, 6, 140);
182
+ }
183
+ for (const line of lines) {
184
+ extractWorkspaceArtifacts(line, target);
185
+ if (isWorkspaceCommandLine(line)) {
186
+ pushUniqueWorkspaceItem(commands, line.replace(/^\$\s+/, ''), 6, 140);
187
+ }
188
+ if (/(?:blocker|blocked|risk|failed|failure|error|timeout|regression|冲突|失败|错误|阻塞|风险|不可合|超时|回归)/i.test(line)) {
189
+ pushUniqueWorkspaceItem(risks, line, 5, 150);
190
+ }
191
+ if (/(?:todo|next|follow[- ]?up|remaining|后续|下一步|待办|继续|剩余)/i.test(line)) {
192
+ pushUniqueWorkspaceItem(nextSteps, line, 5, 150);
193
+ }
194
+ }
195
+ });
196
+
197
+ pushUniqueWorkspaceItem(signals, firstUser, 3, 150);
198
+ if (lastUser && lastUser !== firstUser) {
199
+ pushUniqueWorkspaceItem(signals, lastUser, 3, 150);
200
+ }
201
+ pushUniqueWorkspaceItem(signals, lastAssistant, 3, 150);
202
+
203
+ const source = session && typeof session.source === 'string' ? session.source : '';
204
+ const sourceLabel = session && typeof session.sourceLabel === 'string' ? session.sourceLabel : source;
205
+ const cwd = session && typeof session.cwd === 'string' ? session.cwd : '';
206
+ const messageCount = safeMessages.length;
207
+ const available = messageCount > 0;
208
+ const metrics = [
209
+ { key: 'messages', value: String(messageCount), label: options.messagesLabel || 'Messages' },
210
+ { key: 'user', value: String(roleCounts.user), label: options.userLabel || 'User' },
211
+ { key: 'assistant', value: String(roleCounts.assistant), label: options.assistantLabel || 'Assistant' },
212
+ { key: 'commands', value: String(commands.length), label: options.commandsLabel || 'Commands' },
213
+ { key: 'artifacts', value: String(files.length + links.length), label: options.artifactsLabel || 'Artifacts' },
214
+ { key: 'risks', value: String(risks.length + nextSteps.length), label: options.risksLabel || 'Risks' }
215
+ ];
216
+
217
+ const summary = {
218
+ available,
219
+ source,
220
+ sourceLabel,
221
+ cwd,
222
+ firstTimestamp,
223
+ lastTimestamp,
224
+ messageCount,
225
+ roleCounts,
226
+ metrics,
227
+ signals,
228
+ commands,
229
+ files,
230
+ links,
231
+ risks,
232
+ nextSteps
233
+ };
234
+ summary.briefText = buildWorkspaceBriefText(summary, options.briefLabels || {});
235
+ return summary;
236
+ }
237
+
54
238
  function formatUsageDuration(value, options = {}) {
55
239
  const normalizedLang = typeof options.lang === 'string' ? options.lang.trim().toLowerCase() : '';
56
240
  const isEn = normalizedLang === 'en';
@@ -267,6 +451,32 @@ export function createSessionComputed() {
267
451
  if (index < 0) return 0;
268
452
  return Math.round(((index + 1) / this.sessionTimelineNodes.length) * 100);
269
453
  },
454
+ activeSessionWorkspaceSummary() {
455
+ if (this.mainTab !== 'sessions' || !this.activeSession) {
456
+ return buildSessionWorkspaceSummary(null, []);
457
+ }
458
+ const t = typeof this.t === 'function' ? this.t : null;
459
+ return buildSessionWorkspaceSummary(this.activeSession, this.activeSessionMessages, {
460
+ messagesLabel: t ? t('sessions.workspace.metric.messages') : 'Messages',
461
+ userLabel: t ? t('sessions.workspace.metric.user') : 'User',
462
+ assistantLabel: t ? t('sessions.workspace.metric.assistant') : 'Assistant',
463
+ commandsLabel: t ? t('sessions.workspace.metric.commands') : 'Commands',
464
+ artifactsLabel: t ? t('sessions.workspace.metric.artifacts') : 'Artifacts',
465
+ risksLabel: t ? t('sessions.workspace.metric.risks') : 'Risks',
466
+ briefLabels: {
467
+ title: t ? t('sessions.workspace.copy.title') : 'Session workspace brief',
468
+ source: t ? t('sessions.workspace.copy.source') : 'Source',
469
+ messages: t ? t('sessions.workspace.metric.messages') : 'Messages',
470
+ path: t ? t('sessions.workspace.copy.path') : 'Path',
471
+ signals: t ? t('sessions.workspace.signals') : 'Signals',
472
+ commands: t ? t('sessions.workspace.commands') : 'Reusable commands',
473
+ files: t ? t('sessions.workspace.files') : 'Files',
474
+ links: t ? t('sessions.workspace.links') : 'Links',
475
+ risks: t ? t('sessions.workspace.risks') : 'Risks / blockers',
476
+ nextSteps: t ? t('sessions.workspace.nextSteps') : 'Next steps'
477
+ }
478
+ });
479
+ },
270
480
  sessionQueryPlaceholder() {
271
481
  if (this.isSessionQueryEnabled) {
272
482
  return typeof this.t === 'function'