codexmate 0.1.19 → 0.1.21

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/README.md CHANGED
@@ -63,7 +63,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
63
63
  - **Pi Provider Config**: Manage Pi providers from the Web UI with lossless round-trip writes to `~/.pi/agent/models.json`, auto-fetched remote model lists, and one-click default provider/model switching via `~/.pi/agent/settings.json`.
64
64
  - **Provider Health Cleanup**: Probe local Codex and Claude provider routes, surface failed configs in one modal, and bulk-clean selected broken providers without touching healthy or protected entries.
65
65
  - **Skills Marketplace**: A local-first market to share and import skills between different agent apps.
66
- - **Prompt File Editor**: Unified editor for global and project-level `CLAUDE.md` and `AGENTS.md` with auto-detection of project paths, plus a shared preset pool for reusable prompts.
66
+ - **Prompt File Editor**: Unified editor for global and project-level `CLAUDE.md` and `AGENTS.md` (including OpenCode's `~/.config/opencode/AGENTS.md`) with auto-detection of project paths, plus a shared preset pool for reusable prompts.
67
67
  - **Task Orchestrator**: Plan and execute complex tasks with dependency tracking.
68
68
 
69
69
  ---
package/README.vi.md CHANGED
@@ -69,7 +69,7 @@ Khác với các wrapper đơn giản, Codex Mate hoạt động như một **Ag
69
69
  | **Bridge OpenAI** | ✅ | Chuyển đổi Codex Responses API sang định dạng OpenAI chuẩn |
70
70
  | **Kiểm tra sức khỏe Provider** | ✅ | Kiểm tra route provider Codex/Claude, đánh dấu cấu hình lỗi và xóa hàng loạt các provider hỏng đã chọn một cách an toàn |
71
71
  | **Mẫu Prompt** | ✅ | Plugin prompt tái sử dụng được với hỗ trợ biến |
72
- | **Trình sửa tệp prompt** | ✅ | Chỉnh sửa CLAUDE.md / AGENTS.md và dùng kho preset chung. Áp dụng preset chỉ cập nhật editor; cần lưu thủ công để ghi vào tệp. |
72
+ | **Trình sửa tệp prompt** | ✅ | Chỉnh sửa CLAUDE.md / AGENTS.md (bao gồm `~/.config/opencode/AGENTS.md` của OpenCode) và dùng kho preset chung. Áp dụng preset chỉ cập nhật editor; cần lưu thủ công để ghi vào tệp. |
73
73
  | **Tích hợp MCP** | ✅ | Expose tool và resource cục bộ qua MCP stdio |
74
74
  | **Tự động cập nhật** | ✅ | Cập nhật nhanh qua `codexmate update` |
75
75
 
package/README.zh.md CHANGED
@@ -61,7 +61,7 @@
61
61
  - **Pi Provider 配置**:在 Web UI 中管理 Pi 供应商,写入 `~/.pi/agent/models.json` 时无损保留未知字段,支持自动拉取远端模型列表,并可一键切换 `~/.pi/agent/settings.json` 中的默认供应商/模型。
62
62
  - **Provider 健康清理**:探测本地 Codex 与 Claude provider 路由,在同一个弹窗里集中展示失败配置,并批量清理已选坏配置,不影响正常或受保护项。
63
63
  - **Skills 市场**:本地优先的市场,支持在不同的智能体应用之间共享和导入 Skills。
64
- - **提示词文件编辑器**:统一编辑全局和项目级 `CLAUDE.md` 与 `AGENTS.md`,支持项目路径自动检测,并提供通用预设池复用 prompt。
64
+ - **提示词文件编辑器**:统一编辑全局和项目级 `CLAUDE.md` 与 `AGENTS.md`(含 OpenCode 的 `~/.config/opencode/AGENTS.md`),支持项目路径自动检测,并提供通用预设池复用 prompt。
65
65
  - **任务编排器**:支持带有依赖跟踪的复杂任务规划与执行。
66
66
 
67
67
  ---
@@ -15,6 +15,7 @@ function createAgentsFileController(deps = {}) {
15
15
  CLAUDE_MD_FILE_NAME,
16
16
  readOpenclawAgentsFile,
17
17
  readOpenclawWorkspaceFile,
18
+ OPENCODE_CONFIG_DIR,
18
19
  backupPromptBeforeWrite
19
20
  } = deps;
20
21
 
@@ -33,6 +34,7 @@ function createAgentsFileController(deps = {}) {
33
34
  if (typeof CLAUDE_MD_FILE_NAME !== 'string' || !CLAUDE_MD_FILE_NAME) throw new Error('createAgentsFileController 缺少 CLAUDE_MD_FILE_NAME');
34
35
  if (typeof readOpenclawAgentsFile !== 'function') throw new Error('createAgentsFileController 缺少 readOpenclawAgentsFile');
35
36
  if (typeof readOpenclawWorkspaceFile !== 'function') throw new Error('createAgentsFileController 缺少 readOpenclawWorkspaceFile');
37
+ if (typeof OPENCODE_CONFIG_DIR !== 'string' || !OPENCODE_CONFIG_DIR) throw new Error('createAgentsFileController 缺少 OPENCODE_CONFIG_DIR');
36
38
  if (typeof backupPromptBeforeWrite !== 'function' && typeof backupPromptBeforeWrite !== 'undefined') throw new Error('createAgentsFileController 备份回调无效');
37
39
 
38
40
  function resolveAgentsFilePath(params = {}) {
@@ -236,7 +238,9 @@ function createAgentsFileController(deps = {}) {
236
238
  }
237
239
  let agentsHistoryBucket = '';
238
240
  if (typeof backupPromptBeforeWrite === 'function') {
239
- const bucket = 'codex_' + sanitizeHistoryId(String(params.baseDir || '').trim() || 'global');
241
+ const explicitBucket = typeof params.historyBucket === 'string' ? params.historyBucket.trim() : '';
242
+ const bucket = explicitBucket
243
+ || ('codex_' + sanitizeHistoryId(String(params.baseDir || '').trim() || 'global'));
240
244
  backupPromptBeforeWrite(bucket, filePath);
241
245
  agentsHistoryBucket = bucket;
242
246
  }
@@ -254,6 +258,43 @@ function createAgentsFileController(deps = {}) {
254
258
  }
255
259
  }
256
260
 
261
+
262
+ function readOpencodeAgentsFile(params = {}) {
263
+ const baseDir = OPENCODE_CONFIG_DIR;
264
+ const filePath = path.join(baseDir, AGENTS_FILE_NAME);
265
+ const lineEndingFallback = os.EOL === '\r\n' ? '\r\n' : '\n';
266
+
267
+ if (!fs.existsSync(baseDir)) {
268
+ return {
269
+ exists: false,
270
+ path: filePath,
271
+ content: '',
272
+ lineEnding: lineEndingFallback,
273
+ configDir: baseDir,
274
+ baseDirMissing: true
275
+ };
276
+ }
277
+
278
+ const readResult = readAgentsFile({ baseDir, metaOnly: !!params.metaOnly });
279
+ return {
280
+ ...readResult,
281
+ configDir: baseDir
282
+ };
283
+ }
284
+
285
+ function applyOpencodeAgentsFile(params = {}) {
286
+ const baseDir = OPENCODE_CONFIG_DIR;
287
+ ensureDir(baseDir);
288
+ const result = applyAgentsFile({
289
+ ...params,
290
+ baseDir,
291
+ historyBucket: 'opencode_global'
292
+ });
293
+ return {
294
+ ...result,
295
+ configDir: baseDir
296
+ };
297
+ }
257
298
  function normalizeDiffText(input) {
258
299
  const safe = typeof input === 'string' ? input : '';
259
300
  return normalizeLineEnding(stripUtf8Bom(safe), '\n');
@@ -273,6 +314,8 @@ function createAgentsFileController(deps = {}) {
273
314
  readResult = readOpenclawAgentsFile({ metaOnly });
274
315
  } else if (context === 'openclaw-workspace') {
275
316
  readResult = readOpenclawWorkspaceFile({ ...params, metaOnly });
317
+ } else if (context === 'opencode') {
318
+ readResult = readOpencodeAgentsFile({ metaOnly });
276
319
  } else if (context === 'codex') {
277
320
  readResult = readAgentsFile({ ...params, metaOnly });
278
321
  } else {
@@ -310,6 +353,8 @@ function createAgentsFileController(deps = {}) {
310
353
  applyClaudeMdFile,
311
354
  readAgentsFile,
312
355
  applyAgentsFile,
356
+ readOpencodeAgentsFile,
357
+ applyOpencodeAgentsFile,
313
358
  normalizeDiffText,
314
359
  buildAgentsDiff
315
360
  };
package/cli.js CHANGED
@@ -1190,7 +1190,8 @@ function getApiToolConfigWriteTarget(action) {
1190
1190
  ]);
1191
1191
  const opencodeWriteActions = new Set([
1192
1192
  'apply-opencode-config',
1193
- 'update-opencode-selection'
1193
+ 'update-opencode-selection',
1194
+ 'apply-opencode-agents-file'
1194
1195
  ]);
1195
1196
  const kilocodeWriteActions = new Set([
1196
1197
  'apply-kilocode-config',
@@ -2372,6 +2373,8 @@ const {
2372
2373
  applyClaudeMdFile,
2373
2374
  readAgentsFile,
2374
2375
  applyAgentsFile,
2376
+ readOpencodeAgentsFile,
2377
+ applyOpencodeAgentsFile,
2375
2378
  normalizeDiffText,
2376
2379
  buildAgentsDiff
2377
2380
  } = createAgentsFileController({
@@ -2394,7 +2397,8 @@ const {
2394
2397
  },
2395
2398
  readOpenclawWorkspaceFile() {
2396
2399
  return readOpenclawWorkspaceFile(...arguments);
2397
- }
2400
+ },
2401
+ OPENCODE_CONFIG_DIR
2398
2402
  });
2399
2403
 
2400
2404
  const {
@@ -13694,6 +13698,12 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
13694
13698
  case 'get-openclaw-agents-file':
13695
13699
  result = readOpenclawAgentsFile();
13696
13700
  break;
13701
+ case 'get-opencode-agents-file':
13702
+ result = readOpencodeAgentsFile(params || {});
13703
+ break;
13704
+ case 'apply-opencode-agents-file':
13705
+ result = applyOpencodeAgentsFile(params || {});
13706
+ break;
13697
13707
  case 'apply-openclaw-agents-file':
13698
13708
  result = applyOpenclawAgentsFile(params || {});
13699
13709
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -33,6 +33,9 @@ function resolveAgentsHistoryBucket(instance) {
33
33
  const projectPath = (instance.projectClaudeMdPath || '').trim();
34
34
  return 'claude-project_' + sanitizePromptHistoryId(projectPath || 'global');
35
35
  }
36
+ if (subTab === 'opencode') {
37
+ return 'opencode_global';
38
+ }
36
39
  return 'codex_global';
37
40
  }
38
41
 
@@ -216,6 +219,13 @@ export function createAgentsMethods(options = {}) {
216
219
  this.agentsModalHint = tr('modal.agents.hint.openclawWorkspaceFile', `Workspace / ${fileName}`, { fileName });
217
220
  return;
218
221
  }
222
+ if (context === 'opencode') {
223
+ this.agentsContext = 'opencode';
224
+ this.agentsWorkspaceFileName = '';
225
+ this.agentsModalTitle = tr('modal.agents.title.opencode', 'OpenCode AGENTS.md 编辑器');
226
+ this.agentsModalHint = tr('modal.agents.hint.opencode', '保存后会写入 ~/.config/opencode/AGENTS.md。');
227
+ return;
228
+ }
219
229
  this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
220
230
  if (this.agentsContext === 'openclaw') {
221
231
  this.agentsModalTitle = tr('modal.agents.title.openclaw', 'OpenClaw AGENTS.md 编辑器');
@@ -700,6 +710,8 @@ export function createAgentsMethods(options = {}) {
700
710
  }
701
711
  } else if (this.agentsContext === 'openclaw') {
702
712
  action = 'apply-openclaw-agents-file';
713
+ } else if (this.agentsContext === 'opencode') {
714
+ action = 'apply-opencode-agents-file';
703
715
  } else if (this.agentsContext === 'openclaw-workspace') {
704
716
  action = 'apply-openclaw-workspace-file';
705
717
  params.fileName = this.agentsWorkspaceFileName;
@@ -716,7 +728,11 @@ export function createAgentsMethods(options = {}) {
716
728
  ? this.t('toast.agents.saved.workspace', { name: this.agentsWorkspaceFileName || '' }).replace(/:\s*$/, '')
717
729
  : (this.agentsContext === 'claude-project'
718
730
  ? this.t('toast.agents.saved.claudeProject')
719
- : (this.agentsContext === 'openclaw' ? this.t('toast.agents.saved.openclaw') : this.t('toast.agents.saved.agents')));
731
+ : (this.agentsContext === 'openclaw'
732
+ ? this.t('toast.agents.saved.openclaw')
733
+ : (this.agentsContext === 'opencode'
734
+ ? this.t('toast.agents.saved.opencode')
735
+ : this.t('toast.agents.saved.agents'))));
720
736
  this.showMessage(successLabel, 'success');
721
737
  if (this.mainTab === 'prompts') {
722
738
  this.loadPromptsContent();
@@ -987,7 +1003,7 @@ export function createAgentsMethods(options = {}) {
987
1003
  },
988
1004
 
989
1005
  switchPromptsSubTab(subTab) {
990
- const normalized = subTab === 'claude-project' || subTab === 'system' ? subTab : 'codex';
1006
+ const normalized = subTab === 'claude-project' || subTab === 'system' || subTab === 'opencode' ? subTab : 'codex';
991
1007
  if (normalized === 'claude-project' && !this.projectPathOptions.length && !this.projectPathOptionsLoading) {
992
1008
  this.loadProjectPathOptions();
993
1009
  }
@@ -1035,6 +1051,8 @@ export function createAgentsMethods(options = {}) {
1035
1051
  if (projectPath) {
1036
1052
  rpcParams.baseDir = projectPath;
1037
1053
  }
1054
+ } else if (subTab === 'opencode') {
1055
+ action = 'get-opencode-agents-file';
1038
1056
  } else {
1039
1057
  action = 'get-agents-file';
1040
1058
  }
@@ -1051,7 +1069,7 @@ export function createAgentsMethods(options = {}) {
1051
1069
  this.agentsPath = res.path || '';
1052
1070
  this.agentsExists = !!res.exists;
1053
1071
  this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
1054
- this.agentsContext = subTab === 'claude-project' ? 'claude-project' : 'codex';
1072
+ this.agentsContext = subTab === 'claude-project' ? 'claude-project' : (subTab === 'opencode' ? 'opencode' : 'codex');
1055
1073
  } catch (e) {
1056
1074
  if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
1057
1075
  return;
@@ -732,8 +732,8 @@
732
732
  this.invalidateSessionTimelineMeasurementCache();
733
733
  },
734
734
 
735
- async loadMoreSessionMessages() {
736
- return loadMoreSessionMessagesHelper.call(this);
735
+ async loadMoreSessionMessages(stepSize) {
736
+ return loadMoreSessionMessagesHelper.call(this, stepSize);
737
737
  },
738
738
 
739
739
  suspendSessionTabRender() {
@@ -55,6 +55,7 @@ export function createPiConfigMethods({ api: apiClient }) {
55
55
  : '';
56
56
  const updates = { defaultProvider: providerId };
57
57
  if (firstModel) updates.defaultModel = firstModel;
58
+ else updates.defaultModel = '';
58
59
  try {
59
60
  const result = await apiClient('write-pi-settings', updates);
60
61
  if (result && result.error) throw new Error(result.error);
@@ -126,6 +127,11 @@ export function createPiConfigMethods({ api: apiClient }) {
126
127
  this.messageType = 'error';
127
128
  return;
128
129
  }
130
+ if (!Array.isArray(merged.models) || merged.models.length === 0) {
131
+ this.message = '请至少导入或输入一个模型';
132
+ this.messageType = 'error';
133
+ return;
134
+ }
129
135
  const providerId = isNew ? this.derivePiProviderId(baseUrl) : editor.id;
130
136
  if (isNew) {
131
137
  merged.id = providerId;
@@ -207,6 +213,7 @@ export function createPiConfigMethods({ api: apiClient }) {
207
213
  startPiProviderDraft(tpl) {
208
214
  const template = isPiPlainObject(tpl) ? tpl : {};
209
215
  this.resetPiEditorTransientState();
216
+ this.showAddPiProviderModal = false;
210
217
  this.editingPiProvider = {
211
218
  id: '',
212
219
  isNew: true,
@@ -350,6 +357,8 @@ export function createPiConfigMethods({ api: apiClient }) {
350
357
  const wasNew = !!editor.isNew;
351
358
  this.resetPiProviderEditing();
352
359
  if (wasNew) this.piSelectedProviderTemplate = '';
360
+ this.showAddPiProviderModal = false;
361
+ this.piProviderPickerQuery = '';
353
362
  },
354
363
  removePiProviderModel(index) {
355
364
  if (!this.editingPiProvider) return;
@@ -544,7 +553,7 @@ export function createPiConfigMethods({ api: apiClient }) {
544
553
  if (merged.baseUrl === undefined) merged.baseUrl = values.baseUrl || '';
545
554
  if (merged.api === undefined) merged.api = values.api || '';
546
555
  if (merged.apiKey === undefined) merged.apiKey = values.apiKey || '';
547
- if (merged.models === undefined) merged.models = mapModels(values.models);
556
+ merged.models = mapModels(merged.models !== undefined ? merged.models : values.models);
548
557
  if (isPiPlainObject(orig.headers)) merged.headers = orig.headers;
549
558
  if (typeof orig.title === 'string') merged.title = orig.title;
550
559
  else if (typeof orig.name === 'string') merged.name = orig.name;
@@ -63,6 +63,7 @@ function normalizePromptsSubTab(value) {
63
63
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
64
64
  if (normalized === 'claude-project') return normalized;
65
65
  if (normalized === 'system') return normalized;
66
+ if (normalized === 'opencode') return normalized;
66
67
  return 'codex';
67
68
  }
68
69
 
@@ -229,6 +229,7 @@ const en = Object.freeze({
229
229
  'subtitle.settings': 'Manage downloads, directories, and trash.',
230
230
  'subtitle.prompts': 'Edit AGENTS.md and CLAUDE.md.',
231
231
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
232
+ 'prompts.subTab.opencode': 'AGENTS.md (OpenCode)',
232
233
  'prompts.subTab.project': 'CLAUDE.md',
233
234
  'prompts.subTab.system': 'System Prompt (Pi)',
234
235
  'prompts.subTab.presets': 'Preset pool',
@@ -439,6 +440,7 @@ const en = Object.freeze({
439
440
  'toast.agents.saved.agents': 'AGENTS.md saved',
440
441
  'toast.agents.saved.claudeProject': 'Project CLAUDE.md saved',
441
442
  'toast.agents.saved.openclaw': 'OpenClaw AGENTS.md saved',
443
+ 'toast.agents.saved.opencode': 'OpenCode AGENTS.md saved',
442
444
  'toast.agents.saved.workspace': 'Workspace file saved: {name}',
443
445
  'toast.delete.ok': 'Deleted',
444
446
  'toast.delete.fail': 'Delete failed',
@@ -961,6 +963,7 @@ const en = Object.freeze({
961
963
  'config.agents.open': 'Open AGENTS.md',
962
964
  'modal.agents.title.default': 'AGENTS.md editor',
963
965
  'modal.agents.title.openclaw': 'OpenClaw AGENTS.md editor',
966
+ 'modal.agents.title.opencode': 'OpenCode AGENTS.md editor',
964
967
  'modal.agents.hint.default': 'Saved content will be written to AGENTS.md (next to config.toml).',
965
968
  'modal.agents.title.claudeProject': 'Project CLAUDE.md: {path}',
966
969
  'modal.agents.hint.claudeProject': 'Saved content will be written to CLAUDE.md in: {path}',
@@ -969,6 +972,7 @@ const en = Object.freeze({
969
972
  'modal.agents.contentLabel.claudeProject': 'CLAUDE.md content',
970
973
  'modal.agents.placeholder.claudeProject': 'Edit CLAUDE.md here',
971
974
  'modal.agents.hint.openclaw': 'Saved content will be written to OpenClaw workspace AGENTS.md.',
975
+ 'modal.agents.hint.opencode': 'Saved content will be written to ~/.config/opencode/AGENTS.md.',
972
976
  'modal.agents.title.openclawWorkspaceFile': 'OpenClaw workspace file: {fileName}',
973
977
  'modal.agents.hint.openclawWorkspaceFile': 'Saved content will be written to OpenClaw workspace {fileName}.',
974
978
  'config.url.unset': 'URL not set',
@@ -1362,9 +1366,9 @@ const en = Object.freeze({
1362
1366
  'pi.providers.json.previewTitle': 'Preview',
1363
1367
  'pi.providers.json.editTitle': 'Edit JSON',
1364
1368
  'pi.files.title': 'File JSON',
1365
- 'pi.files.desc': 'View and edit Pi settings.json and models.json in full; JSON validity is checked before saving and invalid content is never written.',
1366
1369
  'pi.files.settingsTitle': 'settings.json (active settings)',
1367
1370
  'pi.files.modelsTitle': 'models.json (providers and models)',
1371
+ 'pi.files.modelsHint': 'Merged automatically from the provider management above; prefer editing providers there instead.',
1368
1372
  'pi.history.apply': 'Apply this record',
1369
1373
  'pi.history.applied': 'History record applied',
1370
1374
  'pi.history.applyFailed': 'Failed to apply history record',
@@ -230,6 +230,7 @@ const ja = Object.freeze({
230
230
  'subtitle.settings': 'ダウンロード・ディレクトリ・ゴミ箱の管理。',
231
231
  'subtitle.prompts': 'AGENTS.md と CLAUDE.md を編集。',
232
232
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
233
+ 'prompts.subTab.opencode': 'AGENTS.md (OpenCode)',
233
234
  'prompts.subTab.project': 'CLAUDE.md',
234
235
  'prompts.subTab.system': 'System Prompt (Pi)',
235
236
  'prompts.subTab.presets': 'プリセット',
@@ -441,6 +442,7 @@ const ja = Object.freeze({
441
442
  'toast.agents.saved.agents': 'AGENTS.md 保存しました',
442
443
  'toast.agents.saved.claudeProject': 'プロジェクト CLAUDE.md 保存しました',
443
444
  'toast.agents.saved.openclaw': 'OpenClaw AGENTS.md 保存しました',
445
+ 'toast.agents.saved.opencode': 'OpenCode AGENTS.md 保存しました',
444
446
  'toast.agents.saved.workspace': 'ワークスペースファイル保存済み: {name}',
445
447
  'toast.delete.ok': '削除しました',
446
448
  'toast.delete.fail': '削除に失敗しました',
@@ -964,6 +966,7 @@ const ja = Object.freeze({
964
966
  'config.agents.open': 'AGENTS.md を開く',
965
967
  'modal.agents.title.default': 'AGENTS.md エディタ',
966
968
  'modal.agents.title.openclaw': 'OpenClaw AGENTS.md エディタ',
969
+ 'modal.agents.title.opencode': 'OpenCode AGENTS.md エディタ',
967
970
  'modal.agents.hint.default': '保存すると対象の AGENTS.md(config.toml と同階層)に書き込まれます。',
968
971
  'modal.agents.title.claudeProject': 'プロジェクト CLAUDE.md: {path}',
969
972
  'modal.agents.hint.claudeProject': '保存するとプロジェクトの CLAUDE.md に書き込まれます: {path}',
@@ -972,6 +975,7 @@ const ja = Object.freeze({
972
975
  'modal.agents.contentLabel.claudeProject': 'CLAUDE.md 内容',
973
976
  'modal.agents.placeholder.claudeProject': 'CLAUDE.md を編集',
974
977
  'modal.agents.hint.openclaw': '保存すると OpenClaw Workspace の AGENTS.md に書き込まれます。',
978
+ 'modal.agents.hint.opencode': '保存すると ~/.config/opencode/AGENTS.md に書き込まれます。',
975
979
  'modal.agents.title.openclawWorkspaceFile': 'OpenClaw ワークスペースファイル: {fileName}',
976
980
  'modal.agents.hint.openclawWorkspaceFile': '保存すると OpenClaw Workspace の {fileName} に書き込まれます。',
977
981
  'config.url.unset': 'URL 未設定',
@@ -1363,9 +1367,9 @@ const ja = Object.freeze({
1363
1367
  'pi.providers.json.previewTitle': 'プレビュー',
1364
1368
  'pi.providers.json.editTitle': 'JSON を編集',
1365
1369
  'pi.files.title': 'ファイル JSON',
1366
- 'pi.files.desc': 'Pi の settings.json と models.json を全文表示して直接編集します。保存前に JSON の正当性を検証し、不正な内容は書き込みません。',
1367
1370
  'pi.files.settingsTitle': 'settings.json(有効な設定)',
1368
1371
  'pi.files.modelsTitle': 'models.json(プロバイダーとモデル)',
1372
+ 'pi.files.modelsHint': '上のプロバイダー管理から自動的にマージされます。プロバイダーの変更は上の管理画面を優先してください。',
1369
1373
  'pi.history.apply': 'この履歴を適用',
1370
1374
  'pi.history.applied': '履歴を適用しました',
1371
1375
  'pi.history.applyFailed': '履歴の適用に失敗しました',
@@ -247,6 +247,7 @@ const vi = Object.freeze({
247
247
  'subtitle.settings': 'Quản lý tải xuống, thư mục và dữ liệu.',
248
248
  'subtitle.prompts': 'Chỉnh sửa AGENTS.md và CLAUDE.md.',
249
249
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
250
+ 'prompts.subTab.opencode': 'AGENTS.md (OpenCode)',
250
251
  'prompts.subTab.project': 'CLAUDE.md',
251
252
  'prompts.subTab.system': 'System Prompt (Pi)',
252
253
  'prompts.subTab.presets': 'Kho preset',
@@ -401,6 +402,7 @@ const vi = Object.freeze({
401
402
  'toast.agents.saved.agents': 'AGENTS.md đã lưu',
402
403
  'toast.agents.saved.claudeProject': 'CLAUDE.md dự án đã lưu',
403
404
  'toast.agents.saved.openclaw': 'OpenClaw AGENTS.md đã lưu',
405
+ 'toast.agents.saved.opencode': 'OpenCode AGENTS.md đã lưu',
404
406
  'toast.agents.saved.workspace': 'Tệp workspace đã lưu: {name}',
405
407
  'toast.delete.ok': 'Đã xóa',
406
408
  'toast.delete.fail': 'Xóa thất bại',
@@ -1073,8 +1075,10 @@ const vi = Object.freeze({
1073
1075
  'config.agents.open': 'Mở AGENTS.md',
1074
1076
  'modal.agents.title.default': 'Trình soạn thảo AGENTS.md',
1075
1077
  'modal.agents.title.openclaw': 'Trình soạn thảo AGENTS.md OpenClaw',
1078
+ 'modal.agents.title.opencode': 'Trình soạn thảo AGENTS.md OpenCode',
1076
1079
  'modal.agents.hint.default': 'Nội dung đã lưu sẽ được ghi vào AGENTS.md (cạnh config.toml).',
1077
1080
  'modal.agents.hint.openclaw': 'Nội dung đã lưu sẽ được ghi vào AGENTS.md không gian làm việc OpenClaw.',
1081
+ 'modal.agents.hint.opencode': 'Nội dung đã lưu sẽ được ghi vào ~/.config/opencode/AGENTS.md.',
1078
1082
  'modal.agents.title.openclawWorkspaceFile': 'File không gian làm việc OpenClaw: {fileName}',
1079
1083
  'modal.agents.hint.openclawWorkspaceFile': 'Nội dung đã lưu sẽ được ghi vào OpenClaw {fileName}.',
1080
1084
  'config.url.unset': 'URL chưa đặt',
@@ -1331,9 +1335,9 @@ const vi = Object.freeze({
1331
1335
  'pi.providers.json.previewTitle': 'Xem trước',
1332
1336
  'pi.providers.json.editTitle': 'Chỉnh sửa JSON',
1333
1337
  'pi.files.title': 'JSON tập tin',
1334
- 'pi.files.desc': 'Hiển thị đầy đủ và chỉnh sửa trực tiếp settings.json và models.json của Pi; kiểm tra tính hợp lệ của JSON trước khi lưu, nội dung không hợp lệ sẽ không được ghi.',
1335
1338
  'pi.files.settingsTitle': 'settings.json (cài đặt đang dùng)',
1336
1339
  'pi.files.modelsTitle': 'models.json (nhà cung cấp và mô hình)',
1340
+ 'pi.files.modelsHint': 'Được hợp nhất tự động từ phần quản lý nhà cung cấp phía trên; hãy ưu tiên chỉnh sửa ở đó.',
1337
1341
  'pi.history.apply': 'Áp dụng bản ghi này',
1338
1342
  'pi.history.applied': 'Đã áp dụng lịch sử',
1339
1343
  'pi.history.applyFailed': 'Áp dụng lịch sử thất bại',
@@ -229,6 +229,7 @@ const zhTw = Object.freeze({
229
229
  'subtitle.settings': '管理下載、目錄與回收站。',
230
230
  'subtitle.prompts': '編輯 AGENTS.md 與 CLAUDE.md。',
231
231
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
232
+ 'prompts.subTab.opencode': 'AGENTS.md (OpenCode)',
232
233
  'prompts.subTab.project': 'CLAUDE.md',
233
234
  'prompts.subTab.system': 'System Prompt (Pi)',
234
235
  'prompts.subTab.presets': '預設池',
@@ -439,6 +440,7 @@ const zhTw = Object.freeze({
439
440
  'toast.agents.saved.agents': 'AGENTS.md 已保存',
440
441
  'toast.agents.saved.claudeProject': '專案 CLAUDE.md 已保存',
441
442
  'toast.agents.saved.openclaw': 'OpenClaw AGENTS.md 已保存',
443
+ 'toast.agents.saved.opencode': 'OpenCode AGENTS.md 已保存',
442
444
  'toast.agents.saved.workspace': '工作區文件已保存: {name}',
443
445
  'toast.delete.ok': '已刪除',
444
446
  'toast.delete.fail': '刪除失敗',
@@ -960,6 +962,7 @@ const zhTw = Object.freeze({
960
962
  'config.agents.open': '打開 AGENTS.md',
961
963
  'modal.agents.title.default': 'AGENTS.md 編輯器',
962
964
  'modal.agents.title.openclaw': 'OpenClaw AGENTS.md 編輯器',
965
+ 'modal.agents.title.opencode': 'OpenCode AGENTS.md 編輯器',
963
966
  'modal.agents.hint.default': '保存後會寫入目標 AGENTS.md(與 config.toml 同級)。',
964
967
  'modal.agents.title.claudeProject': '專案 CLAUDE.md: {path}',
965
968
  'modal.agents.hint.claudeProject': '保存後會寫入專案目錄下的 CLAUDE.md: {path}',
@@ -968,6 +971,7 @@ const zhTw = Object.freeze({
968
971
  'modal.agents.contentLabel.claudeProject': 'CLAUDE.md 內容',
969
972
  'modal.agents.placeholder.claudeProject': '在這裡編輯 CLAUDE.md',
970
973
  'modal.agents.hint.openclaw': '保存後會寫入 OpenClaw Workspace 下的 AGENTS.md。',
974
+ 'modal.agents.hint.opencode': '儲存後會寫入 ~/.config/opencode/AGENTS.md。',
971
975
  'modal.agents.title.openclawWorkspaceFile': 'OpenClaw 工作區文件: {fileName}',
972
976
  'modal.agents.hint.openclawWorkspaceFile': '保存後會寫入 OpenClaw Workspace 下的 {fileName}。',
973
977
  'config.url.unset': '未設 URL',
@@ -1360,9 +1364,9 @@ const zhTw = Object.freeze({
1360
1364
  'pi.providers.json.previewTitle': '預覽',
1361
1365
  'pi.providers.json.editTitle': '編輯 JSON',
1362
1366
  'pi.files.title': '檔案 JSON',
1363
- 'pi.files.desc': '完整展示並直接編輯 Pi 的 settings.json 與 models.json;儲存前校驗 JSON 合法性,非法內容不會寫入。',
1364
1367
  'pi.files.settingsTitle': 'settings.json(生效設定)',
1365
1368
  'pi.files.modelsTitle': 'models.json(供應商與模型)',
1369
+ 'pi.files.modelsHint': '由上方供應商管理自動合併生成;調整供應商請優先使用上方的供應商管理。',
1366
1370
  'pi.history.apply': '套用此紀錄',
1367
1371
  'pi.history.applied': '已套用歷史紀錄',
1368
1372
  'pi.history.applyFailed': '套用歷史紀錄失敗',
@@ -229,6 +229,7 @@ const zh = Object.freeze({
229
229
  'subtitle.settings': '管理下载、目录与回收站。',
230
230
  'subtitle.prompts': '编辑 AGENTS.md 与 CLAUDE.md。',
231
231
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
232
+ 'prompts.subTab.opencode': 'AGENTS.md (OpenCode)',
232
233
  'prompts.subTab.project': 'CLAUDE.md',
233
234
  'prompts.subTab.system': 'System Prompt (Pi)',
234
235
  'prompts.subTab.presets': '预设池',
@@ -439,6 +440,7 @@ const zh = Object.freeze({
439
440
  'toast.agents.saved.agents': 'AGENTS.md 已保存',
440
441
  'toast.agents.saved.claudeProject': '项目 CLAUDE.md 已保存',
441
442
  'toast.agents.saved.openclaw': 'OpenClaw AGENTS.md 已保存',
443
+ 'toast.agents.saved.opencode': 'OpenCode AGENTS.md 已保存',
442
444
  'toast.agents.saved.workspace': '工作区文件已保存: {name}',
443
445
  'toast.delete.ok': '已删除',
444
446
  'toast.delete.fail': '删除失败',
@@ -960,6 +962,7 @@ const zh = Object.freeze({
960
962
  'config.agents.open': '打开 AGENTS.md',
961
963
  'modal.agents.title.default': 'AGENTS.md 编辑器',
962
964
  'modal.agents.title.openclaw': 'OpenClaw AGENTS.md 编辑器',
965
+ 'modal.agents.title.opencode': 'OpenCode AGENTS.md 编辑器',
963
966
  'modal.agents.hint.default': '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
964
967
  'modal.agents.title.claudeProject': '项目 CLAUDE.md: {path}',
965
968
  'modal.agents.hint.claudeProject': '保存后会写入项目目录下的 CLAUDE.md: {path}',
@@ -968,6 +971,7 @@ const zh = Object.freeze({
968
971
  'modal.agents.contentLabel.claudeProject': 'CLAUDE.md 内容',
969
972
  'modal.agents.placeholder.claudeProject': '在这里编辑 CLAUDE.md',
970
973
  'modal.agents.hint.openclaw': 'Workspace / AGENTS.md',
974
+ 'modal.agents.hint.opencode': '保存后会写入 ~/.config/opencode/AGENTS.md。',
971
975
  'modal.agents.title.openclawWorkspaceFile': 'OpenClaw 工作区文件: {fileName}',
972
976
  'modal.agents.hint.openclawWorkspaceFile': 'Workspace / {fileName}',
973
977
  'config.url.unset': '未设 URL',
@@ -1360,9 +1364,9 @@ const zh = Object.freeze({
1360
1364
  'pi.providers.json.previewTitle': '预览',
1361
1365
  'pi.providers.json.editTitle': '编辑 JSON',
1362
1366
  'pi.files.title': '文件 JSON',
1363
- 'pi.files.desc': '完整展示并直接编辑 Pi 的 settings.json 与 models.json;保存前校验 JSON 合法性,非法内容不会写入。',
1364
1367
  'pi.files.settingsTitle': 'settings.json(生效设置)',
1365
1368
  'pi.files.modelsTitle': 'models.json(供应商与模型)',
1369
+ 'pi.files.modelsHint': '由上方供应商管理自动合并生成;调整供应商请优先使用上方的供应商管理。',
1366
1370
  'pi.history.apply': '应用此记录',
1367
1371
  'pi.history.applied': '已应用历史记录',
1368
1372
  'pi.history.applyFailed': '应用历史记录失败',
@@ -49,19 +49,24 @@
49
49
  </div>
50
50
  <div class="card-trailing">
51
51
  <span v-if="piActiveProvider === providerId" class="pill configured">{{ t('pi.providers.active') }}</span>
52
- <button v-else type="button" class="btn-tool" @click.stop="switchPiActiveProvider(providerId)" :disabled="!isToolConfigWriteAllowed('pi') || piSaving">{{ t('pi.providers.setActive') }}</button>
52
+ <button v-else type="button" class="btn-tool btn-tool-compact" @click.stop="switchPiActiveProvider(providerId)" :disabled="!isToolConfigWriteAllowed('pi') || piSaving">{{ t('pi.providers.setActive') }}</button>
53
53
  </div>
54
54
  </div>
55
55
  </div>
56
- <div v-if="!piProviderIds.length" class="config-template-hint">{{ t('pi.providers.empty') }}</div>
56
+ <div v-if="!piProviderIds.length" class="empty-state">
57
+ <div class="empty-state-title">{{ t('pi.providers.empty') }}</div>
58
+ </div>
57
59
  </section>
58
60
 
59
- <section class="selector-section">
61
+ <section class="selector-section pi-file-section">
60
62
  <div class="selector-header">
61
- <span class="selector-title">{{ t('pi.files.title') }}</span>
63
+ <span class="selector-title">{{ t('pi.files.settingsTitle') }}</span>
64
+ <div class="selector-actions">
65
+ <button type="button" class="btn-tool btn-tool-compact" @click="openPiConfigHistory('settings')" :disabled="piHistoryLoading">{{ t('common.history') }}</button>
66
+ <button type="button" class="btn-tool btn-tool-compact" @click="savePiSettingsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piSettingsJsonError">{{ t('common.save') }}</button>
67
+ </div>
62
68
  </div>
63
- <div class="config-template-hint">{{ t('pi.files.desc') }}</div>
64
- <label class="form-label">{{ t('pi.files.settingsTitle') }}</label>
69
+ <div class="config-template-hint">{{ t('pi.providers.targetFile', { path: '~/.pi/agent/settings.json' }) }}</div>
65
70
  <textarea
66
71
  class="template-textarea config-json-editor"
67
72
  v-model="piSettingsJsonDraft"
@@ -71,10 +76,6 @@
71
76
  @input="piSettingsJsonInput"
72
77
  placeholder="{}"></textarea>
73
78
  <p v-if="piSettingsJsonError" class="config-template-hint error-text">{{ piSettingsJsonError }}</p>
74
- <div class="settings-tab-actions">
75
- <button type="button" class="btn-tool" @click="savePiSettingsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piSettingsJsonError">{{ t('common.save') }}</button>
76
- <button type="button" class="btn-mini" @click="openPiConfigHistory('settings')" :disabled="piHistoryLoading">{{ t('common.history') }}</button>
77
- </div>
78
79
  <details v-if="piHistoryTarget === 'settings'" class="prompt-history-panel" open>
79
80
  <summary class="prompt-history-summary">
80
81
  <span>{{ t('common.history') }}</span>
@@ -96,7 +97,18 @@
96
97
  </div>
97
98
  </div>
98
99
  </details>
99
- <label class="form-label">{{ t('pi.files.modelsTitle') }}</label>
100
+ </section>
101
+
102
+ <section class="selector-section pi-file-section">
103
+ <div class="selector-header">
104
+ <span class="selector-title">{{ t('pi.files.modelsTitle') }}</span>
105
+ <div class="selector-actions">
106
+ <button type="button" class="btn-tool btn-tool-compact" @click="openPiConfigHistory('models')" :disabled="piHistoryLoading">{{ t('common.history') }}</button>
107
+ <button type="button" class="btn-tool btn-tool-compact" @click="savePiModelsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piModelsJsonError">{{ t('common.save') }}</button>
108
+ </div>
109
+ </div>
110
+ <div class="config-template-hint">{{ t('pi.files.modelsHint') }}</div>
111
+ <div class="config-template-hint">{{ t('pi.providers.targetFile', { path: '~/.pi/agent/models.json' }) }}</div>
100
112
  <textarea
101
113
  class="template-textarea config-json-editor"
102
114
  v-model="piModelsJsonDraft"
@@ -106,10 +118,6 @@
106
118
  @input="piModelsJsonInput"
107
119
  placeholder="{}"></textarea>
108
120
  <p v-if="piModelsJsonError" class="config-template-hint error-text">{{ piModelsJsonError }}</p>
109
- <div class="settings-tab-actions">
110
- <button type="button" class="btn-tool" @click="savePiModelsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piModelsJsonError">{{ t('common.save') }}</button>
111
- <button type="button" class="btn-mini" @click="openPiConfigHistory('models')" :disabled="piHistoryLoading">{{ t('common.history') }}</button>
112
- </div>
113
121
  <details v-if="piHistoryTarget === 'models'" class="prompt-history-panel" open>
114
122
  <summary class="prompt-history-summary">
115
123
  <span>{{ t('common.history') }}</span>
@@ -7,6 +7,7 @@
7
7
  aria-labelledby="tab-prompts">
8
8
  <div class="prompts-md-tabs" role="tablist" :aria-label="t('tab.prompts')">
9
9
  <button type="button" :class="['prompts-md-tab', { active: promptsSubTab === 'codex' }]" role="tab" :aria-selected="promptsSubTab === 'codex'" @click="switchPromptsSubTab('codex')">{{ t('prompts.subTab.codex') }}</button>
10
+ <button type="button" :class="['prompts-md-tab', { active: promptsSubTab === 'opencode' }]" role="tab" :aria-selected="promptsSubTab === 'opencode'" @click="switchPromptsSubTab('opencode')">{{ t('prompts.subTab.opencode') }}</button>
10
11
  <button type="button" :class="['prompts-md-tab', { active: promptsSubTab === 'claude-project' }]" role="tab" :aria-selected="promptsSubTab === 'claude-project'" @click="switchPromptsSubTab('claude-project')">{{ t('prompts.subTab.project') }}</button>
11
12
  <button type="button" :class="['prompts-md-tab', { active: promptsSubTab === 'system' }]" role="tab" :aria-selected="promptsSubTab === 'system'" @click="switchPromptsSubTab('system')">{{ t('prompts.subTab.system') }}</button>
12
13
  </div>
@@ -3268,7 +3268,7 @@ return function render(_ctx, _cache) {
3268
3268
  : (_openBlock(), _createElementBlock("button", {
3269
3269
  key: 1,
3270
3270
  type: "button",
3271
- class: "btn-tool",
3271
+ class: "btn-tool btn-tool-compact",
3272
3272
  onClick: _withModifiers($event => (_ctx.switchPiActiveProvider(providerId)), ["stop"]),
3273
3273
  disabled: !_ctx.isToolConfigWriteAllowed('pi') || _ctx.piSaving
3274
3274
  }, _toDisplayString(_ctx.t('pi.providers.setActive')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]))
@@ -3280,16 +3280,31 @@ return function render(_ctx, _cache) {
3280
3280
  (!_ctx.piProviderIds.length)
3281
3281
  ? (_openBlock(), _createElementBlock("div", {
3282
3282
  key: 1,
3283
- class: "config-template-hint"
3284
- }, _toDisplayString(_ctx.t('pi.providers.empty')), 1 /* TEXT */))
3283
+ class: "empty-state"
3284
+ }, [
3285
+ _createElementVNode("div", { class: "empty-state-title" }, _toDisplayString(_ctx.t('pi.providers.empty')), 1 /* TEXT */)
3286
+ ]))
3285
3287
  : _createCommentVNode("v-if", true)
3286
3288
  ]),
3287
- _createElementVNode("section", { class: "selector-section" }, [
3289
+ _createElementVNode("section", { class: "selector-section pi-file-section" }, [
3288
3290
  _createElementVNode("div", { class: "selector-header" }, [
3289
- _createElementVNode("span", { class: "selector-title" }, _toDisplayString(_ctx.t('pi.files.title')), 1 /* TEXT */)
3291
+ _createElementVNode("span", { class: "selector-title" }, _toDisplayString(_ctx.t('pi.files.settingsTitle')), 1 /* TEXT */),
3292
+ _createElementVNode("div", { class: "selector-actions" }, [
3293
+ _createElementVNode("button", {
3294
+ type: "button",
3295
+ class: "btn-tool btn-tool-compact",
3296
+ onClick: $event => (_ctx.openPiConfigHistory('settings')),
3297
+ disabled: _ctx.piHistoryLoading
3298
+ }, _toDisplayString(_ctx.t('common.history')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]),
3299
+ _createElementVNode("button", {
3300
+ type: "button",
3301
+ class: "btn-tool btn-tool-compact",
3302
+ onClick: _ctx.savePiSettingsJson,
3303
+ disabled: !_ctx.isToolConfigWriteAllowed('pi') || _ctx.piSaving || _ctx.piFileJsonSaving || !!_ctx.piSettingsJsonError
3304
+ }, _toDisplayString(_ctx.t('common.save')), 9 /* TEXT, PROPS */, ["onClick", "disabled"])
3305
+ ])
3290
3306
  ]),
3291
- _createElementVNode("div", { class: "config-template-hint" }, _toDisplayString(_ctx.t('pi.files.desc')), 1 /* TEXT */),
3292
- _createElementVNode("label", { class: "form-label" }, _toDisplayString(_ctx.t('pi.files.settingsTitle')), 1 /* TEXT */),
3307
+ _createElementVNode("div", { class: "config-template-hint" }, _toDisplayString(_ctx.t('pi.providers.targetFile', { path: '~/.pi/agent/settings.json' })), 1 /* TEXT */),
3293
3308
  _withDirectives(_createElementVNode("textarea", {
3294
3309
  class: "template-textarea config-json-editor",
3295
3310
  "onUpdate:modelValue": $event => ((_ctx.piSettingsJsonDraft) = $event),
@@ -3307,20 +3322,6 @@ return function render(_ctx, _cache) {
3307
3322
  class: "config-template-hint error-text"
3308
3323
  }, _toDisplayString(_ctx.piSettingsJsonError), 1 /* TEXT */))
3309
3324
  : _createCommentVNode("v-if", true),
3310
- _createElementVNode("div", { class: "settings-tab-actions" }, [
3311
- _createElementVNode("button", {
3312
- type: "button",
3313
- class: "btn-tool",
3314
- onClick: _ctx.savePiSettingsJson,
3315
- disabled: !_ctx.isToolConfigWriteAllowed('pi') || _ctx.piSaving || _ctx.piFileJsonSaving || !!_ctx.piSettingsJsonError
3316
- }, _toDisplayString(_ctx.t('common.save')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]),
3317
- _createElementVNode("button", {
3318
- type: "button",
3319
- class: "btn-mini",
3320
- onClick: $event => (_ctx.openPiConfigHistory('settings')),
3321
- disabled: _ctx.piHistoryLoading
3322
- }, _toDisplayString(_ctx.t('common.history')), 9 /* TEXT, PROPS */, ["onClick", "disabled"])
3323
- ]),
3324
3325
  (_ctx.piHistoryTarget === 'settings')
3325
3326
  ? (_openBlock(), _createElementBlock("details", {
3326
3327
  key: 1,
@@ -3386,8 +3387,28 @@ return function render(_ctx, _cache) {
3386
3387
  : _createCommentVNode("v-if", true)
3387
3388
  ])
3388
3389
  ]))
3389
- : _createCommentVNode("v-if", true),
3390
- _createElementVNode("label", { class: "form-label" }, _toDisplayString(_ctx.t('pi.files.modelsTitle')), 1 /* TEXT */),
3390
+ : _createCommentVNode("v-if", true)
3391
+ ]),
3392
+ _createElementVNode("section", { class: "selector-section pi-file-section" }, [
3393
+ _createElementVNode("div", { class: "selector-header" }, [
3394
+ _createElementVNode("span", { class: "selector-title" }, _toDisplayString(_ctx.t('pi.files.modelsTitle')), 1 /* TEXT */),
3395
+ _createElementVNode("div", { class: "selector-actions" }, [
3396
+ _createElementVNode("button", {
3397
+ type: "button",
3398
+ class: "btn-tool btn-tool-compact",
3399
+ onClick: $event => (_ctx.openPiConfigHistory('models')),
3400
+ disabled: _ctx.piHistoryLoading
3401
+ }, _toDisplayString(_ctx.t('common.history')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]),
3402
+ _createElementVNode("button", {
3403
+ type: "button",
3404
+ class: "btn-tool btn-tool-compact",
3405
+ onClick: _ctx.savePiModelsJson,
3406
+ disabled: !_ctx.isToolConfigWriteAllowed('pi') || _ctx.piSaving || _ctx.piFileJsonSaving || !!_ctx.piModelsJsonError
3407
+ }, _toDisplayString(_ctx.t('common.save')), 9 /* TEXT, PROPS */, ["onClick", "disabled"])
3408
+ ])
3409
+ ]),
3410
+ _createElementVNode("div", { class: "config-template-hint" }, _toDisplayString(_ctx.t('pi.files.modelsHint')), 1 /* TEXT */),
3411
+ _createElementVNode("div", { class: "config-template-hint" }, _toDisplayString(_ctx.t('pi.providers.targetFile', { path: '~/.pi/agent/models.json' })), 1 /* TEXT */),
3391
3412
  _withDirectives(_createElementVNode("textarea", {
3392
3413
  class: "template-textarea config-json-editor",
3393
3414
  "onUpdate:modelValue": $event => ((_ctx.piModelsJsonDraft) = $event),
@@ -3401,27 +3422,13 @@ return function render(_ctx, _cache) {
3401
3422
  ]),
3402
3423
  (_ctx.piModelsJsonError)
3403
3424
  ? (_openBlock(), _createElementBlock("p", {
3404
- key: 2,
3425
+ key: 0,
3405
3426
  class: "config-template-hint error-text"
3406
3427
  }, _toDisplayString(_ctx.piModelsJsonError), 1 /* TEXT */))
3407
3428
  : _createCommentVNode("v-if", true),
3408
- _createElementVNode("div", { class: "settings-tab-actions" }, [
3409
- _createElementVNode("button", {
3410
- type: "button",
3411
- class: "btn-tool",
3412
- onClick: _ctx.savePiModelsJson,
3413
- disabled: !_ctx.isToolConfigWriteAllowed('pi') || _ctx.piSaving || _ctx.piFileJsonSaving || !!_ctx.piModelsJsonError
3414
- }, _toDisplayString(_ctx.t('common.save')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]),
3415
- _createElementVNode("button", {
3416
- type: "button",
3417
- class: "btn-mini",
3418
- onClick: $event => (_ctx.openPiConfigHistory('models')),
3419
- disabled: _ctx.piHistoryLoading
3420
- }, _toDisplayString(_ctx.t('common.history')), 9 /* TEXT, PROPS */, ["onClick", "disabled"])
3421
- ]),
3422
3429
  (_ctx.piHistoryTarget === 'models')
3423
3430
  ? (_openBlock(), _createElementBlock("details", {
3424
- key: 3,
3431
+ key: 1,
3425
3432
  class: "prompt-history-panel",
3426
3433
  open: ""
3427
3434
  }, [
@@ -6875,6 +6882,13 @@ return function render(_ctx, _cache) {
6875
6882
  "aria-selected": _ctx.promptsSubTab === 'codex',
6876
6883
  onClick: $event => (_ctx.switchPromptsSubTab('codex'))
6877
6884
  }, _toDisplayString(_ctx.t('prompts.subTab.codex')), 11 /* TEXT, CLASS, PROPS */, ["aria-selected", "onClick"]),
6885
+ _createElementVNode("button", {
6886
+ type: "button",
6887
+ class: _normalizeClass(['prompts-md-tab', { active: _ctx.promptsSubTab === 'opencode' }]),
6888
+ role: "tab",
6889
+ "aria-selected": _ctx.promptsSubTab === 'opencode',
6890
+ onClick: $event => (_ctx.switchPromptsSubTab('opencode'))
6891
+ }, _toDisplayString(_ctx.t('prompts.subTab.opencode')), 11 /* TEXT, CLASS, PROPS */, ["aria-selected", "onClick"]),
6878
6892
  _createElementVNode("button", {
6879
6893
  type: "button",
6880
6894
  class: _normalizeClass(['prompts-md-tab', { active: _ctx.promptsSubTab === 'claude-project' }]),
@@ -530,7 +530,7 @@ export async function loadActiveSessionDetail(api, options = {}) {
530
530
  }
531
531
  }
532
532
 
533
- export async function loadMoreSessionMessages() {
533
+ export async function loadMoreSessionMessages(stepSize) {
534
534
  if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
535
535
  return;
536
536
  }
@@ -541,6 +541,19 @@ export async function loadMoreSessionMessages() {
541
541
  this.sessionPreviewVisibleCount = 0;
542
542
  return;
543
543
  }
544
+ const step = Number.isFinite(stepSize)
545
+ ? Math.max(1, Math.floor(stepSize))
546
+ : (Number.isFinite(this.sessionPreviewLoadStep)
547
+ ? Math.max(1, Math.floor(this.sessionPreviewLoadStep))
548
+ : 40);
549
+ const current = Number.isFinite(this.sessionPreviewVisibleCount)
550
+ ? Math.max(0, Math.floor(this.sessionPreviewVisibleCount))
551
+ : 0;
552
+ const targetVisible = current + step;
553
+ if (targetVisible <= total) {
554
+ this.sessionPreviewVisibleCount = Math.min(total, targetVisible);
555
+ return;
556
+ }
544
557
 
545
558
  this.sessionPreviewVisibleCount = total;
546
559
  if (this.sessionDetailLoading) {
@@ -560,19 +573,24 @@ export async function loadMoreSessionMessages() {
560
573
  const currentLimit = Number.isFinite(currentLimitRaw)
561
574
  ? Math.max(1, Math.floor(currentLimitRaw))
562
575
  : Math.max(1, total);
576
+ const fetchStep = Number.isFinite(this.sessionDetailFetchStep)
577
+ ? Math.max(1, Math.floor(this.sessionDetailFetchStep))
578
+ : 80;
563
579
  const limitCapRaw = Number(this.sessionDetailMessageLimitCap);
564
580
  const limitCap = Number.isFinite(limitCapRaw)
565
581
  ? Math.max(1, Math.floor(limitCapRaw))
566
582
  : 1000;
567
583
 
568
- const nextLimit = totalKnown > 0
569
- ? Math.min(totalKnown, limitCap)
570
- : limitCap;
584
+ let nextLimit = Math.max(currentLimit + fetchStep, targetVisible);
585
+ if (totalKnown > total) {
586
+ nextLimit = Math.min(nextLimit, totalKnown);
587
+ }
588
+ nextLimit = Math.min(nextLimit, limitCap);
571
589
  if (nextLimit <= currentLimit) {
572
590
  return;
573
591
  }
574
592
 
575
- this.sessionPreviewPendingVisibleCount = nextLimit;
593
+ this.sessionPreviewPendingVisibleCount = targetVisible;
576
594
  this.sessionDetailMessageLimit = nextLimit;
577
595
  this.sessionPreviewLoadingMore = true;
578
596
  try {
@@ -580,11 +598,4 @@ export async function loadMoreSessionMessages() {
580
598
  } finally {
581
599
  this.sessionPreviewLoadingMore = false;
582
600
  }
583
-
584
- const loadedTotal = Array.isArray(this.activeSessionMessages)
585
- ? this.activeSessionMessages.length
586
- : 0;
587
- if (loadedTotal > this.sessionPreviewVisibleCount) {
588
- this.sessionPreviewVisibleCount = loadedTotal;
589
- }
590
601
  }
@@ -104,6 +104,13 @@
104
104
  flex: 0 0 auto;
105
105
  }
106
106
 
107
+ .selector-header .selector-actions > .btn-tool + .btn-tool,
108
+ .selector-header .selector-actions > .btn-tool-compact + .btn-tool,
109
+ .selector-header .selector-actions > .btn-tool + .btn-tool-compact,
110
+ .selector-header .selector-actions > .btn-tool-compact + .btn-tool-compact {
111
+ margin-top: 0;
112
+ }
113
+
107
114
  .selector-actions.opencode-provider-actions {
108
115
  display: inline-flex;
109
116
  align-items: stretch;