codexmate 0.0.38 → 0.0.40

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 (43) hide show
  1. package/cli/builtin-proxy.js +626 -207
  2. package/cli/config-bootstrap.js +6 -1
  3. package/cli/openai-bridge.js +541 -210
  4. package/cli.js +189 -4
  5. package/package.json +1 -1
  6. package/plugins/prompt-templates/computed.mjs +61 -3
  7. package/plugins/prompt-templates/manifest.mjs +3 -0
  8. package/web-ui/app.js +14 -3
  9. package/web-ui/modules/app.computed.main-tabs.mjs +39 -30
  10. package/web-ui/modules/app.methods.claude-config.mjs +111 -9
  11. package/web-ui/modules/app.methods.index.mjs +2 -0
  12. package/web-ui/modules/app.methods.openclaw-editing.mjs +48 -0
  13. package/web-ui/modules/app.methods.openclaw-persist.mjs +13 -7
  14. package/web-ui/modules/app.methods.providers.mjs +36 -10
  15. package/web-ui/modules/app.methods.runtime.mjs +76 -1
  16. package/web-ui/modules/app.methods.startup-claude.mjs +7 -0
  17. package/web-ui/modules/app.methods.tool-config-permissions.mjs +87 -0
  18. package/web-ui/modules/config-mode.computed.mjs +3 -3
  19. package/web-ui/modules/i18n/locales/en.mjs +1140 -0
  20. package/web-ui/modules/i18n/locales/ja.mjs +1130 -0
  21. package/web-ui/modules/i18n/locales/vi.mjs +239 -0
  22. package/web-ui/modules/i18n/locales/zh.mjs +1143 -0
  23. package/web-ui/modules/i18n.dict.mjs +9 -3195
  24. package/web-ui/modules/i18n.mjs +65 -16
  25. package/web-ui/partials/index/layout-header.html +16 -46
  26. package/web-ui/partials/index/modal-openclaw-config.html +135 -71
  27. package/web-ui/partials/index/modal-webhook.html +8 -8
  28. package/web-ui/partials/index/modals-basic.html +56 -16
  29. package/web-ui/partials/index/panel-config-claude.html +51 -21
  30. package/web-ui/partials/index/panel-config-codex.html +34 -5
  31. package/web-ui/partials/index/panel-config-openclaw.html +70 -64
  32. package/web-ui/partials/index/panel-dashboard.html +62 -77
  33. package/web-ui/partials/index/panel-settings.html +28 -7
  34. package/web-ui/partials/index/panel-trash.html +14 -14
  35. package/web-ui/res/web-ui-render.precompiled.js +1783 -1386
  36. package/web-ui/styles/controls-forms.css +99 -0
  37. package/web-ui/styles/dashboard.css +46 -14
  38. package/web-ui/styles/layout-shell.css +45 -0
  39. package/web-ui/styles/navigation-panels.css +3 -3
  40. package/web-ui/styles/openclaw-structured.css +383 -33
  41. package/web-ui/styles/responsive.css +68 -0
  42. package/web-ui/styles/sessions-usage.css +105 -9
  43. package/web-ui/styles/settings-panel.css +4 -0
package/cli.js CHANGED
@@ -213,6 +213,7 @@ const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
213
213
  const CODEBUDDY_DIR = path.join(os.homedir(), '.codebuddy');
214
214
  const CODEBUDDY_PROJECTS_DIR = path.join(CODEBUDDY_DIR, 'projects');
215
215
  const CODEXMATE_DIR = path.join(os.homedir(), '.codexmate');
216
+ const CODEXMATE_PREFERENCES_FILE = path.join(CODEXMATE_DIR, 'preferences.json');
216
217
  const CODEXMATE_SESSIONS_DIR = path.join(CODEXMATE_DIR, 'sessions');
217
218
  const CODEXMATE_DERIVED_SESSIONS_DIR = path.join(CODEXMATE_SESSIONS_DIR, 'derived');
218
219
  const CODEXMATE_DERIVED_CODEX_DIR = path.join(CODEXMATE_DERIVED_SESSIONS_DIR, 'codex');
@@ -717,6 +718,7 @@ function readConfig() {
717
718
  }
718
719
 
