codexmate 0.1.10 → 0.1.13

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 (40) hide show
  1. package/README.md +5 -7
  2. package/README.vi.md +4 -7
  3. package/README.zh.md +5 -7
  4. package/cli/agents-files.js +27 -3
  5. package/cli/prompt-history.js +171 -0
  6. package/cli/system-prompt-files.js +20 -2
  7. package/cli.js +162 -5
  8. package/package.json +1 -1
  9. package/web-ui/app.js +95 -4
  10. package/web-ui/index.html +2 -1
  11. package/web-ui/modules/app.methods.agents.mjs +90 -0
  12. package/web-ui/modules/app.methods.index.mjs +2 -0
  13. package/web-ui/modules/app.methods.navigation.mjs +7 -1
  14. package/web-ui/modules/app.methods.pi-config.mjs +371 -0
  15. package/web-ui/modules/app.methods.session-actions.mjs +57 -0
  16. package/web-ui/modules/app.methods.startup-claude.mjs +2 -1
  17. package/web-ui/modules/app.methods.system-prompt.mjs +81 -0
  18. package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -2
  19. package/web-ui/modules/app.methods.web-ui-preferences.mjs +8 -1
  20. package/web-ui/modules/config-mode.computed.mjs +2 -1
  21. package/web-ui/modules/i18n/locales/en.mjs +52 -0
  22. package/web-ui/modules/i18n/locales/ja.mjs +52 -0
  23. package/web-ui/modules/i18n/locales/vi.mjs +52 -0
  24. package/web-ui/modules/i18n/locales/zh-tw.mjs +52 -0
  25. package/web-ui/modules/i18n/locales/zh.mjs +52 -0
  26. package/web-ui/partials/index/layout-header.html +20 -0
  27. package/web-ui/partials/index/panel-config-claude.html +3 -3
  28. package/web-ui/partials/index/panel-config-codex.html +3 -3
  29. package/web-ui/partials/index/panel-config-kilocode.html +5 -5
  30. package/web-ui/partials/index/panel-config-openclaw.html +3 -3
  31. package/web-ui/partials/index/panel-config-opencode.html +5 -5
  32. package/web-ui/partials/index/panel-config-pi.html +171 -0
  33. package/web-ui/partials/index/panel-prompts.html +52 -0
  34. package/web-ui/partials/index/panel-sessions.html +12 -0
  35. package/web-ui/partials/index/panel-settings.html +42 -0
  36. package/web-ui/res/web-ui-render.precompiled.js +1101 -252
  37. package/web-ui/styles/modals-core.css +86 -0
  38. package/web-ui/styles/sessions-preview.css +38 -0
  39. package/web-ui/styles/settings-panel.css +7 -0
  40. package/web-ui/styles/titles-cards.css +2 -2
package/cli.js CHANGED
@@ -108,6 +108,9 @@ const {
108
108
  const {
109
109
  createSystemPromptFileController
110
110
  } = require('./cli/system-prompt-files');
111
+ const {
112
+ createPromptHistoryController
113
+ } = require('./cli/prompt-history');
111
114
  const {
112
115
  createArchiveHelperController
113
116
  } = require('./cli/archive-helpers');
@@ -888,8 +891,8 @@ function isPlainObject(value) {
888
891
  return !!value && typeof value === 'object' && !Array.isArray(value);
889
892
  }
890
893
 
891
- const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude', 'opencode', 'kilocode', 'openclaw']);
892
- const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false, kilocode: false, openclaw: false });
894
+ const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude', 'opencode', 'kilocode', 'openclaw', 'pi']);
895
+ const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false, kilocode: false, openclaw: false, pi: false });
893
896
  let toolConfigWriteGuardDepth = 0;
894
897
 
895
898
  function enterToolConfigWriteGuard() {
@@ -918,7 +921,8 @@ function normalizeToolConfigPermissions(value) {
918
921
  claude: source.claude === true,
919
922
  opencode: source.opencode === true,
920
923
  kilocode: source.kilocode === true,
921
- openclaw: source.openclaw === true
924
+ openclaw: source.openclaw === true,
925
+ pi: source.pi === true
922
926
  };
923
927
  }
924
928
 
