codexmate 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web-ui/app.js CHANGED
@@ -7,8 +7,20 @@
7
7
  formatLatency,
8
8
  buildSpeedTestIssue,
9
9
  isSessionQueryEnabled,
10
- buildSessionListParams
10
+ buildSessionListParams,
11
+ normalizeSessionSource,
12
+ normalizeSessionPathFilter,
13
+ buildSessionFilterCacheState,
14
+ buildSessionTimelineNodes,
15
+ normalizeSessionMessageRole
11
16
  } from './logic.mjs';
17
+ import {
18
+ CONFIG_MODE_SET,
19
+ getProviderConfigModeMeta,
20
+ createConfigModeComputed
21
+ } from './modules/config-mode.computed.mjs';
22
+ import { createSkillsComputed } from './modules/skills.computed.mjs';
23
+ import { createSkillsMethods } from './modules/skills.methods.mjs';
12
24
 
13
25
  document.addEventListener('DOMContentLoaded', () => {
14
26
  if (typeof Vue === 'undefined') {
@@ -81,6 +93,7 @@
81
93
  showOpenclawConfigModal: false,
82
94
  showConfigTemplateModal: false,
83
95
  showAgentsModal: false,
96
+ showSkillsModal: false,
84
97
  showInstallModal: false,
85
98
  configTemplateContent: '',
86
99
  configTemplateApplying: false,
@@ -94,6 +107,19 @@
94
107
  agentsContext: 'codex',
95
108
  agentsModalTitle: 'AGENTS.md 编辑器',
96
109
  agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
110
+ skillsRootPath: '',
111
+ skillsList: [],
112
+ skillsSelectedNames: [],
113
+ skillsLoading: false,
114
+ skillsDeleting: false,
115
+ skillsKeyword: '',
116
+ skillsStatusFilter: 'all',
117
+ skillsImportList: [],
118
+ skillsImportSelectedKeys: [],
119
+ skillsScanningImports: false,
120
+ skillsImporting: false,
121
+ skillsZipImporting: false,
122
+ skillsExporting: false,
97
123
  sessionsList: [],
98
124
  sessionsLoading: false,
99
125
  sessionFilterSource: 'all',
@@ -124,6 +150,13 @@
124
150
  activeSessionDetailClipped: false,
125
151
  sessionDetailLoading: false,
126
152
  sessionDetailRequestSeq: 0,
153
+ sessionTimelineActiveKey: '',
154
+ sessionTimelineRafId: 0,
155
+ sessionMessageRefMap: Object.create(null),
156
+ sessionPreviewScrollEl: null,
157
+ sessionPreviewContainerEl: null,
158
+ sessionPreviewHeaderEl: null,
159
+ sessionPreviewHeaderResizeObserver: null,
127
160
  sessionStandalone: false,
128
161
  sessionStandaloneError: '',
129
162
  sessionStandaloneText: '',
@@ -269,6 +302,8 @@
269
302
  } else if (savedSessionYolo === '1' || savedSessionYolo === 'true') {
270
303
  this.sessionResumeWithYolo = true;
271
304
  }
305
+ this.restoreSessionFilterCache();
306
+ window.addEventListener('resize', this.onWindowResize);
272
307
  const savedConfigs = localStorage.getItem('claudeConfigs');
273
308
  if (savedConfigs) {
274
309
  try {
@@ -304,11 +339,31 @@
304
339
  }
305
340
  this.loadAll();
306
341
  },
342
+ beforeUnmount() {
343
+ this.cancelSessionTimelineSync();
344
+ this.disconnectSessionPreviewHeaderResizeObserver();
345
+ window.removeEventListener('resize', this.onWindowResize);
346
+ this.sessionPreviewScrollEl = null;
347
+ this.sessionPreviewContainerEl = null;
348
+ this.sessionPreviewHeaderEl = null;
349
+ this.sessionMessageRefMap = Object.create(null);
350
+ },
307
351
 
308
352
  computed: {
309
353
  isSessionQueryEnabled() {
310
354
  return isSessionQueryEnabled(this.sessionFilterSource);
311
355
  },
356
+ sessionTimelineNodes() {
357
+ return buildSessionTimelineNodes(this.activeSessionMessages, {
358
+ getKey: (message, index) => this.getRecordRenderKey(message, index)
359
+ });
360
+ },
361
+ sessionTimelineActiveTitle() {
362
+ if (!this.sessionTimelineActiveKey) return '';
363
+ const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
364
+ const matched = nodes.find(node => node.key === this.sessionTimelineActiveKey);
365
+ return matched ? matched.title : '';
366
+ },
312
367
  sessionQueryPlaceholder() {
313
368
  if (this.isSessionQueryEnabled) {
314
369
  return '关键词检索(支持 Codex/Claude,例:claude code)';
@@ -354,63 +409,10 @@
354
409
  installRegistryPreview() {
355
410
  return this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
356
411
  },
357
- inspectorMainTabLabel() {
358
- if (this.mainTab === 'config') return '配置中心';
359
- if (this.mainTab === 'sessions') return '会话浏览';
360
- if (this.mainTab === 'settings') return '设置';
361
- return '未知';
362
- },
363
- inspectorConfigModeLabel() {
364
- if (this.mainTab !== 'config') return '--';
365
- if (this.configMode === 'codex') return 'Codex';
366
- if (this.configMode === 'claude') return 'Claude Code';
367
- if (this.configMode === 'openclaw') return 'OpenClaw';
368
- return '未选择';
369
- },
370
- inspectorCurrentConfigLabel() {
371
- if (this.mainTab !== 'config') return '--';
372
- if (this.configMode === 'codex') {
373
- const provider = typeof this.currentProvider === 'string' ? this.currentProvider.trim() : '';
374
- return provider || '未选择';
375
- }
376
- if (this.configMode === 'claude') {
377
- const config = typeof this.currentClaudeConfig === 'string' ? this.currentClaudeConfig.trim() : '';
378
- return config || '未选择';
379
- }
380
- const openclaw = typeof this.currentOpenclawConfig === 'string' ? this.currentOpenclawConfig.trim() : '';
381
- return openclaw || '未选择';
382
- },
383
- inspectorCurrentModelLabel() {
384
- if (this.mainTab !== 'config') return '--';
385
- if (this.configMode === 'codex') {
386
- const model = typeof this.currentModel === 'string' ? this.currentModel.trim() : '';
387
- return model || '未选择';
388
- }
389
- if (this.configMode === 'claude') {
390
- const model = typeof this.currentClaudeModel === 'string' ? this.currentClaudeModel.trim() : '';
391
- return model || '未选择';
392
- }
393
- const model = this.openclawStructured && typeof this.openclawStructured.agentPrimary === 'string'
394
- ? this.openclawStructured.agentPrimary.trim()
395
- : '';
396
- return model || '按配置文件';
397
- },
398
- inspectorTemplateStatus() {
399
- if (this.mainTab !== 'config') return '--';
400
- if (this.configMode === 'codex') {
401
- if (this.configTemplateApplying || this.codexApplying) {
402
- return '模板应用中';
403
- }
404
- return '模板可编辑(手动确认应用)';
405
- }
406
- if (this.configMode === 'claude') {
407
- return '即时写入 Claude settings';
408
- }
409
- if (this.openclawApplying || this.openclawSaving) {
410
- return 'OpenClaw 保存/应用中';
411
- }
412
- return 'JSON5 可保存并应用';
413
- },
412
+ ...createSkillsComputed(),
413
+
414
+ ...createConfigModeComputed(),
415
+
414
416
  inspectorBusyStatus() {
415
417
  const tasks = [];
416
418
  if (this.loading) tasks.push('初始化');
@@ -418,6 +420,7 @@
418
420
  if (this.codexModelsLoading || this.claudeModelsLoading) tasks.push('模型加载');
419
421
  if (this.codexApplying || this.configTemplateApplying || this.openclawApplying) tasks.push('配置应用');
420
422
  if (this.agentsSaving) tasks.push('AGENTS 保存');
423
+ if (this.skillsLoading || this.skillsDeleting || this.skillsScanningImports || this.skillsImporting || this.skillsZipImporting || this.skillsExporting) tasks.push('Skills 管理');
421
424
  if (this.proxySaving || this.proxyApplying || this.proxyStarting || this.proxyStopping) tasks.push('代理更新');
422
425
  return tasks.length ? tasks.join(' / ') : '空闲';
423
426
  },
@@ -721,9 +724,12 @@
721
724
  },
722
725
 
723
726
  switchConfigMode(mode) {
727
+ const normalizedMode = typeof mode === 'string'
728
+ ? mode.trim().toLowerCase()
729
+ : '';
724
730
  this.mainTab = 'config';
725
- this.configMode = mode;
726
- if (mode === 'claude') {
731
+ this.configMode = CONFIG_MODE_SET.has(normalizedMode) ? normalizedMode : 'codex';
732
+ if (this.configMode === 'claude') {
727
733
  this.refreshClaudeModelContext();
728
734
  }
729
735
  },
@@ -799,6 +805,9 @@
799
805
  this.activeSessionMessages = [];
800
806
  this.activeSessionDetailError = '';
801
807
  this.activeSessionDetailClipped = false;
808
+ this.cancelSessionTimelineSync();
809
+ this.sessionTimelineActiveKey = '';
810
+ this.sessionMessageRefMap = Object.create(null);
802
811
  this.sessionStandaloneError = '';
803
812
  this.sessionStandaloneText = '';
804
813
  this.sessionStandaloneTitle = this.activeSession.title || '会话';
@@ -1167,8 +1176,7 @@
1167
1176
  },
1168
1177
 
1169
1178
  normalizeSessionPathValue(value) {
1170
- if (typeof value !== 'string') return '';
1171
- return value.trim();
1179
+ return normalizeSessionPathFilter(value);
1172
1180
  },
1173
1181
 
1174
1182
  mergeSessionPathOptions(baseList = [], incomingList = []) {
@@ -1277,13 +1285,32 @@
1277
1285
  const value = this.sessionResumeWithYolo ? '1' : '0';
1278
1286
  localStorage.setItem('codexmateSessionResumeYolo', value);
1279
1287
  },
1288
+ restoreSessionFilterCache() {
1289
+ const sourceCache = localStorage.getItem('codexmateSessionFilterSource');
1290
+ const pathCache = localStorage.getItem('codexmateSessionPathFilter');
1291
+ const cached = buildSessionFilterCacheState(sourceCache, pathCache);
1292
+ this.sessionFilterSource = cached.source;
1293
+ this.sessionPathFilter = cached.pathFilter;
1294
+ this.refreshSessionPathOptions(this.sessionFilterSource);
1295
+ },
1296
+ persistSessionFilterCache() {
1297
+ const cached = buildSessionFilterCacheState(this.sessionFilterSource, this.sessionPathFilter);
1298
+ localStorage.setItem('codexmateSessionFilterSource', cached.source);
1299
+ if (cached.pathFilter) {
1300
+ localStorage.setItem('codexmateSessionPathFilter', cached.pathFilter);
1301
+ } else {
1302
+ localStorage.removeItem('codexmateSessionPathFilter');
1303
+ }
1304
+ },
1280
1305
 
1281
1306
  async onSessionSourceChange() {
1282
1307
  this.refreshSessionPathOptions(this.sessionFilterSource);
1308
+ this.persistSessionFilterCache();
1283
1309
  await this.loadSessions();
1284
1310
  },
1285
1311
 
1286
1312
  async onSessionPathFilterChange() {
1313
+ this.persistSessionFilterCache();
1287
1314
  await this.loadSessions();
1288
1315
  },
1289
1316
 
@@ -1297,8 +1324,156 @@
1297
1324
  this.sessionQuery = '';
1298
1325
  this.sessionRoleFilter = 'all';
1299
1326
  this.sessionTimePreset = 'all';
1327
+ this.persistSessionFilterCache();
1300
1328
  await this.onSessionSourceChange();
1301
1329
  },
1330
+ setSessionPreviewContainerRef(el) {
1331
+ this.sessionPreviewContainerEl = el || null;
1332
+ this.updateSessionTimelineOffset();
1333
+ },
1334
+ disconnectSessionPreviewHeaderResizeObserver() {
1335
+ if (!this.sessionPreviewHeaderResizeObserver) return;
1336
+ this.sessionPreviewHeaderResizeObserver.disconnect();
1337
+ this.sessionPreviewHeaderResizeObserver = null;
1338
+ },
1339
+ observeSessionPreviewHeaderResize() {
1340
+ this.disconnectSessionPreviewHeaderResizeObserver();
1341
+ if (!this.sessionPreviewHeaderEl || typeof ResizeObserver !== 'function') return;
1342
+ this.sessionPreviewHeaderResizeObserver = new ResizeObserver(() => {
1343
+ this.updateSessionTimelineOffset();
1344
+ });
1345
+ this.sessionPreviewHeaderResizeObserver.observe(this.sessionPreviewHeaderEl);
1346
+ },
1347
+ setSessionPreviewHeaderRef(el) {
1348
+ this.disconnectSessionPreviewHeaderResizeObserver();
1349
+ this.sessionPreviewHeaderEl = el || null;
1350
+ this.observeSessionPreviewHeaderResize();
1351
+ this.updateSessionTimelineOffset();
1352
+ },
1353
+ setSessionPreviewScrollRef(el) {
1354
+ this.sessionPreviewScrollEl = el || null;
1355
+ if (this.sessionPreviewScrollEl) {
1356
+ this.scheduleSessionTimelineSync();
1357
+ } else {
1358
+ this.cancelSessionTimelineSync();
1359
+ }
1360
+ this.updateSessionTimelineOffset();
1361
+ },
1362
+ updateSessionTimelineOffset() {
1363
+ const container = this.sessionPreviewContainerEl || this.$refs.sessionPreviewContainer;
1364
+ if (!container || !container.style) return;
1365
+ const header = this.sessionPreviewHeaderEl
1366
+ || (this.sessionPreviewScrollEl ? this.sessionPreviewScrollEl.querySelector('.session-preview-header') : null)
1367
+ || container.querySelector('.session-preview-header');
1368
+ const headerHeight = header ? Math.ceil(header.getBoundingClientRect().height) : 0;
1369
+ const offset = headerHeight > 0 ? (headerHeight + 12) : 72;
1370
+ container.style.setProperty('--session-preview-header-offset', `${offset}px`);
1371
+ },
1372
+ bindSessionMessageRef(messageKey, el) {
1373
+ if (!messageKey) return;
1374
+ if (el) {
1375
+ this.sessionMessageRefMap[messageKey] = el;
1376
+ } else {
1377
+ delete this.sessionMessageRefMap[messageKey];
1378
+ }
1379
+ },
1380
+ cancelSessionTimelineSync() {
1381
+ if (!this.sessionTimelineRafId) return;
1382
+ if (typeof cancelAnimationFrame === 'function') {
1383
+ cancelAnimationFrame(this.sessionTimelineRafId);
1384
+ }
1385
+ this.sessionTimelineRafId = 0;
1386
+ },
1387
+ scheduleSessionTimelineSync() {
1388
+ if (this.sessionTimelineRafId) return;
1389
+ if (typeof requestAnimationFrame === 'function') {
1390
+ this.sessionTimelineRafId = requestAnimationFrame(() => {
1391
+ this.sessionTimelineRafId = 0;
1392
+ this.syncSessionTimelineActiveFromScroll();
1393
+ });
1394
+ return;
1395
+ }
1396
+ this.syncSessionTimelineActiveFromScroll();
1397
+ },
1398
+ onSessionPreviewScroll() {
1399
+ this.scheduleSessionTimelineSync();
1400
+ },
1401
+ onWindowResize() {
1402
+ this.updateSessionTimelineOffset();
1403
+ this.scheduleSessionTimelineSync();
1404
+ },
1405
+ syncSessionTimelineActiveFromScroll() {
1406
+ const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
1407
+ if (!nodes.length) {
1408
+ this.sessionTimelineActiveKey = '';
1409
+ return;
1410
+ }
1411
+ const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
1412
+ if (!scrollEl) {
1413
+ this.sessionTimelineActiveKey = nodes[0].key;
1414
+ return;
1415
+ }
1416
+ const scrollRect = scrollEl.getBoundingClientRect();
1417
+ const headerEl = scrollEl.querySelector('.session-preview-header');
1418
+ const headerHeight = headerEl ? headerEl.getBoundingClientRect().height : 0;
1419
+ const anchorLine = scrollRect.top + headerHeight + 8;
1420
+ let activeKey = nodes[0].key;
1421
+ for (const node of nodes) {
1422
+ const messageEl = this.sessionMessageRefMap[node.key];
1423
+ if (!messageEl) continue;
1424
+ const messageRect = messageEl.getBoundingClientRect();
1425
+ if (messageRect.top <= anchorLine) {
1426
+ activeKey = node.key;
1427
+ continue;
1428
+ }
1429
+ break;
1430
+ }
1431
+ this.sessionTimelineActiveKey = activeKey;
1432
+ },
1433
+ jumpToSessionTimelineNode(messageKey) {
1434
+ if (!messageKey) return;
1435
+ const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
1436
+ if (!scrollEl) return;
1437
+ const messageEl = this.sessionMessageRefMap[messageKey];
1438
+ if (!messageEl) return;
1439
+ const headerEl = scrollEl.querySelector('.session-preview-header');
1440
+ const stickyOffset = headerEl ? (headerEl.offsetHeight + 8) : 8;
1441
+ const scrollRect = scrollEl.getBoundingClientRect();
1442
+ const messageRect = messageEl.getBoundingClientRect();
1443
+ const targetScrollTop = scrollEl.scrollTop + (messageRect.top - scrollRect.top) - stickyOffset;
1444
+ this.sessionTimelineActiveKey = messageKey;
1445
+ if (typeof scrollEl.scrollTo === 'function') {
1446
+ scrollEl.scrollTo({
1447
+ top: Math.max(0, targetScrollTop),
1448
+ behavior: 'smooth'
1449
+ });
1450
+ } else {
1451
+ messageEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
1452
+ }
1453
+ },
1454
+
1455
+ normalizeSessionMessage(message) {
1456
+ const fallback = {
1457
+ role: 'assistant',
1458
+ normalizedRole: 'assistant',
1459
+ roleLabel: 'Assistant',
1460
+ text: typeof message === 'string' ? message : '',
1461
+ timestamp: ''
1462
+ };
1463
+ const safeMessage = message && typeof message === 'object' ? message : fallback;
1464
+ const normalizedRole = normalizeSessionMessageRole(
1465
+ safeMessage.normalizedRole || safeMessage.role
1466
+ );
1467
+ const roleLabel = normalizedRole === 'user'
1468
+ ? 'User'
1469
+ : (normalizedRole === 'system' ? 'System' : 'Assistant');
1470
+ return {
1471
+ ...safeMessage,
1472
+ role: normalizedRole,
1473
+ normalizedRole,
1474
+ roleLabel
1475
+ };
1476
+ },
1302
1477
 
1303
1478
  getRecordKey(message) {
1304
1479
  if (!message || !Number.isInteger(message.recordLineIndex) || message.recordLineIndex < 0) {
@@ -1347,6 +1522,9 @@
1347
1522
  this.activeSession = null;
1348
1523
  this.activeSessionMessages = [];
1349
1524
  this.activeSessionDetailClipped = false;
1525
+ this.cancelSessionTimelineSync();
1526
+ this.sessionTimelineActiveKey = '';
1527
+ this.sessionMessageRefMap = Object.create(null);
1350
1528
  } else {
1351
1529
  this.sessionsList = Array.isArray(res.sessions) ? res.sessions : [];
1352
1530
  this.syncSessionPathOptionsForSource(
@@ -1358,10 +1536,19 @@
1358
1536
  this.activeSession = null;
1359
1537
  this.activeSessionMessages = [];
1360
1538
  this.activeSessionDetailClipped = false;
1539
+ this.cancelSessionTimelineSync();
1540
+ this.sessionTimelineActiveKey = '';
1541
+ this.sessionMessageRefMap = Object.create(null);
1361
1542
  } else {
1362
1543
  const oldKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
1363
1544
  const matched = this.sessionsList.find(item => this.getSessionExportKey(item) === oldKey);
1364
1545
  this.activeSession = matched || this.sessionsList[0];
1546
+ this.activeSessionMessages = [];
1547
+ this.activeSessionDetailError = '';
1548
+ this.activeSessionDetailClipped = false;
1549
+ this.cancelSessionTimelineSync();
1550
+ this.sessionTimelineActiveKey = '';
1551
+ this.sessionMessageRefMap = Object.create(null);
1365
1552
  await this.loadActiveSessionDetail();
1366
1553
  }
1367
1554
  void this.loadSessionPathOptions({ source: this.sessionFilterSource });
@@ -1371,6 +1558,9 @@
1371
1558
  this.activeSession = null;
1372
1559
  this.activeSessionMessages = [];
1373
1560
  this.activeSessionDetailClipped = false;
1561
+ this.cancelSessionTimelineSync();
1562
+ this.sessionTimelineActiveKey = '';
1563
+ this.sessionMessageRefMap = Object.create(null);
1374
1564
  this.showMessage('加载会话失败', 'error');
1375
1565
  } finally {
1376
1566
  this.sessionsLoading = false;
@@ -1384,6 +1574,9 @@
1384
1574
  this.activeSessionMessages = [];
1385
1575
  this.activeSessionDetailError = '';
1386
1576
  this.activeSessionDetailClipped = false;
1577
+ this.cancelSessionTimelineSync();
1578
+ this.sessionTimelineActiveKey = '';
1579
+ this.sessionMessageRefMap = Object.create(null);
1387
1580
  await this.loadActiveSessionDetail();
1388
1581
  },
1389
1582
 
@@ -1437,6 +1630,9 @@
1437
1630
  this.activeSessionMessages = [];
1438
1631
  this.activeSessionDetailError = '';
1439
1632
  this.activeSessionDetailClipped = false;
1633
+ this.cancelSessionTimelineSync();
1634
+ this.sessionTimelineActiveKey = '';
1635
+ this.sessionMessageRefMap = Object.create(null);
1440
1636
  return;
1441
1637
  }
1442
1638
 
@@ -1459,10 +1655,14 @@
1459
1655
  this.activeSessionMessages = [];
1460
1656
  this.activeSessionDetailClipped = false;
1461
1657
  this.activeSessionDetailError = res.error;
1658
+ this.cancelSessionTimelineSync();
1659
+ this.sessionTimelineActiveKey = '';
1660
+ this.sessionMessageRefMap = Object.create(null);
1462
1661
  return;
1463
1662
  }
1464
1663
 
1465
- this.activeSessionMessages = Array.isArray(res.messages) ? res.messages : [];
1664
+ const rawMessages = Array.isArray(res.messages) ? res.messages : [];
1665
+ this.activeSessionMessages = rawMessages.map((message) => this.normalizeSessionMessage(message));
1466
1666
  this.activeSessionDetailClipped = !!res.clipped;
1467
1667
  if (this.activeSession) {
1468
1668
  if (res.sourceLabel) {
@@ -1487,6 +1687,10 @@
1487
1687
  if (Number.isFinite(res.totalMessages)) {
1488
1688
  this.syncActiveSessionMessageCount(res.totalMessages);
1489
1689
  }
1690
+ this.$nextTick(() => {
1691
+ this.updateSessionTimelineOffset();
1692
+ this.scheduleSessionTimelineSync();
1693
+ });
1490
1694
  } catch (e) {
1491
1695
  if (requestSeq !== this.sessionDetailRequestSeq) {
1492
1696
  return;
@@ -1494,6 +1698,9 @@
1494
1698
  this.activeSessionMessages = [];
1495
1699
  this.activeSessionDetailClipped = false;
1496
1700
  this.activeSessionDetailError = '加载会话内容失败: ' + e.message;
1701
+ this.cancelSessionTimelineSync();
1702
+ this.sessionTimelineActiveKey = '';
1703
+ this.sessionMessageRefMap = Object.create(null);
1497
1704
  } finally {
1498
1705
  if (requestSeq === this.sessionDetailRequestSeq) {
1499
1706
  this.sessionDetailLoading = false;
@@ -1550,7 +1757,9 @@
1550
1757
  if (this.modelsSource === 'remote' && this.models.length > 0 && !this.models.includes(this.currentModel)) {
1551
1758
  this.currentModel = this.models[0];
1552
1759
  }
1553
- await this.applyCodexConfigDirect({ silent: true });
1760
+ if (getProviderConfigModeMeta(this.configMode)) {
1761
+ await this.applyCodexConfigDirect({ silent: true });
1762
+ }
1554
1763
  },
1555
1764
 
1556
1765
  async onModelChange() {
@@ -1754,6 +1963,8 @@
1754
1963
  }
1755
1964
  },
1756
1965
 
1966
+ ...createSkillsMethods({ api }),
1967
+
1757
1968
  async openOpenclawAgentsEditor() {
1758
1969
  this.setAgentsModalContext('openclaw');
1759
1970
  this.agentsLoading = true;
@@ -3860,4 +4071,3 @@
3860
4071
  app.mount('#app');
3861
4072
  });
3862
4073
 
3863
-