719
720
  function writeConfig(content) {
721
+ assertToolConfigWriteAllowed('codex');
720
722
  try {
721
723
  fs.writeFileSync(CONFIG_FILE, content, 'utf-8');
722
724
  } catch (e) {
@@ -734,6 +736,7 @@ function readModels() {
734
736
  }
735
737
 
736
738
  function writeModels(models) {
739
+ assertToolConfigWriteAllowed('codex');
737
740
  fs.writeFileSync(MODELS_FILE, JSON.stringify(models, null, 2), 'utf-8');
738
741
  }
739
742
 
@@ -747,10 +750,12 @@ function readCurrentModels() {
747
750
  }
748
751
 
749
752
  function writeCurrentModels(data) {
753
+ assertToolConfigWriteAllowed('codex');
750
754
  fs.writeFileSync(CURRENT_MODELS_FILE, JSON.stringify(data, null, 2), 'utf-8');
751
755
  }
752
756
 
753
757
  function updateAuthJson(apiKey) {
758
+ assertToolConfigWriteAllowed('codex');
754
759
  let authData = {};
755
760
  if (fs.existsSync(AUTH_FILE)) {
756
761
  try {
@@ -766,6 +771,141 @@ function isPlainObject(value) {
766
771
  return !!value && typeof value === 'object' && !Array.isArray(value);
767
772
  }
768
773
 
774
+ const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude']);
775
+ const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false });
776
+ let toolConfigWriteGuardDepth = 0;
777
+
778
+ function enterToolConfigWriteGuard() {
779
+ toolConfigWriteGuardDepth += 1;
780
+ let active = true;
781
+ return () => {
782
+ if (!active) return;
783
+ active = false;
784
+ toolConfigWriteGuardDepth = Math.max(0, toolConfigWriteGuardDepth - 1);
785
+ };
786
+ }
787
+
788
+ function isToolConfigWriteGuardActive() {
789
+ return toolConfigWriteGuardDepth > 0;
790
+ }
791
+
792
+ function normalizeToolConfigTarget(value) {
793
+ const target = typeof value === 'string' ? value.trim().toLowerCase() : '';
794
+ return TOOL_CONFIG_PERMISSION_TARGETS.has(target) ? target : '';
795
+ }
796
+
797
+ function normalizeToolConfigPermissions(value) {
798
+ const source = isPlainObject(value) ? value : {};
799
+ return {
800
+ codex: source.codex === true,
801
+ claude: source.claude === true
802
+ };
803
+ }
804
+
805
+ function readCodexmatePreferences() {
806
+ if (!fs.existsSync(CODEXMATE_PREFERENCES_FILE)) return {};
807
+ try {
808
+ const raw = fs.readFileSync(CODEXMATE_PREFERENCES_FILE, 'utf-8');
809
+ const parsed = raw && raw.trim() ? JSON.parse(raw) : {};
810
+ return isPlainObject(parsed) ? parsed : {};
811
+ } catch (_) {
812
+ return {};
813
+ }
814
+ }
815
+
816
+ function writeCodexmatePreferences(preferences) {
817
+ ensureDir(CODEXMATE_DIR);
818
+ writeJsonAtomic(CODEXMATE_PREFERENCES_FILE, isPlainObject(preferences) ? preferences : {});
819
+ }
820
+
821
+ function readToolConfigPermissions() {
822
+ const preferences = readCodexmatePreferences();
823
+ return normalizeToolConfigPermissions(preferences.toolConfigPermissions || TOOL_CONFIG_PERMISSION_DEFAULTS);
824
+ }
825
+
826
+ function isToolConfigWriteAllowed(target) {
827
+ const normalizedTarget = normalizeToolConfigTarget(target);
828
+ if (!normalizedTarget) return false;
829
+ return readToolConfigPermissions()[normalizedTarget] === true;
830
+ }
831
+
832
+ function buildToolConfigWriteDeniedPayload(target) {
833
+ const normalizedTarget = normalizeToolConfigTarget(target) || target || '';
834
+ return {
835
+ error: '当前为仅浏览,未修改配置。',
836
+ errorCode: 'tool-config-write-disabled',
837
+ target: normalizedTarget,
838
+ permissions: readToolConfigPermissions()
839
+ };
840
+ }
841
+
842
+ function assertToolConfigWriteAllowed(target) {
843
+ if (!isToolConfigWriteGuardActive()) return;
844
+ if (isToolConfigWriteAllowed(target)) return;
845
+ const payload = buildToolConfigWriteDeniedPayload(target);
846
+ const err = new Error(payload.error);
847
+ err.code = payload.errorCode;
848
+ err.target = payload.target;
849
+ throw err;
850
+ }
851
+
852
+ function getApiToolConfigWriteTarget(action) {
853
+ const name = typeof action === 'string' ? action.trim() : '';
854
+ if (!name) return '';
855
+ const codexWriteActions = new Set([
856
+ 'apply-config-template',
857
+ 'add-provider',
858
+ 'update-provider',
859
+ 'delete-provider',
860
+ 'reset-config',
861
+ 'add-model',
862
+ 'delete-model',
863
+ 'restore-codex-dir',
864
+ 'import-config',
865
+ 'import-auth-profile',
866
+ 'switch-auth-profile',
867
+ 'delete-auth-profile',
868
+ 'proxy-enable-codex-default',
869
+ 'proxy-apply-provider',
870
+ 'local-bridge-toggle',
871
+ 'local-bridge-set-excluded'
872
+ ]);
873
+ const claudeWriteActions = new Set([
874
+ 'apply-claude-settings-raw',
875
+ 'apply-claude-config',
876
+ 'restore-claude-dir',
877
+ 'claude-local-bridge-toggle',
878
+ 'claude-local-bridge-set-excluded',
879
+ 'claude-local-bridge-sync-providers'
880
+ ]);
881
+ if (codexWriteActions.has(name)) return 'codex';
882
+ if (claudeWriteActions.has(name)) return 'claude';
883
+ return '';
884
+ }
885
+
886
+ function setToolConfigPermission(params = {}) {
887
+ const target = normalizeToolConfigTarget(params && params.target);
888
+ if (!target) return { error: '未知配置对象' };
889
+ const preferences = readCodexmatePreferences();
890
+ const current = normalizeToolConfigPermissions(preferences.toolConfigPermissions || TOOL_CONFIG_PERMISSION_DEFAULTS);
891
+ current[target] = params && params.allowWrite === true;
892
+ preferences.toolConfigPermissions = current;
893
+ writeCodexmatePreferences(preferences);
894
+
895
+ let bootstrapNotice = '';
896
+ if (target === 'codex' && current.codex) {
897
+ const bootstrap = ensureManagedConfigBootstrap({ allowWrite: true });
898
+ bootstrapNotice = bootstrap && bootstrap.notice ? bootstrap.notice : '';
899
+ }
900
+
901
+ return {
902
+ success: true,
903
+ target,
904
+ permissions: current,
905
+ bootstrapNotice
906
+ };
907
+ }
908
+
769
909
  const PROVIDER_CONFIG_KEYS = new Set([
770
910
  'name',
771
911
  'base_url',
@@ -2081,12 +2221,22 @@ function addProviderToConfig(params = {}) {
2081
2221
  const name = typeof params.name === 'string' ? params.name.trim() : '';
2082
2222
  const url = typeof params.url === 'string' ? params.url.trim() : '';
2083
2223
  const key = typeof params.key === 'string' ? params.key.trim() : '';
2224
+ const requireModel = !!params.requireModel;
2225
+ const fallbackModel = (() => {
2226
+ if (requireModel) return '';
2227
+ const list = readModels();
2228
+ return Array.isArray(list) && typeof list[0] === 'string' ? list[0].trim() : '';
2229
+ })();
2230
+ const model = typeof params.model === 'string' && params.model.trim()
2231
+ ? params.model.trim()
2232
+ : fallbackModel;
2084
2233
  const useTransform = !!params.useTransform;
2085
2234
  const allowManaged = !!params.allowManaged;
2086
2235
  const normalizedUrl = normalizeBaseUrl(url);
2087
2236
 
2088
2237
  if (!name) return { error: '名称不能为空' };
2089
2238
  if (!url) return { error: 'URL 不能为空' };
2239
+ if (!model) return { error: '模型名称不能为空' };
2090
2240
  if (!isValidProviderName(name)) {
2091
2241
  return { error: '名称仅支持字母/数字/._-' };
2092
2242
  }
@@ -2163,6 +2313,7 @@ function addProviderToConfig(params = {}) {
2163
2313
  `wire_api = "responses"`,
2164
2314
  `requires_openai_auth = ${requiresOpenaiAuth ? 'true' : 'false'}`,
2165
2315
  `preferred_auth_method = "${safeKey}"`,
2316
+ `models = [{ id = "${escapeTomlBasicString(model)}", name = "${escapeTomlBasicString(model)}" }]`,
2166
2317
  ...extraLines,
2167
2318
  `request_max_retries = 4`,
2168
2319
  `stream_max_retries = 10`,
@@ -2173,6 +2324,13 @@ function addProviderToConfig(params = {}) {
2173
2324
 
2174
2325
  try {
2175
2326
  writeConfig(newContent);
2327
+ const models = readModels();
2328
+ if (!models.includes(model)) {
2329
+ writeModels([...models, model]);
2330
+ }
2331
+ const currentModels = readCurrentModels();
2332
+ currentModels[name] = model;
2333
+ writeCurrentModels(currentModels);
2176
2334
  } catch (e) {
2177
2335
  return { error: `写入配置失败: ${e.message}` };
2178
2336
  }
@@ -5525,6 +5683,7 @@ function readLocalBridgeSettings() {
5525
5683
  }
5526
5684
 
5527
5685
  function writeLocalBridgeSettings(settings) {
5686
+ assertToolConfigWriteAllowed('codex');
5528
5687
  fs.writeFileSync(LOCAL_BRIDGE_SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf-8');
5529
5688
  }
5530
5689
 
@@ -5623,6 +5782,7 @@ function readClaudeLocalBridgeSettings() {
5623
5782
  }
5624
5783
 
5625
5784
  function writeClaudeLocalBridgeSettings(settings) {
5785
+ assertToolConfigWriteAllowed('claude');
5626
5786
  fs.writeFileSync(CLAUDE_LOCAL_BRIDGE_SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf-8');
5627
5787
  }
5628
5788
 
@@ -5637,6 +5797,7 @@ function readClaudeLocalProvidersFile() {
5637
5797
  }
5638
5798
 
5639
5799
  function writeClaudeLocalProvidersFile(data) {
5800
+ assertToolConfigWriteAllowed('claude');
5640
5801
  ensureDir(CONFIG_DIR);
5641
5802
  fs.writeFileSync(CLAUDE_LOCAL_PROVIDERS_FILE, JSON.stringify(data, null, 2), 'utf-8');
5642
5803
  }
@@ -5651,6 +5812,7 @@ function syncClaudeProvidersToBridgeFile() {
5651
5812
  }
5652
5813
 
5653
5814
  function toggleClaudeLocalBridge(params = {}) {
5815
+ assertToolConfigWriteAllowed('claude');
5654
5816
  const enable = !!params.enable;
5655
5817
  const settings = readClaudeLocalBridgeSettings();
5656
5818
 
@@ -9159,6 +9321,7 @@ function maskKey(key) {
9159
9321
 
9160
9322
  // 应用到 Claude Code settings.json(跨平台)
9161
9323
  function applyToClaudeSettings(config = {}) {
9324
+ assertToolConfigWriteAllowed('claude');
9162
9325
  try {
9163
9326
  const apiKey = (config.apiKey || '').trim();
9164
9327
  if (!apiKey) {
@@ -9258,6 +9421,7 @@ function readClaudeSettingsRaw() {
9258
9421
  }
9259
9422
 
9260
9423
  function applyClaudeSettingsRaw(params = {}) {
9424
+ assertToolConfigWriteAllowed('claude');
9261
9425
  const content = typeof params.content === 'string' ? params.content : '';
9262
9426
  if (!content.trim()) {
9263
9427
  return { error: '内容不能为空' };
@@ -10751,14 +10915,28 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
10751
10915
  });
10752
10916
  req.on('end', async () => {
10753
10917
  if (bodyTooLarge) return;
10918
+ let leaveToolConfigWriteGuard = null;
10754
10919
  try {
10755
10920
  const { action, params } = JSON.parse(body || '{}');
10921
+ leaveToolConfigWriteGuard = typeof enterToolConfigWriteGuard === 'function'
10922
+ ? enterToolConfigWriteGuard()
10923
+ : () => {};
10756
10924
  let result;
10757
10925
 
10758
- switch (action) {
10926
+ const guardedToolConfigTarget = getApiToolConfigWriteTarget(action);
10927
+ if (guardedToolConfigTarget && !isToolConfigWriteAllowed(guardedToolConfigTarget)) {
10928
+ result = buildToolConfigWriteDeniedPayload(guardedToolConfigTarget);
10929
+ } else {
10930
+ switch (action) {
10759
10931
  case 'health-check':
10760
10932
  result = { ok: true };
10761
10933
  break;
10934
+ case 'get-tool-config-permissions':
10935
+ result = { permissions: readToolConfigPermissions() };
10936
+ break;
10937
+ case 'set-tool-config-permission':
10938
+ result = setToolConfigPermission(params || {});
10939
+ break;
10762
10940
  case 'status': {
10763
10941
  const statusConfigResult = readConfigOrVirtualDefault();
10764
10942
  const config = statusConfigResult.config;
@@ -10797,7 +10975,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
10797
10975
  configReady: !statusConfigResult.isVirtual,
10798
10976
  configErrorType: statusConfigResult.errorType || '',
10799
10977
  configNotice: statusConfigResult.reason || '',
10800
- initNotice: consumeInitNotice()
10978
+ initNotice: consumeInitNotice(),
10979
+ toolConfigPermissions: readToolConfigPermissions()
10801
10980
  };
10802
10981
  break;
10803
10982
  }
@@ -10866,7 +11045,7 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
10866
11045
  result = buildConfigTemplateDiff(params || {});
10867
11046
  break;
10868
11047
  case 'add-provider':
10869
- result = addProviderToConfig(params || {});
11048
+ result = addProviderToConfig({ ...(params || {}), requireModel: true });
10870
11049
  break;
10871
11050
  case 'update-provider':
10872
11051
  result = updateProviderInConfig(params || {});
@@ -11437,6 +11616,7 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
11437
11616
  break;
11438
11617
  default:
11439
11618
  result = { error: '未知操作' };
11619
+ }
11440
11620
  }
11441
11621
 
11442
11622
  const responseBody = JSON.stringify(result, null, 2);
@@ -11445,7 +11625,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
11445
11625
  'Content-Length': Buffer.byteLength(responseBody, 'utf-8')
11446
11626
  });
11447
11627
  res.end(responseBody, 'utf-8');
11628
+ if (leaveToolConfigWriteGuard) leaveToolConfigWriteGuard();
11448
11629
  } catch (e) {
11630
+ if (leaveToolConfigWriteGuard) leaveToolConfigWriteGuard();
11449
11631
  const errorBody = JSON.stringify({ error: e.message }, null, 2);
11450
11632
  res.writeHead(500, {
11451
11633
  'Content-Type': 'application/json; charset=utf-8',
@@ -16044,7 +16226,10 @@ async function main() {
16044
16226
  const args = process.argv.slice(2);
16045
16227
  const command = args[0];
16046
16228
  const isMcpCommand = command === 'mcp';
16047
- const bootstrap = ensureManagedConfigBootstrap();
16229
+ const shouldGateInitialBootstrap = command === 'run' || isMcpCommand;
16230
+ const bootstrap = ensureManagedConfigBootstrap({
16231
+ allowWrite: shouldGateInitialBootstrap ? isToolConfigWriteAllowed('codex') : true
16232
+ });
16048
16233
  if (bootstrap && bootstrap.notice) {
16049
16234
  // MCP stdio transport requires stdout to be protocol-clean.
16050
16235
  if (!isMcpCommand) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -102,18 +102,75 @@ function renderTemplate(templateText, values = {}) {
102
102
  });
103
103
  }
104
104
 
105
+ function translate(t, key, fallback, params = null) {
106
+ if (typeof t !== 'function') return fallback;
107
+ const translated = t(key, params);
108
+ return translated === key ? fallback : translated;
109
+ }
110
+
111
+ function localizePluginMeta(meta, t) {
112
+ const safe = meta && typeof meta === 'object' ? meta : {};
113
+ const titleKey = typeof safe.titleKey === 'string' ? safe.titleKey : '';
114
+ const descriptionKey = typeof safe.descriptionKey === 'string' ? safe.descriptionKey : '';
115
+ const statusLabelKey = typeof safe.statusLabelKey === 'string' ? safe.statusLabelKey : '';
116
+ return {
117
+ ...safe,
118
+ title: titleKey ? translate(t, titleKey, safe.title || '') : (safe.title || ''),
119
+ description: descriptionKey ? translate(t, descriptionKey, safe.description || '') : (safe.description || ''),
120
+ statusLabel: statusLabelKey ? translate(t, statusLabelKey, safe.statusLabel || '') : (safe.statusLabel || '')
121
+ };
122
+ }
123
+
124
+ const BUILTIN_TEMPLATE_I18N = Object.freeze({
125
+ builtin_comment_polish: Object.freeze({
126
+ nameKey: 'plugins.builtin.commentPolish.name',
127
+ descKey: 'plugins.builtin.commentPolish.desc',
128
+ lineKey: 'plugins.builtin.commentPolish.line1',
129
+ fallbackName: '代码注释润色',
130
+ fallbackDesc: '轻微收敛以下代码注释 {{code}}',
131
+ fallbackLine: '轻微收敛以下代码注释',
132
+ vars: ['{{code}}']
133
+ }),
134
+ builtin_rule_ack: Object.freeze({
135
+ nameKey: 'plugins.builtin.ruleAck.name',
136
+ descKey: 'plugins.builtin.ruleAck.desc',
137
+ lineKey: 'plugins.builtin.ruleAck.line1',
138
+ fallbackName: '规则确认回复',
139
+ fallbackDesc: '请根据【{{rule}}】,收到请回复',
140
+ fallbackLine: '请根据【{{rule}}】,收到请回复',
141
+ vars: []
142
+ })
143
+ });
144
+
145
+ function localizeBuiltinPromptTemplate(item, t) {
146
+ const safe = item && typeof item === 'object' ? item : {};
147
+ if (safe.isBuiltin !== true) return safe;
148
+ const spec = BUILTIN_TEMPLATE_I18N[safe.id];
149
+ if (!spec) return safe;
150
+ const line = translate(t, spec.lineKey, spec.fallbackLine);
151
+ return {
152
+ ...safe,
153
+ name: translate(t, spec.nameKey, spec.fallbackName),
154
+ description: translate(t, spec.descKey, spec.fallbackDesc),
155
+ template: spec.vars && spec.vars.length ? [line, '', ...spec.vars].join('\n') : line
156
+ };
157
+ }
158
+
105
159
  import { pluginsRegistry } from '../registry.mjs';
106
160
 
107
161
  export function createPluginsComputed() {
108
162
  return {
109
163
  pluginsCatalog() {
110
- return pluginsRegistry.map((entry) => entry && entry.meta).filter(Boolean);
164
+ return pluginsRegistry
165
+ .map((entry) => entry && entry.meta)
166
+ .filter(Boolean)
167
+ .map((meta) => localizePluginMeta(meta, this.t));
111
168
  },
112
169
 
113
170
  pluginsActiveMeta() {
114
171
  const id = typeof this.pluginsActiveId === 'string' ? this.pluginsActiveId.trim() : '';
115
172
  const entry = pluginsRegistry.find((item) => item && item.id === id) || null;
116
- return entry && entry.meta ? entry.meta : null;
173
+ return entry && entry.meta ? localizePluginMeta(entry.meta, this.t) : null;
117
174
  },
118
175
 
119
176
  pluginsActiveAttribution() {
@@ -138,6 +195,7 @@ export function createPluginsComputed() {
138
195
  const list = Array.isArray(this.promptTemplatesListRaw) ? this.promptTemplatesListRaw : [];
139
196
  return list
140
197
  .map((item) => normalizePromptTemplateEntry(item))
198
+ .map((item) => localizeBuiltinPromptTemplate(item, this.t))
141
199
  .filter((item) => item.id && item.name)
142
200
  .map((item) => {
143
201
  const vars = parseTemplateVariables(item.template);
@@ -178,7 +236,7 @@ export function createPluginsComputed() {
178
236
  const id = typeof draft.id === 'string' ? draft.id : '';
179
237
  const name = typeof draft.name === 'string' ? draft.name : '';
180
238
  if (!id && !name) return null;
181
- return normalizePromptTemplateEntry(draft);
239
+ return localizeBuiltinPromptTemplate(normalizePromptTemplateEntry(draft), this.t);
182
240
  },
183
241
 
184
242
  promptTemplateVars() {
@@ -3,8 +3,11 @@ import { pluginOwnership } from './ownership.mjs';
3
3
  const baseMeta = {
4
4
  id: 'prompt-templates',
5
5
  title: 'Prompt Templates',
6
+ titleKey: 'plugins.catalog.promptTemplates.title',
6
7
  description: 'Standardized, template-driven prompts with variables and copy/export helpers.',
8
+ descriptionKey: 'plugins.catalog.promptTemplates.description',
7
9
  statusLabel: 'standard',
10
+ statusLabelKey: 'plugins.status.standard',
8
11
  tone: 'configured'
9
12
  };
10
13
 
package/web-ui/app.js CHANGED
@@ -62,11 +62,13 @@ document.addEventListener('DOMContentLoaded', () => {
62
62
  messageType: '',
63
63
  showAddModal: false,
64
64
  showEditModal: false,
65
+ showAddProviderKey: false,
65
66
  showEditProviderKey: false,
66
67
  showModelModal: false,
67
68
  showModelListModal: false,
68
69
  showClaudeConfigModal: false,
69
70
  showEditConfigModal: false,
71
+ showAddClaudeConfigKey: false,
70
72
  showEditClaudeConfigKey: false,
71
73
  showOpenclawConfigModal: false,
72
74
  showConfigTemplateModal: false,
@@ -268,7 +270,7 @@ document.addEventListener('DOMContentLoaded', () => {
268
270
  installRegistryPreset: 'default',
269
271
  installRegistryCustom: '',
270
272
  installStatusTargets: null,
271
- newProvider: { name: '', url: '', key: '', useTransform: false, _suggestedModel: '' },
273
+ newProvider: { name: '', url: '', key: '', model: '', useTransform: false },
272
274
  resetConfigLoading: false,
273
275
  editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false },
274
276
  newModelName: '',
@@ -293,7 +295,8 @@ document.addEventListener('DOMContentLoaded', () => {
293
295
  currentOpenclawConfig: '',
294
296
  openclawConfigs: {
295
297
  '默认配置': {
296
- content: DEFAULT_OPENCLAW_TEMPLATE
298
+ content: DEFAULT_OPENCLAW_TEMPLATE,
299
+ isDefault: true
297
300
  }
298
301
  },
299
302
  openclawEditing: { name: '', content: '', lockName: false },
@@ -343,6 +346,11 @@ document.addEventListener('DOMContentLoaded', () => {
343
346
  overrideModels: true,
344
347
  showKey: false
345
348
  },
349
+ openclawAccordionStep: 1,
350
+ openclawValidation: {
351
+ providerName: { valid: true, message: '' },
352
+ modelId: { valid: true, message: '' }
353
+ },
346
354
  openclawAgentsList: [],
347
355
  openclawProviders: [],
348
356
  openclawMissingProviders: [],
@@ -358,6 +366,8 @@ document.addEventListener('DOMContentLoaded', () => {
358
366
  codexDownloadProgress: 0,
359
367
  codexDownloadTimer: null,
360
368
  settingsTab: 'general',
369
+ toolConfigPermissions: { codex: false, claude: false },
370
+ toolConfigPermissionSaving: { codex: false, claude: false },
361
371
  sessionTrashEnabled: true,
362
372
  sessionTrashItems: [],
363
373
  sessionTrashVisibleCount: SESSION_TRASH_PAGE_SIZE,
@@ -567,7 +577,8 @@ document.addEventListener('DOMContentLoaded', () => {
567
577
  : { content: DEFAULT_OPENCLAW_TEMPLATE };
568
578
  const normalized = {
569
579
  '默认配置': {
570
- content: typeof defaultEntry.content === 'string' ? defaultEntry.content : DEFAULT_OPENCLAW_TEMPLATE
580
+ content: typeof defaultEntry.content === 'string' ? defaultEntry.content : DEFAULT_OPENCLAW_TEMPLATE,
581
+ isDefault: true
571
582
  }
572
583
  };
573
584
  for (const [name, value] of Object.entries(source)) {