codexmate 0.0.3 → 0.0.4

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.html CHANGED
@@ -1666,20 +1666,23 @@
1666
1666
  Codex<br>
1667
1667
  <span class="accent">Mate.</span>
1668
1668
  </h1>
1669
- <p class="subtitle">本地配置中枢,统一管理 Codex / Claude Code / 会话。</p>
1669
+ <p class="subtitle">本地配置中枢,统一管理 Codex / Claude Code / OpenClaw / 会话。</p>
1670
1670
 
1671
1671
  <!-- 模式切换器 -->
1672
- <div class="segmented-control">
1673
- <button :class="['segment', { active: configMode === 'codex' }]" @click="switchConfigMode('codex')">
1674
- Codex 配置
1675
- </button>
1676
- <button :class="['segment', { active: configMode === 'claude' }]" @click="switchConfigMode('claude')">
1677
- Claude Code 配置
1678
- </button>
1679
- <button :class="['segment', { active: configMode === 'sessions' }]" @click="switchConfigMode('sessions')">
1680
- 会话浏览
1681
- </button>
1682
- </div>
1672
+ <div class="segmented-control">
1673
+ <button :class="['segment', { active: configMode === 'codex' }]" @click="switchConfigMode('codex')">
1674
+ Codex 配置
1675
+ </button>
1676
+ <button :class="['segment', { active: configMode === 'claude' }]" @click="switchConfigMode('claude')">
1677
+ Claude Code 配置
1678
+ </button>
1679
+ <button :class="['segment', { active: configMode === 'openclaw' }]" @click="switchConfigMode('openclaw')">
1680
+ OpenClaw 配置
1681
+ </button>
1682
+ <button :class="['segment', { active: configMode === 'sessions' }]" @click="switchConfigMode('sessions')">
1683
+ 会话浏览
1684
+ </button>
1685
+ </div>
1683
1686
 
1684
1687
  <!-- 内容包裹器 - 稳定布局 -->
1685
1688
  <div class="content-wrapper">
@@ -1767,10 +1770,10 @@
1767
1770
  </div>
1768
1771
  </div>
1769
1772
 
1770
- <!-- Claude Code 配置模式 -->
1771
- <div v-show="configMode === 'claude'" class="mode-content">
1772
- <!-- 添加提供商按钮 -->
1773
- <button class="btn-add" @click="showClaudeConfigModal = true" v-if="!loading && !initError">
1773
+ <!-- Claude Code 配置模式 -->
1774
+ <div v-show="configMode === 'claude'" class="mode-content">
1775
+ <!-- 添加提供商按钮 -->
1776
+ <button class="btn-add" @click="showClaudeConfigModal = true" v-if="!loading && !initError">
1774
1777
  <svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
1775
1778
  <path d="M10 4v12M4 10h12"/>
1776
1779
  </svg>
@@ -1811,11 +1814,69 @@
1811
1814
  </div>
1812
1815
  </div>
1813
1816
  </div>