@@ -976,7 +980,7 @@ function normalizeMainTabPreference(value) {
976
980
 
977
981
  function normalizeConfigModePreference(value) {
978
982
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
979
- return ['codex', 'claude', 'openclaw', 'opencode', 'kilocode'].includes(normalized) ? normalized : 'codex';
983
+ return ['codex', 'claude', 'openclaw', 'opencode', 'kilocode', 'pi'].includes(normalized) ? normalized : 'codex';
980
984
  }
981
985
 
982
986
  function normalizeUsageTimeRangePreference(value) {
@@ -1200,9 +1204,123 @@ function getApiToolConfigWriteTarget(action) {
1200
1204
  if (opencodeWriteActions.has(name)) return 'opencode';
1201
1205
  if (kilocodeWriteActions.has(name)) return 'kilocode';
1202
1206
  if (openclawWriteActions.has(name)) return 'openclaw';
1207
+ const piWriteActions = new Set([
1208
+ 'write-pi-models',
1209
+ 'write-pi-settings'
1210
+ ]);
1211
+ if (piWriteActions.has(name)) return 'pi';
1203
1212
  return '';
1204
1213
  }
1205
1214
 
1215
+ function getPiAgentDir() {
1216
+ const homeDir = (typeof os.homedir === 'function' ? os.homedir() : null) || process.env.HOME || process.env.USERPROFILE || '';
1217
+ return path.join(homeDir, '.pi', 'agent');
1218
+ }
1219
+
1220
+ function readPiModels(params = {}) {
1221
+ try {
1222
+ const dir = getPiAgentDir();
1223
+ const filePath = path.join(dir, 'models.json');
1224
+ if (!fs.existsSync(filePath)) {
1225
+ return { providers: {} };
1226
+ }
1227
+ const raw = fs.readFileSync(filePath, 'utf-8');
1228
+ const data = JSON.parse(raw);
1229
+ const providers = (data && typeof data === 'object' && data.providers && typeof data.providers === 'object')
1230
+ ? data.providers
1231
+ : {};
1232
+ return { providers };
1233
+ } catch (e) {
1234
+ return { error: e && e.message ? e.message : '读取 Pi models.json 失败' };
1235
+ }
1236
+ }
1237
+
1238
+ function writePiModels(params = {}) {
1239
+ const providers = params && typeof params.providers === 'object' ? params.providers : null;
1240
+ if (providers === null) {
1241
+ return { error: 'providers 不能为空' };
1242
+ }
1243
+ try {
1244
+ const dir = getPiAgentDir();
1245
+ fs.mkdirSync(dir, { recursive: true });
1246
+ const filePath = path.join(dir, 'models.json');
1247
+ let existing = {};
1248
+ if (fs.existsSync(filePath)) {
1249
+ try {
1250
+ existing = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
1251
+ } catch (_) {
1252
+ existing = {};
1253
+ }
1254
+ }
1255
+ existing.providers = providers;
1256
+ fs.writeFileSync(filePath, JSON.stringify(existing, null, 2), 'utf-8');
1257
+ return { success: true };
1258
+ } catch (e) {
1259
+ return { error: e && e.message ? e.message : '写入 Pi models.json 失败' };
1260
+ }
1261
+ }
1262
+
1263
+ async function fetchPiRemoteModels(params = {}) {
1264
+ const baseUrl = typeof params.baseUrl === 'string' ? params.baseUrl.trim() : '';
1265
+ const apiKey = typeof params.apiKey === 'string' ? params.apiKey.trim() : '';
1266
+ if (!baseUrl || !isValidHttpUrl(baseUrl)) {
1267
+ return { error: 'baseUrl 无效', models: [] };
1268
+ }
1269
+ try {
1270
+ const result = await fetchModelsFromBaseUrl(baseUrl, apiKey);
1271
+ const models = result && Array.isArray(result.models)
1272
+ ? [...new Set(result.models.filter((id) => typeof id === 'string' && id !== ''))].sort()
1273
+ : [];
1274
+ if (models.length > 0) return { models };
1275
+ return { error: (result && result.error) || '未获取到可用模型', models: [] };
1276
+ } catch (e) {
1277
+ return { error: e && e.message ? e.message : '请求失败', models: [] };
1278
+ }
1279
+ }
1280
+
1281
+ function readPiSettings(params = {}) {
1282
+ try {
1283
+ const dir = getPiAgentDir();
1284
+ const filePath = path.join(dir, 'settings.json');
1285
+ if (!fs.existsSync(filePath)) {
1286
+ return { settings: {} };
1287
+ }
1288
+ const raw = fs.readFileSync(filePath, 'utf-8');
1289
+ const data = JSON.parse(raw);
1290
+ return { settings: data || {} };
1291
+ } catch (e) {
1292
+ return { error: e && e.message ? e.message : '读取 Pi settings.json 失败' };
1293
+ }
1294
+ }
1295
+
1296
+ function writePiSettings(params = {}) {
1297
+ const updates = {};
1298
+ if (typeof (params && params.defaultProvider) === 'string') updates.defaultProvider = params.defaultProvider;
1299
+ if (typeof (params && params.defaultModel) === 'string') updates.defaultModel = params.defaultModel;
1300
+ if (Object.keys(updates).length === 0) {
1301
+ return { error: '缺少可写入的设置项' };
1302
+ }
1303
+ try {
1304
+ const dir = getPiAgentDir();
1305
+ fs.mkdirSync(dir, { recursive: true });
1306
+ const filePath = path.join(dir, 'settings.json');
1307
+ let existing = {};
1308
+ if (fs.existsSync(filePath)) {
1309
+ try {
1310
+ existing = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
1311
+ } catch (_) {
1312
+ existing = {};
1313
+ }
1314
+ if (!existing || typeof existing !== 'object' || Array.isArray(existing)) existing = {};
1315
+ }
1316
+ const updated = { ...existing, ...updates };
1317
+ fs.writeFileSync(filePath, JSON.stringify(updated, null, 2), 'utf-8');
1318
+ return { success: true, settings: updated };
1319
+ } catch (e) {
1320
+ return { error: e && e.message ? e.message : '写入 Pi settings.json 失败' };
1321
+ }
1322
+ }
1323
+
1206
1324
  function setToolConfigPermission(params = {}) {
1207
1325
  const target = normalizeToolConfigTarget(params && params.target);
1208
1326
  if (!target) return { error: '未知配置对象' };
@@ -2057,6 +2175,17 @@ async function fetchProviderModels(providerName, overrides = {}) {
2057
2175
  }
2058
2176
 
2059
2177
  // buildAgentsDiff keeps the metaOnly optimization inside cli/agents-files.js.
2178
+ const {
2179
+ backupPromptBeforeWrite: historyBackup,
2180
+ listPromptHistory,
2181
+ readPromptHistory,
2182
+ clearPromptHistory
2183
+ } = createPromptHistoryController({
2184
+ fs,
2185
+ path,
2186
+ CONFIG_DIR
2187
+ });
2188
+
2060
2189
  const {
2061
2190
  resolveAgentsFilePath,
2062
2191
  validateAgentsBaseDir,
@@ -2083,6 +2212,7 @@ const {
2083
2212
  AGENTS_FILE_NAME,
2084
2213
  CLAUDE_DIR,
2085
2214
  CLAUDE_MD_FILE_NAME,
2215
+ backupPromptBeforeWrite: historyBackup,
2086
2216
  readOpenclawAgentsFile() {
2087
2217
  return readOpenclawAgentsFile(...arguments);
2088
2218
  },
@@ -2102,7 +2232,8 @@ const {
2102
2232
  crypto,
2103
2233
  buildLineDiff,
2104
2234
  CONFIG_DIR,
2105
- PI_AGENT_DIR
2235
+ PI_AGENT_DIR,
2236
+ backupPromptBeforeWrite: historyBackup
2106
2237
  });
2107
2238
 
2108
2239
  const {
@@ -13157,6 +13288,26 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
13157
13288
  };
13158
13289
  break;
13159
13290
  }
13291
+ case 'read-pi-models': {
13292
+ result = readPiModels(params || {});
13293
+ break;
13294
+ }
13295
+ case 'write-pi-models': {
13296
+ result = writePiModels(params || {});
13297
+ break;
13298
+ }
13299
+ case 'read-pi-settings': {
13300
+ result = readPiSettings(params || {});
13301
+ break;
13302
+ }
13303
+ case 'write-pi-settings': {
13304
+ result = writePiSettings(params || {});
13305
+ break;
13306
+ }
13307
+ case 'fetch-pi-remote-models': {
13308
+ result = await fetchPiRemoteModels(params || {});
13309
+ break;
13310
+ }
13160
13311
  case 'install-status':
13161
13312
  result = buildInstallStatusReport();
13162
13313
  break;
@@ -13378,6 +13529,12 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
13378
13529
  case 'preview-system-prompt-diff':
13379
13530
  result = buildSystemPromptDiff(params || {});
13380
13531
  break;
13532
+ case 'list-prompt-history':
13533
+ result = listPromptHistory((params && params.bucket) || '');
13534
+ break;
13535
+ case 'get-prompt-history':
13536
+ result = readPromptHistory((params && params.bucket) || '', (params && params.id) || '');
13537
+ break;
13381
13538
  case 'switch':
13382
13539
  case 'use':
13383
13540
  case 'add':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.1.10",
3
+ "version": "0.1.13",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/web-ui/app.js CHANGED
@@ -26,6 +26,35 @@ document.addEventListener('DOMContentLoaded', () => {
26
26
  }
27
27
 
28
28
  const { createApp } = Vue;
29
+ const showFatalErrorOverlay = (label, message, stack, extra) => {
30
+ try {
31
+ const target = document.querySelector('#app') || document.body;
32
+ const pre = document.createElement('pre');
33
+ pre.style.cssText = 'position:fixed;bottom:0;left:0;right:0;background:#900;color:#fff;padding:12px;white-space:pre-wrap;font-size:12px;z-index:9999;';
34
+ pre.textContent = '[' + label + '] ' + (message || '') + '\n' + (stack || '') + (extra ? '\n' + extra : '');
35
+ target.appendChild(pre);
36
+ } catch (_) {}
37
+ };
38
+ window.addEventListener('error', (event) => {
39
+ console.error('[window error]', event.message, event.filename, event.lineno, event.colno, event.error);
40
+ try {
41
+ const target = document.querySelector('#app') || document.body;
42
+ const pre = document.createElement('pre');
43
+ pre.style.cssText = 'position:fixed;bottom:60px;left:0;right:0;background:#b00;color:#fff;padding:12px;white-space:pre-wrap;font-size:12px;z-index:9999;';
44
+ pre.textContent = '[window error] ' + (event.message || '') + '\n' + ((event.error && event.error.stack) || (event.filename + ':' + event.lineno + ':' + event.colno));
45
+ target.appendChild(pre);
46
+ } catch (_) {}
47
+ });
48
+ window.addEventListener('unhandledrejection', (event) => {
49
+ console.error('[unhandled rejection]', event.reason);
50
+ try {
51
+ const target = document.querySelector('#app') || document.body;
52
+ const pre = document.createElement('pre');
53
+ pre.style.cssText = 'position:fixed;bottom:120px;left:0;right:0;background:#b00;color:#fff;padding:12px;white-space:pre-wrap;font-size:12px;z-index:9999;';
54
+ pre.textContent = '[unhandled rejection] ' + (event.reason && event.reason.message ? event.reason.message : String(event.reason)) + '\n' + ((event.reason && event.reason.stack) || '');
55
+ target.appendChild(pre);
56
+ } catch (_) {}
57
+ });
29
58
 
30
59
  const appOptions = {
31
60
  data() {
@@ -74,6 +103,13 @@ document.addEventListener('DOMContentLoaded', () => {
74
103
  showAgentsModal: false,
75
104
  promptsSubTab: 'codex',
76
105
  projectClaudeMdPath: '',
106
+ promptHistoryVisible: false,
107
+ promptHistoryLoading: false,
108
+ promptHistoryBucket: '',
109
+ promptHistoryItems: [],
110
+ promptHistoryPreviewId: '',
111
+ promptHistoryPreviewContent: '',
112
+ promptHistoryError: '',
77
113
  promptPresets: [],
78
114
  selectedPromptPresetId: '',
79
115
  promptPresetNameDraft: '',
@@ -89,6 +125,29 @@ document.addEventListener('DOMContentLoaded', () => {
89
125
  showCodexBridgePoolModal: false,
90
126
  showClaudeBridgePoolModal: false,
91
127
  showWebhookModal: false,
128
+ piProviders: {},
129
+ piProviderIds: [],
130
+ editingPiProvider: null,
131
+ addingPiProviderId: '',
132
+ addingPiProviderName: '',
133
+ addingPiProviderBaseUrl: '',
134
+ addingPiProviderApi: 'openai-completions',
135
+ addingPiProviderApiKey: '',
136
+ addingPiProviderModel: '',
137
+ addingPiRemoteModels: [],
138
+ addingPiRemoteLoading: false,
139
+ addingPiRemoteError: '',
140
+ addingPiRemoteDebounce: null,
141
+ showAddPiProviderModal: false,
142
+ piProviderLoading: false,
143
+ piSaving: false,
144
+ piRemoteModels: [],
145
+ piRemoteModelsLoading: false,
146
+ piRemoteModelError: '',
147
+ piModelSearch: '',
148
+ piActiveProvider: '',
149
+ piActiveModel: '',
150
+ piShowKey: false,
92
151
  // Plugins
93
152
  pluginsActiveId: 'prompt-templates',
94
153
  pluginsLoading: false,
@@ -130,6 +189,7 @@ document.addEventListener('DOMContentLoaded', () => {
130
189
  configTemplateDiffFingerprint: '',
131
190
  _configTemplateDiffPreviewRequestToken: null,
132
191
  configTemplateDiffConfirmEnabled: true,
192
+ configModeVisibility: { codex: true, claude: true, openclaw: true, opencode: true, kilocode: true, pi: true },
133
193
  codexApplying: false,
134
194
  _pendingCodexApplyOptions: null,
135
195
  agentsContent: '',
@@ -161,6 +221,13 @@ document.addEventListener('DOMContentLoaded', () => {
161
221
  sysPromptPath: '',
162
222
  sysPromptExists: false,
163
223
  sysPromptHash: '',
224
+ sysHistoryBucket: '',
225
+ sysHistoryVisible: false,
226
+ sysHistoryLoading: false,
227
+ sysHistoryItems: [],
228
+ sysHistoryPreviewId: '',
229
+ sysHistoryPreviewContent: '',
230
+ sysHistoryError: '',
164
231
  sysPromptLoading: false,
165
232
  sysPromptSaving: false,
166
233
  sysPromptDiffVisible: false,
@@ -418,8 +485,8 @@ document.addEventListener('DOMContentLoaded', () => {
418
485
  providerCacheError: '',
419
486
  providerCacheRequestSeq: 0,
420
487
  settingsTab: 'general',
421
- toolConfigPermissions: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: false },
422
- toolConfigPermissionSaving: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: false },
488
+ toolConfigPermissions: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: false, pi: false },
489
+ toolConfigPermissionSaving: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: false, pi: false },
423
490
  sessionTrashEnabled: true,
424
491
  sessionTrashItems: [],
425
492
  sessionTrashVisibleCount: SESSION_TRASH_PAGE_SIZE,
@@ -762,8 +829,19 @@ document.addEventListener('DOMContentLoaded', () => {
762
829
  }
763
830
  },
764
831
 
832
+ watch: {
833
+ configMode(newMode) {
834
+ if (newMode === 'pi' && typeof this.loadPiSources === 'function') {
835
+ this.loadPiSources();
836
+ }
837
+ }
838
+ },
839
+
765
840
  computed: createAppComputed(),
766
- methods: createAppMethods()
841
+ methods: {
842
+ ...createAppMethods(),
843
+ ...(typeof createPiConfigMethods === 'function' ? createPiConfigMethods({ api }) : {})
844
+ }
767
845
  };
768
846
 
769
847
  if (typeof window.__CODEXMATE_WEB_UI_RENDER__ === 'function') {
@@ -771,6 +849,19 @@ document.addEventListener('DOMContentLoaded', () => {
771
849
  }
772
850
 
773
851
  const app = createApp(appOptions);
852
+ app.config.errorHandler = (err, vm, info) => {
853
+ console.error('[Vue error handler]', err, info);
854
+ if (err && err.stack) console.error(err.stack);
855
+ showFatalErrorOverlay('Vue error', err && err.message ? err.message : String(err), err && err.stack ? err.stack : '', info || '');
856
+ };
774
857
 
775
- app.mount('#app');
858
+ try {
859
+ app.mount('#app');
860
+ } catch (error) {
861
+ console.error('Failed to mount Web UI:', error);
862
+ const fallback = document.querySelector('#app');
863
+ if (fallback) {
864
+ fallback.innerHTML = '<pre style="color:red;white-space:pre-wrap;">Failed to mount Web UI\n' + (error && error.stack ? error.stack : String(error)) + '</pre>';
865
+ }
866
+ }
776
867
  });
package/web-ui/index.html CHANGED
@@ -18,6 +18,7 @@
18
18
  <!-- @include ./partials/index/panel-config-openclaw.html -->
19
19
  <!-- @include ./partials/index/panel-config-opencode.html -->
20
20
  <!-- @include ./partials/index/panel-config-kilocode.html -->
21
+ <!-- @include ./partials/index/panel-config-pi.html -->
21
22
  <!-- @include ./partials/index/panel-sessions.html -->
22
23
  <!-- @include ./partials/index/panel-usage.html -->
23
24
  <!-- @include ./partials/index/panel-docs.html -->
@@ -34,6 +35,6 @@
34
35
  <!-- @include ./partials/index/modal-skills.html -->
35
36
  <!-- @include ./partials/index/modal-health-check.html -->
36
37
  <!-- @include ./partials/index/modal-confirm-toast.html -->
37
- <script type="module" src="/web-ui/app.js"></script>
38
+ <script type="module" src="/web-ui/app.js?cb=3"></script>
38
39
  </body>
39
40
  </html>
@@ -20,6 +20,22 @@ function isValidOpenclawWorkspaceFileName(fileName) {
20
20
  return true;
21
21
  }
22
22
 
23
+ function sanitizePromptHistoryId(raw) {
24
+ const safe = typeof raw === 'string' ? raw.trim() : '';
25
+ if (!safe) return 'global';
26
+ const replaced = safe.replace(/[^A-Za-z0-9_.-]/g, '_');
27
+ return (replaced || 'global').slice(0, 64);
28
+ }
29
+
30
+ function resolveAgentsHistoryBucket(instance) {
31
+ const subTab = typeof instance.promptsSubTab === 'string' ? instance.promptsSubTab : 'codex';
32
+ if (subTab === 'claude-project') {
33
+ const projectPath = (instance.projectClaudeMdPath || '').trim();
34
+ return 'claude-project_' + sanitizePromptHistoryId(projectPath || 'global');
35
+ }
36
+ return 'codex_global';
37
+ }
38
+
23
39
  export function createAgentsMethods(options = {}) {
24
40
  const {
25
41
  api,
@@ -681,6 +697,9 @@ export function createAgentsMethods(options = {}) {
681
697
  this.showMessage(res.error, 'error');
682
698
  return;
683
699
  }
700
+ if (typeof res.historyBucket === 'string' && res.historyBucket) {
701
+ this.promptHistoryBucket = res.historyBucket;
702
+ }
684
703
  const successLabel = this.agentsContext === 'openclaw-workspace'
685
704
  ? this.t('toast.agents.saved.workspace', { name: this.agentsWorkspaceFileName || '' }).replace(/:\s*$/, '')
686
705
  : (this.agentsContext === 'claude-project'
@@ -884,6 +903,77 @@ export function createAgentsMethods(options = {}) {
884
903
  await this.persistPromptPresets();
885
904
  this.showMessage(this.t('prompts.presets.toast.deleted'), 'success');
886
905
  },
906
+ async openPromptHistory() {
907
+ if (this.promptHistoryLoading) return;
908
+ const bucket = this.promptHistoryBucket || resolveAgentsHistoryBucket(this);
909
+ this.promptHistoryVisible = true;
910
+ this.promptHistoryError = '';
911
+ await this.loadPromptHistory(bucket);
912
+ },
913
+
914
+ async loadPromptHistory(bucketArg) {
915
+ if (this.promptHistoryLoading) return;
916
+ const bucket = typeof bucketArg === 'string' && bucketArg.trim()
917
+ ? bucketArg.trim()
918
+ : (this.promptHistoryBucket || resolveAgentsHistoryBucket(this));
919
+ this.promptHistoryBucket = bucket;
920
+ this.promptHistoryLoading = true;
921
+ this.promptHistoryError = '';
922
+ this.promptHistoryItems = [];
923
+ this.promptHistoryPreviewId = '';
924
+ this.promptHistoryPreviewContent = '';
925
+ try {
926
+ const res = await api('list-prompt-history', { bucket });
927
+ if (res && res.error) {
928
+ this.promptHistoryError = res.error;
929
+ return;
930
+ }
931
+ this.promptHistoryItems = Array.isArray(res) ? res : [];
932
+ } catch (e) {
933
+ this.promptHistoryError = this.t('toast.load.fail');
934
+ } finally {
935
+ this.promptHistoryLoading = false;
936
+ }
937
+ },
938
+
939
+ closePromptHistory() {
940
+ this.promptHistoryVisible = false;
941
+ this.promptHistoryPreviewId = '';
942
+ this.promptHistoryPreviewContent = '';
943
+ this.promptHistoryError = '';
944
+ },
945
+
946
+ async viewPromptHistoryItem(item) {
947
+ if (!item || !item.id) return;
948
+ if (this.promptHistoryLoading) return;
949
+ if (this.promptHistoryPreviewId === item.id && this.promptHistoryPreviewContent) return;
950
+ this.promptHistoryPreviewId = item.id;
951
+ this.promptHistoryPreviewContent = '';
952
+ try {
953
+ const res = await api('get-prompt-history', { bucket: this.promptHistoryBucket, id: item.id });
954
+ if (res && res.error) {
955
+ this.promptHistoryError = res.error;
956
+ this.promptHistoryPreviewId = '';
957
+ return;
958
+ }
959
+ this.promptHistoryPreviewContent = typeof res.content === 'string' ? res.content : '';
960
+ } catch (e) {
961
+ this.promptHistoryError = this.t('toast.load.fail');
962
+ this.promptHistoryPreviewId = '';
963
+ }
964
+ },
965
+
966
+ applyPromptHistoryToEditor() {
967
+ if (!this.promptHistoryPreviewContent) return;
968
+ if (this.agentsSaving || this.agentsDiffVisible) return;
969
+ this.agentsContent = this.promptHistoryPreviewContent;
970
+ if (typeof this.onAgentsContentInput === 'function') this.onAgentsContentInput();
971
+ this.promptHistoryVisible = false;
972
+ this.promptHistoryPreviewId = '';
973
+ this.promptHistoryPreviewContent = '';
974
+ this.showMessage(this.t('toast.history.restored'), 'success');
975
+ },
976
+
887
977
  switchPromptsSubTab(subTab) {
888
978
  const normalized = subTab === 'claude-project' || subTab === 'system' ? subTab : 'codex';
889
979
  if (normalized === 'claude-project' && !this.projectPathOptions.length && !this.projectPathOptionsLoading) {
@@ -22,6 +22,7 @@ import { createOpenclawEditingMethods } from './app.methods.openclaw-editing.mjs
22
22
  import { createOpenclawPersistMethods } from './app.methods.openclaw-persist.mjs';
23
23
  import { createOpencodeConfigMethods } from './app.methods.opencode-config.mjs';
24
24
  import { createKilocodeConfigMethods } from './app.methods.kilocode-config.mjs';
25
+ import { createPiConfigMethods } from './app.methods.pi-config.mjs';
25
26
  import { createProvidersMethods } from './app.methods.providers.mjs';
26
27
  import { createProviderCacheMethods } from './app.methods.provider-cache.mjs';
27
28
  import { createRuntimeMethods } from './app.methods.runtime.mjs';
@@ -102,6 +103,7 @@ export function createAppMethods() {
102
103
  modelCatalog: OPENCODE_MODEL_CATALOG
103
104
  }),
104
105
  ...createKilocodeConfigMethods({ api }),
106
+ ...createPiConfigMethods({ api }),
105
107
  ...createInstallMethods({ api }),
106
108
  ...createRuntimeMethods({ api })
107
109
  };
@@ -145,7 +145,13 @@
145
145
  if (typeof this.ensureMainTabSwitchState === 'function') {
146
146
  this.ensureMainTabSwitchState().pendingConfigMode = '';
147
147
  }
148
- this.configMode = configModeSet.has(normalizedMode) ? normalizedMode : 'codex';
148
+ const resolvedMode = configModeSet.has(normalizedMode) ? normalizedMode : 'codex';
149
+ if (typeof this.isConfigModeVisible === 'function' && !this.isConfigModeVisible(resolvedMode)) {
150
+ const fallback = ['codex', 'claude', 'openclaw', 'opencode', 'kilocode', 'pi'].find(m => this.isConfigModeVisible(m)) || 'codex';
151
+ this.configMode = configModeSet.has(fallback) ? fallback : 'codex';
152
+ } else {
153
+ this.configMode = resolvedMode;
154
+ }
149
155
  if (this.mainTab === 'config') {
150
156
  if (this.configMode === 'kilocode' && typeof this.loadKilocodeConfig === 'function') {
151
157
  this.loadKilocodeConfig();