codexmate 0.1.7 → 0.1.9

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.
@@ -0,0 +1,294 @@
1
+ import {
2
+ buildAgentsDiffPreview,
3
+ buildAgentsDiffPreviewRequest,
4
+ isAgentsDiffPreviewPayloadTooLarge,
5
+ shouldApplyAgentsDiffPreviewResponse
6
+ } from '../logic.mjs';
7
+ import { issueLatestRequestToken, isLatestRequestToken } from './request-token.mjs';
8
+
9
+ export function createSystemPromptMethods(options = {}) {
10
+ const {
11
+ api,
12
+ apiWithMeta
13
+ } = options;
14
+
15
+ return {
16
+
17
+ switchSysPromptScope(scope) {
18
+ if (this.sysPromptLoading || this.sysPromptSaving) return;
19
+ const normalized = scope === 'project' ? scope : 'global';
20
+ if (normalized === this.sysPromptScope) return;
21
+ this.sysPromptScope = normalized;
22
+ this.loadSystemPrompt();
23
+ },
24
+
25
+ switchSysPromptMode(mode) {
26
+ if (this.sysPromptLoading || this.sysPromptSaving) return;
27
+ const normalized = mode === 'system' ? mode : 'append';
28
+ if (normalized === this.sysPromptMode) return;
29
+ this.sysPromptMode = normalized;
30
+ this.loadSystemPrompt();
31
+ },
32
+
33
+ resetSysPromptDiffState() {
34
+ this.sysPromptDiffVisible = false;
35
+ this.sysPromptDiffLoading = false;
36
+ this.sysPromptDiffError = '';
37
+ this.sysPromptDiffLines = [];
38
+ this.sysPromptDiffStats = { added: 0, removed: 0, unchanged: 0 };
39
+ this.sysPromptDiffTruncated = false;
40
+ this.sysPromptDiffHasChangesValue = false;
41
+ this.sysPromptDiffFingerprint = '';
42
+ this._sysPromptDiffPreviewRequestToken = null;
43
+ },
44
+
45
+ hasSysPromptContentChanged() {
46
+ const original = typeof this.sysPromptOriginalContent === 'string' ? this.sysPromptOriginalContent : '';
47
+ const current = typeof this.sysPromptContent === 'string' ? this.sysPromptContent : '';
48
+ return original !== current;
49
+ },
50
+
51
+ buildSysPromptDiffFingerprint() {
52
+ const scope = this.sysPromptScope || 'global';
53
+ const mode = this.sysPromptMode || 'append';
54
+ const content = typeof this.sysPromptContent === 'string' ? this.sysPromptContent : '';
55
+ const original = typeof this.sysPromptOriginalContent === 'string' ? this.sysPromptOriginalContent : '';
56
+ return `${scope}::${mode}::${content.length}::${content}::${original.length}::${original}`;
57
+ },
58
+
59
+ async loadSystemPrompt() {
60
+ const requestToken = issueLatestRequestToken(this, '_sysPromptOpenRequestToken');
61
+ this.sysPromptLoading = true;
62
+ this.resetSysPromptDiffState();
63
+ try {
64
+ const params = {
65
+ scope: this.sysPromptScope || 'global',
66
+ mode: this.sysPromptMode || 'append'
67
+ };
68
+ const res = await api('get-system-prompt', params);
69
+ if (!isLatestRequestToken(this, '_sysPromptOpenRequestToken', requestToken)) return;
70
+ if (res.error) {
71
+ this.showMessage(res.error, 'error');
72
+ return;
73
+ }
74
+ this.sysPromptContent = res.content || '';
75
+ this.sysPromptOriginalContent = this.sysPromptContent;
76
+ this.sysPromptPath = res.path || '';
77
+ this.sysPromptExists = !!res.exists;
78
+ this.sysPromptHash = res.hash || '';
79
+ } catch (e) {
80
+ if (!isLatestRequestToken(this, '_sysPromptOpenRequestToken', requestToken)) return;
81
+ this.showMessage(this.t('toast.load.fail'), 'error');
82
+ } finally {
83
+ if (isLatestRequestToken(this, '_sysPromptOpenRequestToken', requestToken)) {
84
+ this.sysPromptLoading = false;
85
+ }
86
+ }
87
+ },
88
+
89
+ async prepareSysPromptDiff() {
90
+ const requestFingerprint = this.buildSysPromptDiffFingerprint();
91
+ const requestToken = Symbol('sys-prompt-diff-preview');
92
+ this._sysPromptDiffPreviewRequestToken = requestToken;
93
+ this.sysPromptDiffVisible = true;
94
+ this.sysPromptDiffLoading = true;
95
+ this.sysPromptDiffError = '';
96
+ this.sysPromptDiffLines = [];
97
+ this.sysPromptDiffStats = { added: 0, removed: 0, unchanged: 0 };
98
+ this.sysPromptDiffTruncated = false;
99
+ this.sysPromptDiffHasChangesValue = false;
100
+ try {
101
+ const shouldApplyPreviewState = () => shouldApplyAgentsDiffPreviewResponse({
102
+ isVisible: this.sysPromptDiffVisible,
103
+ requestToken,
104
+ activeRequestToken: this._sysPromptDiffPreviewRequestToken,
105
+ requestFingerprint,
106
+ currentFingerprint: this.buildSysPromptDiffFingerprint()
107
+ });
108
+ const applyPreviewState = (diff) => {
109
+ if (!shouldApplyPreviewState()) return false;
110
+ const normalizedDiff = diff && typeof diff === 'object' ? diff : {};
111
+ const rawLines = Array.isArray(normalizedDiff.lines) ? normalizedDiff.lines : [];
112
+ this.sysPromptDiffLines = rawLines.filter(line => line && line.type);
113
+ this.sysPromptDiffTruncated = !!normalizedDiff.truncated;
114
+ this.sysPromptDiffHasChangesValue = !!normalizedDiff.hasChanges;
115
+ if (normalizedDiff.stats && typeof normalizedDiff.stats === 'object') {
116
+ this.sysPromptDiffStats = {
117
+ added: Number(normalizedDiff.stats.added || 0),
118
+ removed: Number(normalizedDiff.stats.removed || 0),
119
+ unchanged: Number(normalizedDiff.stats.unchanged || 0)
120
+ };
121
+ } else {
122
+ const stats = { added: 0, removed: 0, unchanged: 0 };
123
+ for (const line of this.sysPromptDiffLines) {
124
+ if (line && line.type === 'add') stats.added += 1;
125
+ else if (line && line.type === 'del') stats.removed += 1;
126
+ else stats.unchanged += 1;
127
+ }
128
+ this.sysPromptDiffStats = stats;
129
+ }
130
+ this.sysPromptDiffFingerprint = requestFingerprint;
131
+ return true;
132
+ };
133
+ const previewRequest = buildAgentsDiffPreviewRequest({
134
+ baseContent: this.sysPromptOriginalContent,
135
+ content: this.sysPromptContent,
136
+ lineEnding: '\n',
137
+ context: 'sys-prompt',
138
+ baseDir: undefined
139
+ });
140
+ if (previewRequest.exceedsBodyLimit) {
141
+ applyPreviewState(buildAgentsDiffPreview({
142
+ baseContent: this.sysPromptOriginalContent,
143
+ content: this.sysPromptContent
144
+ }));
145
+ return;
146
+ }
147
+ const rpcParams = {
148
+ ...previewRequest.params,
149
+ scope: this.sysPromptScope || 'global',
150
+ mode: this.sysPromptMode || 'append'
151
+ };
152
+ const res = await apiWithMeta('preview-system-prompt-diff', rpcParams);
153
+ if (!shouldApplyPreviewState()) return;
154
+ if (res.error) {
155
+ if (isAgentsDiffPreviewPayloadTooLarge(res)) {
156
+ applyPreviewState(buildAgentsDiffPreview({
157
+ baseContent: this.sysPromptOriginalContent,
158
+ content: this.sysPromptContent
159
+ }));
160
+ return;
161
+ }
162
+ this.sysPromptDiffError = res.error;
163
+ return;
164
+ }
165
+ applyPreviewState(res.diff);
166
+ } catch (e) {
167
+ if (shouldApplyAgentsDiffPreviewResponse({
168
+ isVisible: this.sysPromptDiffVisible,
169
+ requestToken,
170
+ activeRequestToken: this._sysPromptDiffPreviewRequestToken,
171
+ requestFingerprint,
172
+ currentFingerprint: this.buildSysPromptDiffFingerprint()
173
+ })) {
174
+ this.sysPromptDiffError = '生成差异失败';
175
+ }
176
+ } finally {
177
+ if (this._sysPromptDiffPreviewRequestToken === requestToken) {
178
+ this.sysPromptDiffLoading = false;
179
+ }
180
+ }
181
+ },
182
+
183
+ cancelSysPromptEdit() {
184
+ if (this.sysPromptSaving || this.sysPromptDiffLoading) return;
185
+ this.resetSysPromptDiffState();
186
+ this.loadSystemPrompt();
187
+ },
188
+
189
+ async applySystemPrompt() {
190
+ if (this.sysPromptSaving) return;
191
+ if (!this.sysPromptDiffVisible) {
192
+ if (!this.hasSysPromptContentChanged()) {
193
+ this.showMessage(this.t('toast.noChanges'), 'info');
194
+ return;
195
+ }
196
+ await this.prepareSysPromptDiff();
197
+ return;
198
+ }
199
+ if (this.sysPromptDiffLoading) return;
200
+ if (this.sysPromptDiffError) {
201
+ this.showMessage(this.sysPromptDiffError, 'error');
202
+ return;
203
+ }
204
+ const fingerprint = this.buildSysPromptDiffFingerprint();
205
+ if (this.sysPromptDiffFingerprint !== fingerprint) {
206
+ await this.prepareSysPromptDiff();
207
+ return;
208
+ }
209
+ if (!this.sysPromptDiffHasChangesValue) {
210
+ this.showMessage(this.t('toast.noChanges'), 'info');
211
+ return;
212
+ }
213
+ this.sysPromptSaving = true;
214
+ try {
215
+ const params = {
216
+ scope: this.sysPromptScope || 'global',
217
+ mode: this.sysPromptMode || 'append',
218
+ content: this.sysPromptContent,
219
+ baseHash: this.sysPromptHash
220
+ };
221
+ const res = await api('apply-system-prompt', params);
222
+ if (res.error) {
223
+ this.showMessage(res.error, 'error');
224
+ return;
225
+ }
226
+ this.showMessage(this.t('sysPrompt.toast.saved'), 'success');
227
+ await this.loadSystemPrompt();
228
+ } catch (e) {
229
+ this.showMessage(this.t('toast.save.fail'), 'error');
230
+ } finally {
231
+ this.sysPromptSaving = false;
232
+ }
233
+ },
234
+
235
+ copySysPromptContent() {
236
+ const content = typeof this.sysPromptContent === 'string' ? this.sysPromptContent : '';
237
+ if (!content) return;
238
+ try {
239
+ navigator.clipboard?.writeText(content);
240
+ this.showMessage(this.t('common.copy'), 'success');
241
+ } catch (_) {}
242
+ },
243
+
244
+ exportSysPromptContent() {
245
+ const content = typeof this.sysPromptContent === 'string' ? this.sysPromptContent : '';
246
+ if (!content) return;
247
+ const filename = (this.sysPromptMode === 'system' ? 'SYSTEM.md' : 'APPEND_SYSTEM.md');
248
+ const blob = new Blob([content], { type: 'text/markdown' });
249
+ const a = document.createElement('a');
250
+ a.href = URL.createObjectURL(blob);
251
+ a.download = filename;
252
+ document.body.appendChild(a);
253
+ a.click();
254
+ a.remove();
255
+ URL.revokeObjectURL(a.href);
256
+ },
257
+
258
+ async pasteSysPromptContent() {
259
+ if (this.sysPromptLoading || this.sysPromptSaving || this.sysPromptDiffVisible) return;
260
+ try {
261
+ const text = await navigator.clipboard.readText();
262
+ if (text) {
263
+ this.sysPromptContent = text;
264
+ this.showMessage(this.t('common.paste'), 'success');
265
+ }
266
+ } catch (_) {}
267
+ },
268
+
269
+ sysPromptContextHint() {
270
+ if (!this.sysPromptLoading && !this.sysPromptDiffVisible && this.hasSysPromptContentChanged()) {
271
+ return { text: this.t('sysPrompt.hint.unsaved'), warn: true };
272
+ }
273
+ if (this.sysPromptDiffVisible && (this.sysPromptDiffLoading || this.sysPromptSaving)) {
274
+ return { text: this.t('diff.hint.busy'), warn: false };
275
+ }
276
+ if (this.sysPromptDiffVisible && this.sysPromptDiffError) {
277
+ return { text: this.t('diff.hint.failedBack'), warn: false };
278
+ }
279
+ if (this.sysPromptDiffVisible && !this.sysPromptDiffHasChangesValue) {
280
+ return { text: this.t('diff.hint.noChangesBack'), warn: false };
281
+ }
282
+ if (this.sysPromptDiffVisible && this.sysPromptDiffTruncated) {
283
+ return { text: this.t('diff.viewHint.truncated'), warn: false };
284
+ }
285
+ if (this.sysPromptDiffVisible) {
286
+ return { text: this.t('diff.viewHint.preview'), warn: false };
287
+ }
288
+ if (!this.sysPromptLoading) {
289
+ return { text: this.t('sysPrompt.hint.twoStepSave'), warn: false };
290
+ }
291
+ return null;
292
+ }
293
+ };
294
+ }
@@ -231,6 +231,7 @@ const en = Object.freeze({
231
231
  'subtitle.prompts': 'Edit AGENTS.md and CLAUDE.md.',
232
232
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
233
233
  'prompts.subTab.project': 'CLAUDE.md',
234
+ 'prompts.subTab.system': 'System Prompt',
234
235
  'prompts.subTab.presets': 'Preset pool',
235
236
  'prompts.presets.title': 'Presets',
236
237
  'prompts.presets.hint': 'Manage presets.',
@@ -663,6 +664,19 @@ const en = Object.freeze({
663
664
  'sessions.copyResume': 'Copy resume command',
664
665
  'sessions.preview.refresh': 'Refresh content',
665
666
  'sessions.preview.loading': 'Loading...',
667
+ 'sessions.batch.select': 'Batch delete',
668
+ 'sessions.batch.exit': 'Exit selection',
669
+ 'sessions.batch.selectAll': 'Select all',
670
+ 'sessions.batch.unselectAll': 'Clear selection',
671
+ 'sessions.batch.deleteSelected': 'Delete selected',
672
+ 'sessions.batch.deleteSelectedN': 'Delete selected ({count})',
673
+ 'sessions.batch.deletingSelected': 'Deleting...',
674
+ 'sessions.batch.deleteConfirm': 'Delete the {count} selected sessions?',
675
+ 'sessions.batch.deleteConfirmHard': 'Permanently delete the {count} selected sessions? This cannot be undone.',
676
+ 'sessions.batch.deleteHint': '{count} selected',
677
+ 'sessions.batch.emptySelection': 'Select sessions to delete first',
678
+ 'sessions.batch.partialFail': '{failed} failed to delete; selection kept',
679
+ 'sessions.batch.allFail': 'Delete failed, please retry',
666
680
  'sessions.preview.deleteHard': 'Delete permanently',
667
681
  'sessions.preview.moveToTrash': 'Move to trash',
668
682
  'sessions.preview.deleting': 'Deleting...',
@@ -1575,6 +1589,16 @@ const en = Object.freeze({
1575
1589
  'kilocode.writeRequired': 'Enable KiloCode write access first',
1576
1590
  'kilocode.saved': 'KiloCode config saved',
1577
1591
  'kilocode.autoSaved': 'KiloCode config synced',
1592
+ 'sysPrompt.scope.label': 'Scope',
1593
+ 'sysPrompt.scope.global': 'Global (~/.pi/agent/)',
1594
+ 'sysPrompt.scope.project': 'Project (<cwd>/.pi/)',
1595
+ 'sysPrompt.mode.label': 'Mode',
1596
+ 'sysPrompt.mode.append': 'Append to default prompt (APPEND_SYSTEM.md)',
1597
+ 'sysPrompt.mode.system': 'Replace default prompt (SYSTEM.md)',
1598
+ 'sysPrompt.placeholder': 'Type system prompt content here…',
1599
+ 'sysPrompt.hint.unsaved': 'Unsaved changes detected',
1600
+ 'sysPrompt.hint.twoStepSave': 'Click preview to see the diff, then confirm to save.',
1601
+ 'sysPrompt.toast.saved': 'System prompt saved',
1578
1602
  });
1579
1603
 
1580
1604
  export { en };
@@ -232,6 +232,7 @@ const ja = Object.freeze({
232
232
  'subtitle.prompts': 'AGENTS.md と CLAUDE.md を編集。',
233
233
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
234
234
  'prompts.subTab.project': 'CLAUDE.md',
235
+ 'prompts.subTab.system': 'System Prompt',
235
236
  'prompts.subTab.presets': 'プリセット',
236
237
  'prompts.presets.title': 'プリセット',
237
238
  'prompts.presets.hint': 'プリセットを管理します。',
@@ -665,6 +666,19 @@ const ja = Object.freeze({
665
666
  'sessions.copyResume': '再開コマンドをコピー',
666
667
  'sessions.preview.refresh': '更新',
667
668
  'sessions.preview.loading': 'プレビュー読み込み中...',
669
+ 'sessions.batch.select': '一括削除',
670
+ 'sessions.batch.exit': '選択を終了',
671
+ 'sessions.batch.selectAll': 'すべて選択',
672
+ 'sessions.batch.unselectAll': '選択を解除',
673
+ 'sessions.batch.deleteSelected': '選択を削除',
674
+ 'sessions.batch.deleteSelectedN': '選択を削除({count})',
675
+ 'sessions.batch.deletingSelected': '削除中...',
676
+ 'sessions.batch.deleteConfirm': '選択した {count} 件のセッションを削除しますか?',
677
+ 'sessions.batch.deleteConfirmHard': '選択した {count} 件のセッションを完全に削除しますか?元に戻せません。',
678
+ 'sessions.batch.deleteHint': '{count} 件選択',
679
+ 'sessions.batch.emptySelection': '削除するセッションを選択してください',
680
+ 'sessions.batch.partialFail': '{failed} 件の削除に失敗しました。選択を維持します',
681
+ 'sessions.batch.allFail': '削除に失敗しました。再試行してください',
668
682
  'sessions.preview.deleteHard': '直接削除',
669
683
  'sessions.preview.moveToTrash': 'ゴミ箱に移動',
670
684
  'sessions.preview.deleting': '削除中...',
@@ -1576,6 +1590,16 @@ const ja = Object.freeze({
1576
1590
  'kilocode.writeRequired': '先に KiloCode の書き込みを有効化してください',
1577
1591
  'kilocode.saved': 'KiloCode 設定を保存しました',
1578
1592
  'kilocode.autoSaved': 'KiloCode 設定を同期しました',
1593
+ 'sysPrompt.scope.label': 'スコープ',
1594
+ 'sysPrompt.scope.global': 'グローバル (~/.pi/agent/)',
1595
+ 'sysPrompt.scope.project': 'プロジェクト (<cwd>/.pi/)',
1596
+ 'sysPrompt.mode.label': 'モード',
1597
+ 'sysPrompt.mode.append': 'デフォルトに追加 (APPEND_SYSTEM.md)',
1598
+ 'sysPrompt.mode.system': 'デフォルトを置換 (SYSTEM.md)',
1599
+ 'sysPrompt.placeholder': 'システムプロンプトの内容を入力…',
1600
+ 'sysPrompt.hint.unsaved': '未保存の変更があります',
1601
+ 'sysPrompt.hint.twoStepSave': 'プレビューで差分を確認し、確認後に保存します。',
1602
+ 'sysPrompt.toast.saved': 'System prompt を保存しました',
1579
1603
  });
1580
1604
 
1581
1605
  export { ja };
@@ -250,6 +250,7 @@ const vi = Object.freeze({
250
250
  'subtitle.prompts': 'Chỉnh sửa AGENTS.md và CLAUDE.md.',
251
251
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
252
252
  'prompts.subTab.project': 'CLAUDE.md',
253
+ 'prompts.subTab.system': 'System Prompt',
253
254
  'prompts.subTab.presets': 'Kho preset',
254
255
  'prompts.presets.title': 'Preset',
255
256
  'prompts.presets.hint': 'Quản lý preset.',
@@ -871,6 +872,19 @@ const vi = Object.freeze({
871
872
  'sessions.copyResume': 'Sao chép lệnh tiếp tục',
872
873
  'sessions.preview.refresh': 'Làm mới nội dung',
873
874
  'sessions.preview.loading': 'Đang tải...',
875
+ 'sessions.batch.select': 'Xóa hàng loạt',
876
+ 'sessions.batch.exit': 'Thoát chọn',
877
+ 'sessions.batch.selectAll': 'Chọn tất cả',
878
+ 'sessions.batch.unselectAll': 'Bỏ chọn tất cả',
879
+ 'sessions.batch.deleteSelected': 'Xóa đã chọn',
880
+ 'sessions.batch.deleteSelectedN': 'Xóa đã chọn ({count})',
881
+ 'sessions.batch.deletingSelected': 'Đang xóa...',
882
+ 'sessions.batch.deleteConfirm': 'Xóa {count} phiên đã chọn?',
883
+ 'sessions.batch.deleteConfirmHard': 'Xóa vĩnh viễn {count} phiên đã chọn? Không thể hoàn tác.',
884
+ 'sessions.batch.deleteHint': 'Đã chọn {count}',
885
+ 'sessions.batch.emptySelection': 'Hãy chọn phiên cần xóa trước',
886
+ 'sessions.batch.partialFail': '{failed} mục xóa thất bại, giữ nguyên lựa chọn',
887
+ 'sessions.batch.allFail': 'Xóa thất bại, vui lòng thử lại',
874
888
  'sessions.preview.deleteHard': 'Xóa vĩnh viễn',
875
889
  'sessions.preview.moveToTrash': 'Chuyển vào thùng rác',
876
890
  'sessions.preview.deleting': 'Đang xóa...',
@@ -1545,6 +1559,16 @@ const vi = Object.freeze({
1545
1559
  'kilocode.writeRequired': 'Hãy bật quyền ghi KiloCode trước',
1546
1560
  'kilocode.saved': 'Đã lưu cấu hình KiloCode',
1547
1561
  'kilocode.autoSaved': 'Đã đồng bộ cấu hình KiloCode',
1562
+ 'sysPrompt.scope.label': 'Phạm vi',
1563
+ 'sysPrompt.scope.global': 'Toàn cục (~/.pi/agent/)',
1564
+ 'sysPrompt.scope.project': 'Dự án (<cwd>/.pi/)',
1565
+ 'sysPrompt.mode.label': 'Chế độ',
1566
+ 'sysPrompt.mode.append': 'Thêm vào prompt mặc định (APPEND_SYSTEM.md)',
1567
+ 'sysPrompt.mode.system': 'Thay thế prompt mặc định (SYSTEM.md)',
1568
+ 'sysPrompt.placeholder': 'Nhập nội dung system prompt…',
1569
+ 'sysPrompt.hint.unsaved': 'Có thay đổi chưa lưu',
1570
+ 'sysPrompt.hint.twoStepSave': 'Nhấn xem trước để xem diff, sau đó xác nhận để lưu.',
1571
+ 'sysPrompt.toast.saved': 'Đã lưu system prompt',
1548
1572
  });
1549
1573
 
1550
1574
  export { vi };
@@ -231,6 +231,7 @@ const zhTw = Object.freeze({
231
231
  'subtitle.prompts': '編輯 AGENTS.md 與 CLAUDE.md。',
232
232
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
233
233
  'prompts.subTab.project': 'CLAUDE.md',
234
+ 'prompts.subTab.system': 'System Prompt',
234
235
  'prompts.subTab.presets': '預設池',
235
236
  'prompts.presets.title': '預設池',
236
237
  'prompts.presets.hint': '管理預設。',
@@ -662,6 +663,19 @@ const zhTw = Object.freeze({
662
663
  'sessions.copyResume': '複製恢復命令',
663
664
  'sessions.preview.refresh': '重新整理內容',
664
665
  'sessions.preview.loading': '載入中...',
666
+ 'sessions.batch.select': '批次刪除',
667
+ 'sessions.batch.exit': '退出選擇',
668
+ 'sessions.batch.selectAll': '全選',
669
+ 'sessions.batch.unselectAll': '取消全選',
670
+ 'sessions.batch.deleteSelected': '刪除選中',
671
+ 'sessions.batch.deleteSelectedN': '刪除選中({count})',
672
+ 'sessions.batch.deletingSelected': '刪除中...',
673
+ 'sessions.batch.deleteConfirm': '確定刪除選中的 {count} 個會話嗎?',
674
+ 'sessions.batch.deleteConfirmHard': '確定永久刪除選中的 {count} 個會話嗎?該操作不可還原。',
675
+ 'sessions.batch.deleteHint': '已選 {count} 個',
676
+ 'sessions.batch.emptySelection': '請先選擇要刪除的會話',
677
+ 'sessions.batch.partialFail': '其中 {failed} 個刪除失敗,已保留選中',
678
+ 'sessions.batch.allFail': '刪除失敗,請重試',
665
679
  'sessions.preview.deleteHard': '直接刪除',
666
680
  'sessions.preview.moveToTrash': '移入回收站',
667
681
  'sessions.preview.deleting': '刪除中...',
@@ -1573,6 +1587,16 @@ const zhTw = Object.freeze({
1573
1587
  'kilocode.writeRequired': '請先開啟 KiloCode 寫入開關',
1574
1588
  'kilocode.saved': 'KiloCode 設定已儲存',
1575
1589
  'kilocode.autoSaved': 'KiloCode 設定已同步',
1590
+ 'sysPrompt.scope.label': '作用域',
1591
+ 'sysPrompt.scope.global': '全域 (~/.pi/agent/)',
1592
+ 'sysPrompt.scope.project': '專案 (<cwd>/.pi/)',
1593
+ 'sysPrompt.mode.label': '模式',
1594
+ 'sysPrompt.mode.append': '追加到預設提示詞 (APPEND_SYSTEM.md)',
1595
+ 'sysPrompt.mode.system': '替換預設提示詞 (SYSTEM.md)',
1596
+ 'sysPrompt.placeholder': '在此輸入 system prompt 內容…',
1597
+ 'sysPrompt.hint.unsaved': '有未儲存的變更',
1598
+ 'sysPrompt.hint.twoStepSave': '點擊預覽查看差異,確認後儲存。',
1599
+ 'sysPrompt.toast.saved': 'System prompt 已儲存',
1576
1600
  });
1577
1601
 
1578
1602
  export { zhTw };
@@ -231,6 +231,7 @@ const zh = Object.freeze({
231
231
  'subtitle.prompts': '编辑 AGENTS.md 与 CLAUDE.md。',
232
232
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
233
233
  'prompts.subTab.project': 'CLAUDE.md',
234
+ 'prompts.subTab.system': 'System Prompt',
234
235
  'prompts.subTab.presets': '预设池',
235
236
  'prompts.presets.title': '预设池',
236
237
  'prompts.presets.hint': '管理预设。',
@@ -662,6 +663,19 @@ const zh = Object.freeze({
662
663
  'sessions.copyResume': '复制恢复命令',
663
664
  'sessions.preview.refresh': '刷新内容',
664
665
  'sessions.preview.loading': '加载中...',
666
+ 'sessions.batch.select': '批量删除',
667
+ 'sessions.batch.exit': '退出选择',
668
+ 'sessions.batch.selectAll': '全选',
669
+ 'sessions.batch.unselectAll': '取消全选',
670
+ 'sessions.batch.deleteSelected': '删除选中',
671
+ 'sessions.batch.deleteSelectedN': '删除选中({count})',
672
+ 'sessions.batch.deletingSelected': '删除中...',
673
+ 'sessions.batch.deleteConfirm': '确定删除选中的 {count} 个会话吗?',
674
+ 'sessions.batch.deleteConfirmHard': '确定永久删除选中的 {count} 个会话吗?该操作不可恢复。',
675
+ 'sessions.batch.deleteHint': '已选 {count} 个',
676
+ 'sessions.batch.emptySelection': '请先选择要删除的会话',
677
+ 'sessions.batch.partialFail': '其中 {failed} 个删除失败,已保留选中',
678
+ 'sessions.batch.allFail': '删除失败,请重试',
665
679
  'sessions.preview.deleteHard': '直接删除',
666
680
  'sessions.preview.moveToTrash': '移入回收站',
667
681
  'sessions.preview.deleting': '删除中...',
@@ -1573,6 +1587,16 @@ const zh = Object.freeze({
1573
1587
  'kilocode.writeRequired': '请先打开 KiloCode 写入开关',
1574
1588
  'kilocode.saved': 'KiloCode 配置已保存',
1575
1589
  'kilocode.autoSaved': 'KiloCode 配置已同步',
1590
+ 'sysPrompt.scope.label': '作用域',
1591
+ 'sysPrompt.scope.global': '全局 (~/.pi/agent/)',
1592
+ 'sysPrompt.scope.project': '项目 (<cwd>/.pi/)',
1593
+ 'sysPrompt.mode.label': '模式',
1594
+ 'sysPrompt.mode.append': '追加到默认提示词 (APPEND_SYSTEM.md)',
1595
+ 'sysPrompt.mode.system': '替换默认提示词 (SYSTEM.md)',
1596
+ 'sysPrompt.placeholder': '在此输入 system prompt 内容…',
1597
+ 'sysPrompt.hint.unsaved': '有未保存的改动',
1598
+ 'sysPrompt.hint.twoStepSave': '点击预览查看差异,确认后保存。',
1599
+ 'sysPrompt.toast.saved': 'System prompt 已保存',
1576
1600
  });
1577
1601
 
1578
1602
  export { zh };
@@ -0,0 +1,9 @@
1
+ export function issueLatestRequestToken(context, key) {
2
+ const token = (Number(context[key]) || 0) + 1;
3
+ context[key] = token;
4
+ return token;
5
+ }
6
+
7
+ export function isLatestRequestToken(context, key, token) {
8
+ return !!context && context[key] === token;
9
+ }
@@ -8,6 +8,7 @@
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
10
  <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
+ <button type="button" :class="['prompts-md-tab', { active: promptsSubTab === 'system' }]" role="tab" :aria-selected="promptsSubTab === 'system'" @click="switchPromptsSubTab('system')">{{ t('prompts.subTab.system') }}</button>
11
12
  </div>
12
13
 
13
14
  <div v-if="promptsSubTab === 'claude-project'" class="project-path-selector">
@@ -33,7 +34,7 @@
33
34
  </div>
34
35
 
35
36
 
36
- <div class="prompts-editor">
37
+ <div v-if="promptsSubTab !== 'system'" class="prompts-editor">
37
38
  <div class="prompts-editor-toolbar">
38
39
  <div class="form-hint">
39
40
  {{ agentsPath || t('common.notLoaded') }}
@@ -189,4 +190,98 @@
189
190
  </div>
190
191
 
191
192
  </div>
193
+
194
+ <div v-if="promptsSubTab === 'system'">
195
+ <div class="sys-prompt-controls">
196
+ <div class="sys-prompt-control-group">
197
+ <label class="form-label">{{ t('sysPrompt.scope.label') }}</label>
198
+ <select class="form-input sys-prompt-select" :value="sysPromptScope" @change="switchSysPromptScope($event.target.value)" :disabled="sysPromptLoading || sysPromptSaving || sysPromptDiffVisible">
199
+ <option value="global">{{ t('sysPrompt.scope.global') }}</option>
200
+ <option value="project">{{ t('sysPrompt.scope.project') }}</option>
201
+ </select>
202
+ </div>
203
+ <div class="sys-prompt-control-group">
204
+ <label class="form-label">{{ t('sysPrompt.mode.label') }}</label>
205
+ <select class="form-input sys-prompt-select" :value="sysPromptMode" @change="switchSysPromptMode($event.target.value)" :disabled="sysPromptLoading || sysPromptSaving || sysPromptDiffVisible">
206
+ <option value="append">{{ t('sysPrompt.mode.append') }}</option>
207
+ <option value="system">{{ t('sysPrompt.mode.system') }}</option>
208
+ </select>
209
+ </div>
210
+ </div>
211
+
212
+ <div class="prompts-editor">
213
+ <div class="prompts-editor-toolbar">
214
+ <div class="form-hint">
215
+ {{ sysPromptPath || t('common.notLoaded') }}
216
+ <span v-if="sysPromptPath">
217
+ ({{ sysPromptExists ? t('common.exists') : t('common.notExistsWillCreateOnSave') }})
218
+ </span>
219
+ </div>
220
+ <div class="prompts-editor-actions">
221
+ <div class="prompts-editor-group prompts-editor-group--secondary">
222
+ <button class="btn-mini" @click="exportSysPromptContent" :disabled="sysPromptLoading || sysPromptSaving || !sysPromptContent" :title="t('modal.agents.export')">
223
+ <svg class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M8 11l4 4 4-4M4 17v2h16v-2"/></svg>
224
+ </button>
225
+ <button class="btn-mini" @click="copySysPromptContent" :disabled="sysPromptLoading || sysPromptSaving || !sysPromptContent" :title="t('modal.agents.copy')">
226
+ <svg class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
227
+ </button>
228
+ <button class="btn-mini" @click="pasteSysPromptContent" :disabled="sysPromptLoading || sysPromptSaving || sysPromptDiffVisible" :title="t('common.paste')">
229
+ <svg class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"/></svg>
230
+ </button>
231
+ </div>
232
+ <div class="prompts-editor-group prompts-editor-group--workflow">
233
+ <button class="btn-mini" @click="cancelSysPromptEdit" :disabled="sysPromptSaving || sysPromptDiffLoading" :title="t('common.cancel')">
234
+ <svg class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
235
+ </button>
236
+ <button v-if="sysPromptDiffVisible" class="btn-mini" @click="resetSysPromptDiffState" :disabled="sysPromptSaving || sysPromptDiffLoading" :title="t('common.backToEdit')">
237
+ <svg class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
238
+ </button>
239
+ <button class="btn-mini btn-confirm-mini" @click="applySystemPrompt" :disabled="sysPromptSaving || sysPromptLoading || sysPromptDiffLoading || (!sysPromptDiffVisible && !hasSysPromptContentChanged()) || (sysPromptDiffVisible && !sysPromptDiffHasChangesValue)" :title="sysPromptDiffVisible ? t('common.save') : t('common.preview')">
240
+ <svg v-if="sysPromptDiffVisible" class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>
241
+ <svg v-else class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
242
+ </button>
243
+ </div>
244
+ </div>
245
+ </div>
246
+ <div class="form-group">
247
+ <div v-if="sysPromptDiffVisible">
248
+ <div
249
+ v-if="!sysPromptDiffLoading && !sysPromptDiffError && !sysPromptDiffTruncated && (sysPromptDiffStats.added || sysPromptDiffStats.removed)"
250
+ class="agents-diff-summary">
251
+ <span class="agents-diff-stat add">+{{ sysPromptDiffStats.added }}</span>
252
+ <span class="agents-diff-stat del">-{{ sysPromptDiffStats.removed }}</span>
253
+ </div>
254
+ <div v-if="sysPromptDiffLoading" class="state-message">{{ t('diff.generating') }}</div>
255
+ <div v-else-if="sysPromptDiffError" class="state-message error">{{ sysPromptDiffError }}</div>
256
+ <div v-else-if="sysPromptDiffTruncated" class="agents-diff-empty">{{ t('diff.tooLargeSkip') }}</div>
257
+ <div v-else-if="!sysPromptDiffHasChangesValue" class="agents-diff-empty">{{ t('diff.noChanges') }}</div>
258
+ <div v-else class="agents-diff-view agents-diff-editor">
259
+ <div
260
+ v-for="(line, index) in sysPromptDiffLines"
261
+ :key="'sys-prompt-diff-' + index"
262
+ :class="['agents-diff-line', line.type]">
263
+ <span class="agents-diff-line-sign">
264
+ {{ line.type === 'add' ? '+' : (line.type === 'del' ? '-' : ' ') }}
265
+ </span>
266
+ <span class="agents-diff-line-text">{{ line.value }}</span>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ <div :class="['editor-frame', { 'editor-frame--loading': sysPromptLoading }]">
271
+ <div v-if="sysPromptLoading" class="editor-skeleton">
272
+ <div class="skeleton-line" v-for="i in 6" :key="i"></div>
273
+ </div>
274
+ <textarea
275
+ v-model="sysPromptContent"
276
+ class="form-input template-editor"
277
+ spellcheck="false"
278
+ :readonly="sysPromptLoading || sysPromptSaving || sysPromptDiffVisible"
279
+ :placeholder="t('sysPrompt.placeholder')"></textarea>
280
+ </div>
281
+ <div v-if="sysPromptContextHint()" :class="['prompts-context-hint', { 'prompts-context-hint--warn': sysPromptContextHint().warn }]">
282
+ {{ sysPromptContextHint().text }}
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </div>
192
287
  </div>