1814
- </div>
1815
- </div>
1816
-
1817
- <!-- 会话浏览模式 -->
1818
- <div v-show="configMode === 'sessions'" class="mode-content">
1817
+ </div>
1818
+ </div>
1819
+
1820
+ <!-- OpenClaw 配置模式 -->
1821
+ <div v-show="configMode === 'openclaw'" class="mode-content">
1822
+ <button class="btn-add" @click="openOpenclawAddModal" v-if="!loading && !initError">
1823
+ <svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
1824
+ <path d="M10 4v12M4 10h12"/>
1825
+ </svg>
1826
+ 添加 OpenClaw 配置
1827
+ </button>
1828
+ <div class="config-template-hint">
1829
+ 默认应用到 <code>~/.openclaw/openclaw.json</code>;支持 JSON5(注释/尾逗号)。
1830
+ </div>
1831
+
1832
+ <div class="selector-section">
1833
+ <div class="selector-header">
1834
+ <span class="selector-title">AGENTS.md</span>
1835
+ </div>
1836
+ <div class="config-template-hint">
1837
+ 管理 OpenClaw Workspace 指令文件,默认读写 <code>~/.openclaw/workspace/AGENTS.md</code>。
1838
+ </div>
1839
+ <button class="btn-tool" @click="openOpenclawAgentsEditor" :disabled="loading || !!initError || agentsLoading">
1840
+ {{ agentsLoading ? '加载中...' : '打开 AGENTS.md 编辑器' }}
1841
+ </button>
1842
+ </div>
1843
+
1844
+ <div class="card-list">
1845
+ <div v-for="(config, name) in openclawConfigs" :key="name"
1846
+ :class="['card', { active: currentOpenclawConfig === name }]"
1847
+ @click="applyOpenclawConfig(name)">
1848
+ <div class="card-leading">
1849
+ <div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
1850
+ <div class="card-content">
1851
+ <div class="card-title">{{ name }}</div>
1852
+ <div class="card-subtitle">{{ openclawSubtitle(config) }}</div>
1853
+ </div>
1854
+ </div>
1855
+ <div class="card-trailing">
1856
+ <span :class="['pill', openclawHasContent(config) ? 'configured' : 'empty']">
1857
+ {{ openclawHasContent(config) ? '已配置' : '未配置' }}
1858
+ </span>
1859
+ <div class="card-actions" @click.stop>
1860
+ <button class="card-action-btn" @click="openOpenclawEditModal(name)" title="编辑">
1861
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1862
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
1863
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
1864
+ </svg>
1865
+ </button>
1866
+ <button class="card-action-btn delete" @click="deleteOpenclawConfig(name)" title="删除">
1867
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1868
+ <path d="M3 6h18"/>
1869
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
1870
+ </svg>
1871
+ </button>
1872
+ </div>
1873
+ </div>
1874
+ </div>
1875
+ </div>
1876
+ </div>
1877
+
1878
+ <!-- 会话浏览模式 -->
1879
+ <div v-show="configMode === 'sessions'" class="mode-content">
1819
1880
  <div class="selector-section">
1820
1881
  <div class="selector-header">
1821
1882
  <span class="selector-title">会话来源</span>
@@ -2124,9 +2185,9 @@
2124
2185
  </div>
2125
2186
 
2126
2187
  <!-- 编辑Claude配置模态框 -->
2127
- <div v-if="showEditConfigModal" class="modal-overlay" @click.self="closeEditConfigModal">
2128
- <div class="modal">
2129
- <div class="modal-title">编辑 Claude Code 配置</div>
2188
+ <div v-if="showEditConfigModal" class="modal-overlay" @click.self="closeEditConfigModal">
2189
+ <div class="modal">
2190
+ <div class="modal-title">编辑 Claude Code 配置</div>
2130
2191
 
2131
2192
  <div class="form-group">
2132
2193
  <label class="form-label">配置名称</label>
@@ -2150,13 +2211,61 @@
2150
2211
  <button class="btn btn-confirm" @click="updateConfig">保存</button>
2151
2212
  <button class="btn btn-confirm secondary" @click="saveAndApplyConfig">保存并应用到 Claude 配置</button>
2152
2213
  <button class="btn btn-confirm secondary" @click="saveAndApplyEnvCompat">兼容模式应用到环境变量</button>
