codexmate 0.1.8 → 0.1.10

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.
@@ -639,27 +639,16 @@ export function createSessionActionMethods(options = {}) {
639
639
  }
640
640
  },
641
641
 
642
- async deleteSession(session) {
642
+ async deleteSession(session, batchOpts = {}) {
643
+ const batch = !!(batchOpts && batchOpts.batch);
643
644
  if (!this.isDeleteAvailable(session)) {
644
- this.showMessage('不支持此操作', 'error');
645
- return;
645
+ if (!batch) this.showMessage('不支持此操作', 'error');
646
+ return false;
646
647
  }
647
648
  const useTrash = this.sessionTrashEnabled !== false;
648
- if (!useTrash && typeof this.requestConfirmDialog === 'function') {
649
- const confirmed = await this.requestConfirmDialog({
650
- title: '直接删除会话',
651
- message: '关闭回收站后,删除会话将直接永久删除,且无法恢复。',
652
- confirmText: '直接删除',
653
- cancelText: '取消',
654
- danger: true
655
- });
656
- if (!confirmed) {
657
- return;
658
- }
659
- }
660
649
  const key = this.getSessionExportKey(session);
661
650
  if (this.sessionDeleting[key]) {
662
- return;
651
+ return false;
663
652
  }
664
653
  this.sessionDeleting[key] = true;
665
654
  try {
@@ -670,13 +659,14 @@ export function createSessionActionMethods(options = {}) {
670
659
  filePath: session.filePath
671
660
  });
672
661
  if (!res || res.error) {
673
- this.showMessage((res && res.error) || '删除失败', 'error');
674
- return;
662
+ const msg = (res && res.error) || (typeof this.t === 'function' ? this.t('toast.delete.fail') : '删除失败');
663
+ if (!batch) this.showMessage(msg, 'error');
664
+ return false;
675
665
  }
676
666
  this.removeSessionPin(session);
677
667
  if (useTrash) {
678
668
  this.invalidateSessionTrashRequests();
679
- this.showMessage('已移入回收站', 'success');
669
+ if (!batch) this.showMessage('已移入回收站', 'success');
680
670
  if (this.sessionTrashLoadedOnce) {
681
671
  this.prependSessionTrashItem(this.buildSessionTrashItemFromSession(session, res), {
682
672
  totalCount: res && res.totalCount !== undefined ? res.totalCount : undefined
@@ -689,7 +679,7 @@ export function createSessionActionMethods(options = {}) {
689
679
  this.sessionTrashItems
690
680
  );
691
681
  }
692
- } else {
682
+ } else if (!batch) {
693
683
  this.showMessage('已删除', 'success');
694
684
  }
695
685
  if (typeof this.invalidateSessionsUsageData === 'function') {
@@ -700,11 +690,146 @@ export function createSessionActionMethods(options = {}) {
700
690
  } catch (_) {
701
691
  // The delete already succeeded remotely; keep the success result.
702
692
  }
693
+ return true;
703
694
  } catch (_) {
704
- this.showMessage(this.t('toast.delete.fail'), 'error');
695
+ if (!batch) {
696
+ this.showMessage(this.t && this.t('toast.delete.fail') ? this.t('toast.delete.fail') : '删除失败', 'error');
697
+ }
698
+ return false;
705
699
  } finally {
706
700
  this.sessionDeleting[key] = false;
707
701
  }
702
+ },
703
+
704
+ isSessionBatchSelectable(session) {
705
+ return this.isDeleteAvailable(session);
706
+ },
707
+
708
+ isSessionSelectedForBatch(session) {
709
+ const key = this.getSessionExportKey(session);
710
+ if (!key) return false;
711
+ return !!this.sessionSelectedKeys[key];
712
+ },
713
+
714
+ toggleSessionSelectionForBatch(session) {
715
+ const key = this.getSessionExportKey(session);
716
+ if (!key) return;
717
+ if (this.sessionSelectedKeys[key]) {
718
+ const next = { ...this.sessionSelectedKeys };
719
+ delete next[key];
720
+ this.sessionSelectedKeys = next;
721
+ return;
722
+ }
723
+ this.sessionSelectedKeys = { ...this.sessionSelectedKeys, [key]: true };
724
+ },
725
+
726
+ enterSessionBatchSelectMode() {
727
+ if (this.sessionBatchSelectMode) return;
728
+ this.sessionBatchSelectMode = true;
729
+ this.sessionSelectedKeys = {};
730
+ },
731
+
732
+ exitSessionBatchSelectMode() {
733
+ if (!this.sessionBatchSelectMode) return;
734
+ this.sessionBatchSelectMode = false;
735
+ this.sessionSelectedKeys = {};
736
+ },
737
+
738
+ getSelectedBatchCount() {
739
+ return this.sessionsList.filter(s => this.sessionBatchSelectMode && this.isSessionSelectedForBatch(s)).length;
740
+ },
741
+
742
+ getVisibleBatchSelectableCount() {
743
+ return (this.visibleSessionsList || []).filter(s => this.isDeleteAvailable(s)).length;
744
+ },
745
+
746
+ isAllVisibleBatchSelected() {
747
+ const visible = (this.visibleSessionsList || []).filter(s => this.isDeleteAvailable(s));
748
+ if (visible.length === 0) return false;
749
+ return visible.every(s => this.isSessionSelectedForBatch(s) || this.sessionDeleting[this.getSessionExportKey(s)]);
750
+ },
751
+
752
+ toggleSelectAllVisibleForBatch() {
753
+ const visible = (this.visibleSessionsList || []).filter(s => this.isDeleteAvailable(s) && !this.sessionDeleting[this.getSessionExportKey(s)]);
754
+ if (visible.length === 0) return;
755
+ const allSelected = visible.every(s => this.isSessionSelectedForBatch(s));
756
+ const next = { ...this.sessionSelectedKeys };
757
+ if (allSelected) {
758
+ for (const s of visible) {
759
+ delete next[this.getSessionExportKey(s)];
760
+ }
761
+ } else {
762
+ for (const s of visible) {
763
+ next[this.getSessionExportKey(s)] = true;
764
+ }
765
+ }
766
+ this.sessionSelectedKeys = next;
767
+ },
768
+
769
+ clearSessionBatchSelection() {
770
+ this.sessionSelectedKeys = {};
771
+ },
772
+
773
+ async deleteSelectedSessions() {
774
+ if (this.sessionDeletingSelected) return;
775
+ const selected = (this.sessionsList || []).filter(s => this.isSessionSelectedForBatch(s));
776
+ if (selected.length === 0) {
777
+ if (typeof this.t === 'function') {
778
+ this.showMessage(this.t('sessions.batch.emptySelection'), 'warning');
779
+ }
780
+ return;
781
+ }
782
+ const useTrash = this.sessionTrashEnabled !== false;
783
+ this.sessionDeletingSelected = true;
784
+ let failed = 0;
785
+ const succeededKeys = [];
786
+ try {
787
+ for (const session of selected) {
788
+ const ok = await this.deleteSession(session, { batch: true });
789
+ if (ok) {
790
+ succeededKeys.push(this.getSessionExportKey(session));
791
+ } else {
792
+ failed += 1;
793
+ }
794
+ }
795
+ if (succeededKeys.length > 0) {
796
+ if (typeof this.invalidateSessionsUsageData === 'function') {
797
+ this.invalidateSessionsUsageData({ preserveList: true });
798
+ }
799
+ this.invalidateSessionTrashRequests();
800
+ // Remove succeeded sessions from the current list in order
801
+ for (const key of succeededKeys) {
802
+ const session = (this.sessionsList || []).find(s => this.getSessionExportKey(s) === key);
803
+ if (session) {
804
+ try {
805
+ await this.removeSessionFromCurrentList(session);
806
+ } catch (_) {
807
+ // Remote delete already succeeded; ignore local cleanup errors.
808
+ }
809
+ }
810
+ }
811
+ // Clear succeeded selections; keep failed ones selected
812
+ const next = {};
813
+ for (const session of selected) {
814
+ const k = this.getSessionExportKey(session);
815
+ if (failed > 0 && !succeededKeys.includes(k)) {
816
+ next[k] = true;
817
+ }
818
+ }
819
+ this.sessionSelectedKeys = next;
820
+ }
821
+ if (failed === 0) {
822
+ const okMsg = useTrash ? '已移入回收站' : '已删除';
823
+ this.showMessage(okMsg + ' · ' + succeededKeys.length, 'success');
824
+ this.sessionBatchSelectMode = false;
825
+ } else if (failed === selected.length) {
826
+ this.showMessage(this.t('sessions.batch.allFail'), 'error');
827
+ } else {
828
+ this.showMessage(this.t('sessions.batch.partialFail', { failed }), 'warning');
829
+ }
830
+ } finally {
831
+ this.sessionDeletingSelected = false;
832
+ }
708
833
  }
709
834
  };
710
835
  }
@@ -303,6 +303,7 @@ export function createSessionBrowserMethods(options = {}) {
303
303
  },
304
304
 
305
305
  onSessionSortChange() {
306
+ this.exitSessionBatchSelectMode();
306
307
  const normalized = this.normalizeSessionSortMode(this.sessionSortMode);
307
308
  this.sessionSortMode = normalized;
308
309
  if (typeof this.persistWebUiPreferences === 'function') {
@@ -445,6 +446,7 @@ export function createSessionBrowserMethods(options = {}) {
445
446
 
446
447
  setSessionSource(value) {
447
448
  if (this.sessionsLoading) return;
449
+ this.exitSessionBatchSelectMode();
448
450
  this.sessionFilterSource = value;
449
451
  this.refreshSessionPathOptions(value);
450
452
  this.persistSessionFilterCache();
@@ -478,12 +480,14 @@ export function createSessionBrowserMethods(options = {}) {
478
480
  },
479
481
 
480
482
  async onSessionPathFilterChange() {
483
+ this.exitSessionBatchSelectMode();
481
484
  this.persistSessionFilterCache();
482
485
  syncSessionsFilterUrl(this);
483
486
  await this.loadSessions();
484
487
  },
485
488
 
486
489
  async onSessionFilterChange() {
490
+ this.exitSessionBatchSelectMode();
487
491
  this.persistSessionFilterCache();
488
492
  syncSessionsFilterUrl(this);
489
493
  await this.loadSessions();
@@ -552,6 +556,7 @@ export function createSessionBrowserMethods(options = {}) {
552
556
  },
553
557
 
554
558
  async clearSessionFilters() {
559
+ this.exitSessionBatchSelectMode();
555
560
  this.sessionFilterSource = 'all';
556
561
  this.sessionPathFilter = '';
557
562
  this.sessionQuery = '';
@@ -8,7 +8,6 @@ const LEGACY_WEB_UI_PREFERENCE_KEYS = Object.freeze([
8
8
  'codexmate_prompts_sub_tab',
9
9
  'codexmate_project_claude_md_path',
10
10
  'codexmateNavState.v1',
11
- 'codexmateTaskOrchestrationTabEnabled',
12
11
  'codexmateSessionLoadNativeDialog',
13
12
  'codexmateSessionFilterSource',
14
13
  'codexmateSessionPathFilter',
@@ -63,9 +62,21 @@ function normalizeUsageTimeRange(value) {
63
62
  function normalizePromptsSubTab(value) {
64
63
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
65
64
  if (normalized === 'claude-project') return normalized;
65
+ if (normalized === 'system') return normalized;
66
66
  return 'codex';
67
67
  }
68
68
 
69
+ function normalizeSysPromptScope(value) {
70
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
71
+ if (normalized === 'project') return normalized;
72
+ return 'global';
73
+ }
74
+
75
+ function normalizeSysPromptMode(value) {
76
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
77
+ return normalized === 'append' ? 'append' : 'system';
78
+ }
79
+
69
80
  function normalizePromptPresets(value) {
70
81
  if (!Array.isArray(value)) return [];
71
82
  const seen = new Set();
@@ -187,10 +198,6 @@ function collectLegacyLocalStoragePreferences(storage) {
187
198
  preferences.configTemplateDiffConfirmEnabled = normalizeBoolean(read('codexmateConfigTemplateDiffConfirmEnabled'), true);
188
199
  found = true;
189
200
  }
190
- if (has('codexmateTaskOrchestrationTabEnabled')) {
191
- preferences.taskOrchestrationTabEnabled = normalizeBoolean(read('codexmateTaskOrchestrationTabEnabled'), true);
192
- found = true;
193
- }
194
201
  if (has('codexmateSessionLoadNativeDialog')) {
195
202
  preferences.sessionLoadNativeDialog = normalizeBoolean(read('codexmateSessionLoadNativeDialog'), false);
196
203
  found = true;
@@ -333,13 +340,14 @@ export function createWebUiPreferencesMethods(options = {}) {
333
340
  : this.configTemplateDiffConfirmEnabled !== false,
334
341
  sessionsUsageTimeRange: normalizeUsageTimeRange(hasOwn(source, 'sessionsUsageTimeRange') ? source.sessionsUsageTimeRange : this.sessionsUsageTimeRange),
335
342
  promptsSubTab: normalizePromptsSubTab(hasOwn(source, 'promptsSubTab') ? source.promptsSubTab : this.promptsSubTab),
343
+ sysPromptScope: normalizeSysPromptScope(hasOwn(source, 'sysPromptScope') ? source.sysPromptScope : this.sysPromptScope),
344
+ sysPromptMode: normalizeSysPromptMode(hasOwn(source, 'sysPromptMode') ? source.sysPromptMode : this.sysPromptMode),
336
345
  promptPresets: normalizePromptPresets(hasOwn(source, 'promptPresets') ? source.promptPresets : this.promptPresets),
337
346
  projectClaudeMdPath: typeof source.projectClaudeMdPath === 'string'
338
347
  ? source.projectClaudeMdPath
339
348
  : (typeof this.projectClaudeMdPath === 'string' ? this.projectClaudeMdPath : ''),
340
349
  sidebarCollapsed: normalizeBoolean(hasOwn(source, 'sidebarCollapsed') ? source.sidebarCollapsed : this.sidebarCollapsed, false),
341
350
  starPrompted: normalizeBoolean(hasOwn(source, 'starPrompted') ? source.starPrompted : this.starPrompted, false),
342
- taskOrchestrationTabEnabled: normalizeBoolean(hasOwn(source, 'taskOrchestrationTabEnabled') ? source.taskOrchestrationTabEnabled : this.taskOrchestrationTabEnabled, true),
343
351
  sessionLoadNativeDialog: normalizeBoolean(hasOwn(source, 'sessionLoadNativeDialog') ? source.sessionLoadNativeDialog : this.sessionLoadNativeDialog, false),
344
352
  language: typeof source.language === 'string'
345
353
  ? source.language
@@ -386,6 +394,12 @@ export function createWebUiPreferencesMethods(options = {}) {
386
394
  if (typeof source.promptsSubTab === 'string') {
387
395
  this.promptsSubTab = normalizePromptsSubTab(source.promptsSubTab);
388
396
  }
397
+ if (typeof source.sysPromptScope === 'string') {
398
+ this.sysPromptScope = normalizeSysPromptScope(source.sysPromptScope);
399
+ }
400
+ if (typeof source.sysPromptMode === 'string') {
401
+ this.sysPromptMode = normalizeSysPromptMode(source.sysPromptMode);
402
+ }
389
403
  if (Array.isArray(source.promptPresets)) {
390
404
  this.promptPresets = normalizePromptPresets(source.promptPresets);
391
405
  if (this.selectedPromptPresetId && !this.promptPresets.some(preset => preset.id === this.selectedPromptPresetId)) {
@@ -401,9 +415,6 @@ export function createWebUiPreferencesMethods(options = {}) {
401
415
  if (hasOwn(source, 'starPrompted')) {
402
416
  this.starPrompted = normalizeBoolean(source.starPrompted, false);
403
417
  }
404
- if (hasOwn(source, 'taskOrchestrationTabEnabled')) {
405
- this.taskOrchestrationTabEnabled = normalizeBoolean(source.taskOrchestrationTabEnabled, true);
406
- }
407
418
  if (hasOwn(source, 'sessionLoadNativeDialog')) {
408
419
  this.sessionLoadNativeDialog = normalizeBoolean(source.sessionLoadNativeDialog, false);
409
420
  }