2153
- </div>
2154
- </div>
2155
- </div>
2156
-
2157
- <div v-if="showConfigTemplateModal" class="modal-overlay" @click.self="closeConfigTemplateModal">
2158
- <div class="modal modal-wide">
2159
- <div class="modal-title">Config 模板编辑器(手动确认应用)</div>
2214
+ </div>
2215
+ </div>
2216
+ </div>
2217
+
2218
+ <div v-if="showOpenclawConfigModal" class="modal-overlay" @click.self="closeOpenclawConfigModal">
2219
+ <div class="modal modal-wide">
2220
+ <div class="modal-title">{{ openclawEditorTitle }}</div>
2221
+
2222
+ <div class="form-group">
2223
+ <label class="form-label">配置名称</label>
2224
+ <input v-model="openclawEditing.name" class="form-input" :readonly="openclawEditing.lockName" placeholder="例如: 默认配置">
2225
+ </div>
2226
+
2227
+ <div class="form-group">
2228
+ <label class="form-label">目标文件</label>
2229
+ <div class="form-hint">
2230
+ {{ openclawConfigPath || '未加载' }}
2231
+ <span v-if="openclawConfigPath">
2232
+ ({{ openclawConfigExists ? '已存在' : '不存在,将在应用时创建' }})
2233
+ </span>
2234
+ </div>
2235
+ <div class="btn-group" style="justify-content:flex-start;">
2236
+ <button class="btn btn-confirm secondary" @click="loadOpenclawConfigFromFile" :disabled="openclawFileLoading">
2237
+ {{ openclawFileLoading ? '加载中...' : '加载当前配置' }}
2238
+ </button>
2239
+ </div>
2240
+ </div>
2241
+
2242
+ <div class="form-group">
2243
+ <label class="form-label">OpenClaw 配置(JSON5)</label>
2244
+ <textarea
2245
+ v-model="openclawEditing.content"
2246
+ class="form-input template-editor"
2247
+ spellcheck="false"
2248
+ placeholder="在这里编辑 OpenClaw 配置(JSON5)"></textarea>
2249
+ <div class="template-editor-warning">
2250
+ 保存仅写入本地配置库;点击“保存并应用”后会写入 openclaw.json。
2251
+ </div>
2252
+ </div>
2253
+
2254
+ <div class="btn-group">
2255
+ <button class="btn btn-cancel" @click="closeOpenclawConfigModal">取消</button>
2256
+ <button class="btn btn-confirm" @click="saveOpenclawConfig" :disabled="openclawSaving">
2257
+ {{ openclawSaving ? '保存中...' : '保存' }}
2258
+ </button>
2259
+ <button class="btn btn-confirm secondary" @click="saveAndApplyOpenclawConfig" :disabled="openclawApplying">
2260
+ {{ openclawApplying ? '应用中...' : '保存并应用' }}
2261
+ </button>
2262
+ </div>
2263
+ </div>
2264
+ </div>
2265
+
2266
+ <div v-if="showConfigTemplateModal" class="modal-overlay" @click.self="closeConfigTemplateModal">
2267
+ <div class="modal modal-wide">
2268
+ <div class="modal-title">Config 模板编辑器(手动确认应用)</div>
2160
2269
 
2161
2270
  <div class="form-group">
2162
2271
  <label class="form-label">config.toml 模板</label>
@@ -2179,9 +2288,9 @@
2179
2288
  </div>
2180
2289
  </div>
2181
2290
 
2182
- <div v-if="showAgentsModal" class="modal-overlay" @click.self="closeAgentsModal">
2183
- <div class="modal modal-wide">
2184
- <div class="modal-title">AGENTS.md 编辑器</div>
2291
+ <div v-if="showAgentsModal" class="modal-overlay" @click.self="closeAgentsModal">
2292
+ <div class="modal modal-wide">
2293
+ <div class="modal-title">{{ agentsModalTitle }}</div>
2185
2294
 
2186
2295
  <div class="form-group">
2187
2296
  <label class="form-label">目标文件</label>
@@ -2195,16 +2304,16 @@
2195
2304
 
2196
2305
  <div class="form-group">
2197
2306
  <label class="form-label">AGENTS.md 内容</label>
2198
- <textarea
2199
- v-model="agentsContent"
2200
- class="form-input template-editor"
2201
- spellcheck="false"
2202
- :readonly="agentsLoading"
2203
- placeholder="在这里编辑 AGENTS.md 内容"></textarea>
2204
- <div class="template-editor-warning">
2205
- 保存后会写入目标 AGENTS.md(与 config.toml 同级)。
2206
- </div>
2207
- </div>
2307
+ <textarea
2308
+ v-model="agentsContent"
2309
+ class="form-input template-editor"
2310
+ spellcheck="false"
2311
+ :readonly="agentsLoading"
2312
+ placeholder="在这里编辑 AGENTS.md 内容"></textarea>
2313
+ <div class="template-editor-warning">
2314
+ {{ agentsModalHint }}
2315
+ </div>
2316
+ </div>
2208
2317
 
2209
2318
  <div class="btn-group">
2210
2319
  <button class="btn btn-cancel" @click="closeAgentsModal">取消</button>
@@ -2221,11 +2330,22 @@
2221
2330
  <div v-if="message" :class="['toast', messageType]">{{ message }}</div>
2222
2331
  </div>
2223
2332
 
2224
- <script>
2225
- const { createApp } = Vue;
2226
- const API_BASE = 'http://localhost:3737';
2227
-
2228
- async function api(action, params = {}) {
2333
+ <script>
2334
+ const { createApp } = Vue;
2335
+ const API_BASE = 'http://localhost:3737';
2336
+ const DEFAULT_OPENCLAW_TEMPLATE = `{
2337
+ // OpenClaw config (JSON5)
2338
+ agent: {
2339
+ model: "gpt-4.1"
2340
+ },
2341
+ agents: {
2342
+ defaults: {
2343
+ workspace: "~/.openclaw/workspace"
2344
+ }
2345
+ }
2346
+ }`;
2347
+
2348
+ async function api(action, params = {}) {
2229
2349
  const res = await fetch(`${API_BASE}/api`, {
2230
2350
  method: 'POST',
2231
2351
  headers: { 'Content-Type': 'application/json' },
@@ -2250,19 +2370,23 @@
2250
2370
  showEditModal: false,
2251
2371
  showModelModal: false,
2252
2372
  showModelListModal: false,
2253
- showClaudeConfigModal: false,
2254
- showEditConfigModal: false,
2255
- showConfigTemplateModal: false,
2256
- showAgentsModal: false,
2257
- configTemplateContent: '',
2258
- configTemplateApplying: false,
2259
- agentsContent: '',
2260
- agentsPath: '',
2261
- agentsExists: false,
2262
- agentsLineEnding: '\n',
2263
- agentsLoading: false,
2264
- agentsSaving: false,
2265
- sessionsList: [],
2373
+ showClaudeConfigModal: false,
2374
+ showEditConfigModal: false,
2375
+ showOpenclawConfigModal: false,
2376
+ showConfigTemplateModal: false,
2377
+ showAgentsModal: false,
2378
+ configTemplateContent: '',
2379
+ configTemplateApplying: false,
2380
+ agentsContent: '',
2381
+ agentsPath: '',
2382
+ agentsExists: false,
2383
+ agentsLineEnding: '\n',
2384
+ agentsLoading: false,
2385
+ agentsSaving: false,
2386
+ agentsContext: 'codex',
2387
+ agentsModalTitle: 'AGENTS.md 编辑器',
2388
+ agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
2389
+ sessionsList: [],
2266
2390
  sessionsLoading: false,
2267
2391
  sessionFilterSource: 'all',
2268
2392
  sessionPathFilter: '',
@@ -2293,10 +2417,10 @@
2293
2417
  speedLoading: {},
2294
2418
  newProvider: { name: '', url: '', key: '' },
2295
2419
  editingProvider: { name: '', url: '', key: '' },
2296
- newModelName: '',
2297
- currentClaudeConfig: '',
2298
- editingConfig: { name: '', apiKey: '', baseUrl: '', model: '' },
2299
- claudeConfigs: {
2420
+ newModelName: '',
2421
+ currentClaudeConfig: '',
2422
+ editingConfig: { name: '', apiKey: '', baseUrl: '', model: '' },
2423
+ claudeConfigs: {
2300
2424
  '智谱GLM': {
2301
2425
  apiKey: '',
2302
2426
  baseUrl: 'https://open.bigmodel.cn/api/anthropic',
@@ -2304,19 +2428,33 @@
2304
2428
  hasKey: false
2305
2429
  }
2306
2430
  },
2307
- newClaudeConfig: {
2308
- name: '',
2309
- apiKey: '',
2310
- baseUrl: 'https://open.bigmodel.cn/api/anthropic',
2311
- model: 'glm-4.7'
2312
- }
2313
- }
2314
- },
2315
- mounted() {
2316
- const savedConfigs = localStorage.getItem('claudeConfigs');
2317
- if (savedConfigs) {
2318
- try {
2319
- this.claudeConfigs = JSON.parse(savedConfigs);
2431
+ newClaudeConfig: {
2432
+ name: '',
2433
+ apiKey: '',
2434
+ baseUrl: 'https://open.bigmodel.cn/api/anthropic',
2435
+ model: 'glm-4.7'
2436
+ },
2437
+ currentOpenclawConfig: '',
2438
+ openclawConfigs: {
2439
+ '默认配置': {
2440
+ content: DEFAULT_OPENCLAW_TEMPLATE
2441
+ }
2442
+ },
2443
+ openclawEditing: { name: '', content: '', lockName: false },
2444
+ openclawEditorTitle: '添加 OpenClaw 配置',
2445
+ openclawConfigPath: '',
2446
+ openclawConfigExists: false,
2447
+ openclawLineEnding: '\n',
2448
+ openclawFileLoading: false,
2449
+ openclawSaving: false,
2450
+ openclawApplying: false
2451
+ }
2452
+ },
2453
+ mounted() {
2454
+ const savedConfigs = localStorage.getItem('claudeConfigs');
2455
+ if (savedConfigs) {
2456
+ try {
2457
+ this.claudeConfigs = JSON.parse(savedConfigs);
2320
2458
  for (const [name, config] of Object.entries(this.claudeConfigs)) {
2321
2459
  if (config.apiKey && config.apiKey.includes('****')) {
2322
2460
  config.apiKey = '';
@@ -2331,10 +2469,27 @@
2331
2469
  }
2332
2470
  } catch (e) {
2333
2471
  console.error('加载 Claude 配置失败:', e);
2334
- }
2335
- }
2336
- this.loadAll();
2337
- },
2472
+ }
2473
+ }
2474
+ const savedOpenclawConfigs = localStorage.getItem('openclawConfigs');
2475
+ if (savedOpenclawConfigs) {
2476
+ try {
2477
+ this.openclawConfigs = JSON.parse(savedOpenclawConfigs);
2478
+ const configNames = Object.keys(this.openclawConfigs);
2479
+ if (configNames.length > 0) {
2480
+ this.currentOpenclawConfig = configNames[0];
2481
+ }
2482
+ } catch (e) {
2483
+ console.error('加载 OpenClaw 配置失败:', e);
2484
+ }
2485
+ } else {
2486
+ const configNames = Object.keys(this.openclawConfigs);
2487
+ if (configNames.length > 0) {
2488
+ this.currentOpenclawConfig = configNames[0];
2489
+ }
2490
+ }
2491
+ this.loadAll();
2492
+ },
2338
2493
  methods: {
2339
2494
  async loadAll() {
2340
2495
  this.loading = true;
@@ -2614,7 +2769,8 @@
2614
2769
  pathFilter: this.sessionPathFilter,
2615
2770
  query,
2616
2771
  queryMode: 'and',
2617
- queryScope: 'summary',
2772
+ queryScope: 'content',
2773
+ contentScanLimit: 2,
2618
2774
  roleFilter: this.sessionRoleFilter,
2619
2775
  timeRangePreset: this.sessionTimePreset,
2620
2776
  limit: 200,
@@ -2828,44 +2984,84 @@
2828
2984
  }
2829
2985
  },
2830
2986
 
2831
- async openAgentsEditor() {
2832
- this.agentsLoading = true;
2833
- try {
2834
- const res = await api('get-agents-file');
2835
- if (res.error) {
2836
- this.showMessage(res.error, 'error');
2987
+ async openAgentsEditor() {
2988
+ this.setAgentsModalContext('codex');
2989
+ this.agentsLoading = true;
2990
+ try {
2991
+ const res = await api('get-agents-file');
2992
+ if (res.error) {
2993
+ this.showMessage(res.error, 'error');
2837
2994
  return;
2838
2995
  }
2839
2996
  this.agentsContent = res.content || '';
2840
2997
  this.agentsPath = res.path || '';
2841
2998
  this.agentsExists = !!res.exists;
2842
- this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
2843
- this.showAgentsModal = true;
2844
- } catch (e) {
2845
- this.showMessage('加载 AGENTS.md 失败: ' + e.message, 'error');
2846
- } finally {
2847
- this.agentsLoading = false;
2848
- }
2849
- },
2850
-
2851
- closeAgentsModal() {
2852
- this.showAgentsModal = false;
2853
- this.agentsContent = '';
2854
- this.agentsPath = '';
2855
- this.agentsExists = false;
2856
- this.agentsLineEnding = '\n';
2857
- this.agentsSaving = false;
2858
- },
2859
-
2860
- async applyAgentsContent() {
2861
- this.agentsSaving = true;
2862
- try {
2863
- const res = await api('apply-agents-file', {
2864
- content: this.agentsContent,
2865
- lineEnding: this.agentsLineEnding
2866
- });
2867
- if (res.error) {
2868
- this.showMessage(res.error, 'error');
2999
+ this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
3000
+ this.showAgentsModal = true;
3001
+ } catch (e) {
3002
+ this.showMessage('加载 AGENTS.md 失败: ' + e.message, 'error');
3003
+ } finally {
3004
+ this.agentsLoading = false;
3005
+ }
3006
+ },
3007
+
3008
+ async openOpenclawAgentsEditor() {
3009
+ this.setAgentsModalContext('openclaw');
3010
+ this.agentsLoading = true;
3011
+ try {
3012
+ const res = await api('get-openclaw-agents-file');
3013
+ if (res.error) {
3014
+ this.showMessage(res.error, 'error');
3015
+ return;
3016
+ }
3017
+ if (res.configError) {
3018
+ this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
3019
+ }
3020
+ this.agentsContent = res.content || '';
3021
+ this.agentsPath = res.path || '';
3022
+ this.agentsExists = !!res.exists;
3023
+ this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
3024
+ this.showAgentsModal = true;
3025
+ } catch (e) {
3026
+ this.showMessage('加载 OpenClaw AGENTS.md 失败: ' + e.message, 'error');
3027
+ } finally {
3028
+ this.agentsLoading = false;
3029
+ }
3030
+ },
3031
+
3032
+ setAgentsModalContext(context) {
3033
+ this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
3034
+ if (this.agentsContext === 'openclaw') {
3035
+ this.agentsModalTitle = 'OpenClaw AGENTS.md 编辑器';
3036
+ this.agentsModalHint = '保存后会写入 OpenClaw Workspace 下的 AGENTS.md。';
3037
+ } else {
3038
+ this.agentsModalTitle = 'AGENTS.md 编辑器';
3039
+ this.agentsModalHint = '保存后会写入目标 AGENTS.md(与 config.toml 同级)。';
3040
+ }
3041
+ },
3042
+
3043
+ closeAgentsModal() {
3044
+ this.showAgentsModal = false;
3045
+ this.agentsContent = '';
3046
+ this.agentsPath = '';
3047
+ this.agentsExists = false;
3048
+ this.agentsLineEnding = '\n';
3049
+ this.agentsSaving = false;
3050
+ this.setAgentsModalContext('codex');
3051
+ },
3052
+
3053
+ async applyAgentsContent() {
3054
+ this.agentsSaving = true;
3055
+ try {
3056
+ const action = this.agentsContext === 'openclaw'
3057
+ ? 'apply-openclaw-agents-file'
3058
+ : 'apply-agents-file';
3059
+ const res = await api(action, {
3060
+ content: this.agentsContent,
3061
+ lineEnding: this.agentsLineEnding
3062
+ });
3063
+ if (res.error) {
3064
+ this.showMessage(res.error, 'error');
2869
3065
  return;
2870
3066
  }
2871
3067
  this.showMessage('AGENTS.md 已保存', 'success');
@@ -3128,20 +3324,187 @@
3128
3324
  }
3129
3325
  },
3130
3326
 
3131
- closeClaudeConfigModal() {
3132
- this.showClaudeConfigModal = false;
3133
- this.newClaudeConfig = {
3134
- name: '',
3135
- apiKey: '',
3136
- baseUrl: 'https://open.bigmodel.cn/api/anthropic',
3137
- model: 'glm-4.7'
3138
- };
3139
- },
3140
-
3141
- formatLatency(result) {
3142
- if (!result) return '';
3143
- if (!result.ok) return result.status ? `ERR ${result.status}` : 'ERR';
3144
- const ms = typeof result.durationMs === 'number' ? result.durationMs : 0;
3327
+ closeClaudeConfigModal() {
3328
+ this.showClaudeConfigModal = false;
3329
+ this.newClaudeConfig = {
3330
+ name: '',
3331
+ apiKey: '',
3332
+ baseUrl: 'https://open.bigmodel.cn/api/anthropic',
3333
+ model: 'glm-4.7'
3334
+ };
3335
+ },
3336
+
3337
+ openclawHasContent(config) {
3338
+ return !!(config && typeof config.content === 'string' && config.content.trim());
3339
+ },
3340
+
3341
+ openclawSubtitle(config) {
3342
+ if (!this.openclawHasContent(config)) {
3343
+ return '未设置配置';
3344
+ }
3345
+ const length = config.content.trim().length;
3346
+ return `已保存 ${length} 字符`;
3347
+ },
3348
+
3349
+ saveOpenclawConfigs() {
3350
+ localStorage.setItem('openclawConfigs', JSON.stringify(this.openclawConfigs));
3351
+ },
3352
+
3353
+ openOpenclawAddModal() {
3354
+ this.openclawEditorTitle = '添加 OpenClaw 配置';
3355
+ this.openclawEditing = {
3356
+ name: '',
3357
+ content: DEFAULT_OPENCLAW_TEMPLATE,
3358
+ lockName: false
3359
+ };
3360
+ this.openclawConfigPath = '';
3361
+ this.openclawConfigExists = false;
3362
+ this.openclawLineEnding = '\n';
3363
+ this.showOpenclawConfigModal = true;
3364
+ },
3365
+
3366
+ openOpenclawEditModal(name) {
3367
+ const config = this.openclawConfigs[name];
3368
+ this.openclawEditorTitle = `编辑 OpenClaw 配置: ${name}`;
3369
+ this.openclawEditing = {
3370
+ name,
3371
+ content: (config && config.content) ? config.content : '',
3372
+ lockName: true
3373
+ };
3374
+ this.showOpenclawConfigModal = true;
3375
+ },
3376
+
3377
+ closeOpenclawConfigModal() {
3378
+ this.showOpenclawConfigModal = false;
3379
+ this.openclawEditing = { name: '', content: '', lockName: false };
3380
+ this.openclawSaving = false;
3381
+ this.openclawApplying = false;
3382
+ },
3383
+
3384
+ async loadOpenclawConfigFromFile() {
3385
+ this.openclawFileLoading = true;
3386
+ try {
3387
+ const res = await api('get-openclaw-config');
3388
+ if (res.error) {
3389
+ this.showMessage(res.error, 'error');
3390
+ return;
3391
+ }
3392
+ this.openclawConfigPath = res.path || '';
3393
+ this.openclawConfigExists = !!res.exists;
3394
+ this.openclawLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
3395
+ if (res.content && res.content.trim()) {
3396
+ this.openclawEditing.content = res.content;
3397
+ } else if (!this.openclawEditing.content) {
3398
+ this.openclawEditing.content = DEFAULT_OPENCLAW_TEMPLATE;
3399
+ }
3400
+ this.showMessage('已加载当前 OpenClaw 配置', 'success');
3401
+ } catch (e) {
3402
+ this.showMessage('加载 OpenClaw 配置失败: ' + e.message, 'error');
3403
+ } finally {
3404
+ this.openclawFileLoading = false;
3405
+ }
3406
+ },
3407
+
3408
+ persistOpenclawConfig({ closeModal = true } = {}) {
3409
+ if (!this.openclawEditing.name || !this.openclawEditing.name.trim()) {
3410
+ this.showMessage('请输入配置名称', 'error');
3411
+ return '';
3412
+ }
3413
+ const name = this.openclawEditing.name.trim();
3414
+ if (!this.openclawEditing.lockName && this.openclawConfigs[name]) {
3415
+ this.showMessage('配置名称已存在', 'error');
3416
+ return '';
3417
+ }
3418
+ if (!this.openclawEditing.content || !this.openclawEditing.content.trim()) {
3419
+ this.showMessage('配置内容不能为空', 'error');
3420
+ return '';
3421
+ }
3422
+
3423
+ this.openclawConfigs[name] = {
3424
+ content: this.openclawEditing.content
3425
+ };
3426
+ this.currentOpenclawConfig = name;
3427
+ this.saveOpenclawConfigs();
3428
+ if (closeModal) {
3429
+ this.closeOpenclawConfigModal();
3430
+ }
3431
+ return name;
3432
+ },
3433
+
3434
+ async saveOpenclawConfig() {
3435
+ this.openclawSaving = true;
3436
+ try {
3437
+ const name = this.persistOpenclawConfig();
3438
+ if (!name) return;
3439
+ this.showMessage('OpenClaw 配置已保存', 'success');
3440
+ } finally {
3441
+ this.openclawSaving = false;
3442
+ }
3443
+ },
3444
+
3445
+ async saveAndApplyOpenclawConfig() {
3446
+ this.openclawApplying = true;
3447
+ try {
3448
+ const name = this.persistOpenclawConfig({ closeModal: false });
3449
+ if (!name) return;
3450
+ const config = this.openclawConfigs[name];
3451
+ const res = await api('apply-openclaw-config', {
3452
+ content: config.content,
3453
+ lineEnding: this.openclawLineEnding
3454
+ });
3455
+ if (res.error || res.success === false) {
3456
+ this.showMessage(res.error || '应用 OpenClaw 配置失败', 'error');
3457
+ return;
3458
+ }
3459
+ this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
3460
+ this.openclawConfigExists = true;
3461
+ const targetTip = res.targetPath ? `(${res.targetPath})` : '';
3462
+ this.showMessage(`已保存并应用 OpenClaw 配置${targetTip}`, 'success');
3463
+ this.closeOpenclawConfigModal();
3464
+ } catch (e) {
3465
+ this.showMessage('应用 OpenClaw 配置失败: ' + e.message, 'error');
3466
+ } finally {
3467
+ this.openclawApplying = false;
3468
+ }
3469
+ },
3470
+
3471
+ deleteOpenclawConfig(name) {
3472
+ if (Object.keys(this.openclawConfigs).length <= 1) {
3473
+ return this.showMessage('至少保留一个配置', 'error');
3474
+ }
3475
+ if (!confirm(`确定删除配置 "${name}"?`)) return;
3476
+ delete this.openclawConfigs[name];
3477
+ if (this.currentOpenclawConfig === name) {
3478
+ this.currentOpenclawConfig = Object.keys(this.openclawConfigs)[0];
3479
+ }
3480
+ this.saveOpenclawConfigs();
3481
+ this.showMessage('OpenClaw 配置已删除', 'success');
3482
+ },
3483
+
3484
+ async applyOpenclawConfig(name) {
3485
+ this.currentOpenclawConfig = name;
3486
+ const config = this.openclawConfigs[name];
3487
+ if (!this.openclawHasContent(config)) {
3488
+ return this.showMessage('该配置为空,请先编辑', 'error');
3489
+ }
3490
+ const res = await api('apply-openclaw-config', {
3491
+ content: config.content,
3492
+ lineEnding: this.openclawLineEnding
3493
+ });
3494
+ if (res.error || res.success === false) {
3495
+ this.showMessage(res.error || '应用 OpenClaw 配置失败', 'error');
3496
+ } else {
3497
+ this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
3498
+ this.openclawConfigExists = true;
3499
+ const targetTip = res.targetPath ? `(${res.targetPath})` : '';
3500
+ this.showMessage(`已应用 OpenClaw 配置: ${name}${targetTip}`, 'success');
3501
+ }
3502
+ },
3503
+
3504
+ formatLatency(result) {
3505
+ if (!result) return '';
3506
+ if (!result.ok) return result.status ? `ERR ${result.status}` : 'ERR';
3507
+ const ms = typeof result.durationMs === 'number' ? result.durationMs : 0;
3145
3508
  return `${ms}ms`;
3146
3509
  },
3147
3510