Skip to content

Commit b871d4f

Browse files
committed
Settings update
1 parent 09924de commit b871d4f

12 files changed

Lines changed: 305 additions & 217 deletions

File tree

extensions/copilot/package.json

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2477,11 +2477,6 @@
24772477
"title": "%github.copilot.command.clearMemories%",
24782478
"category": "Chat"
24792479
},
2480-
{
2481-
"command": "github.copilot.sessionSearch.resetConsent",
2482-
"title": "%github.copilot.command.resetSessionSearchConsent%",
2483-
"category": "Chat"
2484-
},
24852480
{
24862481
"command": "github.copilot.terminal.explainTerminalLastCommand",
24872482
"title": "%github.copilot.command.explainTerminalLastCommand%",
@@ -4015,6 +4010,27 @@
40154010
"tags": [
40164011
"experimental"
40174012
]
4013+
},
4014+
"github.copilot.chat.sessionSearch.cloudSync.enabled": {
4015+
"type": "boolean",
4016+
"default": false,
4017+
"markdownDescription": "%github.copilot.config.sessionSearch.cloudSync.enabled%",
4018+
"when": "github.copilot.sessionSearch.enabled",
4019+
"tags": [
4020+
"experimental"
4021+
]
4022+
},
4023+
"github.copilot.chat.sessionSearch.cloudSync.excludeRepositories": {
4024+
"type": "array",
4025+
"default": [],
4026+
"items": {
4027+
"type": "string"
4028+
},
4029+
"markdownDescription": "%github.copilot.config.sessionSearch.cloudSync.excludeRepositories%",
4030+
"when": "github.copilot.sessionSearch.enabled",
4031+
"tags": [
4032+
"experimental"
4033+
]
40184034
}
40194035
}
40204036
},
@@ -4793,26 +4809,6 @@
47934809
"tags": [
47944810
"advanced"
47954811
]
4796-
},
4797-
"github.copilot.chat.advanced.sessionSearch.storageLevel": {
4798-
"type": "string",
4799-
"enum": [
4800-
"none",
4801-
"local",
4802-
"user"
4803-
],
4804-
"enumDescriptions": [
4805-
"%github.copilot.config.sessionSearch.storageLevel.none%",
4806-
"%github.copilot.config.sessionSearch.storageLevel.local%",
4807-
"%github.copilot.config.sessionSearch.storageLevel.user%"
4808-
],
4809-
"default": "none",
4810-
"scope": "resource",
4811-
"markdownDescription": "%github.copilot.config.sessionSearch.storageLevel%",
4812-
"when": "github.copilot.sessionSearch.enabled",
4813-
"tags": [
4814-
"advanced"
4815-
]
48164812
}
48174813
}
48184814
}

extensions/copilot/package.nls.json

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,8 @@
171171
"copilot.chronicle.description": "Session history tools and insights",
172172
"copilot.chronicle.standup.description": "Generate a standup report from recent coding sessions",
173173
"copilot.chronicle.tips.description": "Get personalized tips based on your Copilot usage patterns",
174-
"copilot.chronicle.improve.description": "Get suggestions to improve your coding workflow",
175-
"github.copilot.command.resetSessionSearchConsent": "Reset Session Search Consent",
176-
"github.copilot.config.sessionSearch.storageLevel": "How Copilot stores your session history. Controls whether sessions are kept locally or synced to the cloud for features like `/chronicle`.",
177-
"github.copilot.config.sessionSearch.storageLevel.none": "No session storage (default)",
178-
"github.copilot.config.sessionSearch.storageLevel.local": "Keep on this device only — sessions stay local, not synced to cloud",
179-
"github.copilot.config.sessionSearch.storageLevel.user": "Sync to my account — sessions synced to cloud, visible only to you",
180-
"github.copilot.config.sessionSearch.storageLevel.repo_and_user": "Sync for team — sessions synced to cloud, visible to repo collaborators",
174+
"github.copilot.config.sessionSearch.cloudSync.enabled": "Enable cloud sync for session data. When enabled, session data is synced to your Copilot account for cross-device access.",
175+
"github.copilot.config.sessionSearch.cloudSync.excludeRepositories": "Repository patterns to exclude from cloud sync. Use exact `owner/repo` names or glob patterns like `my-org/*`. Sessions from matching repos will only be stored locally.",
181176
"copilot.workspace.explain.description": "Explain how the code in your active editor works",
182177
"copilot.workspace.edit.description": "Edit files in your workspace",
183178
"copilot.workspace.review.description": "Review the selected code in your active editor",

extensions/copilot/src/extension/chronicle/common/sessionIndexingPreference.ts

Lines changed: 30 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import * as l10n from '@vscode/l10n';
76
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
7+
import picomatch from 'picomatch';
88

99
/**
1010
* Session indexing levels — matches CLI's MissionControlIndexingLevel.
@@ -14,83 +14,55 @@ import { ConfigKey, IConfigurationService } from '../../../platform/configuratio
1414
*/
1515
export type SessionIndexingLevel = 'local' | 'user' | 'repo_and_user';
1616

17-
/**
18-
* Storage level setting values (includes 'none' for first-use notification).
19-
*/
20-
export type StorageLevelSetting = 'none' | SessionIndexingLevel;
21-
2217
/**
2318
* Manages user preferences for session indexing via VS Code settings.
2419
*
25-
* Uses the `github.copilot.chat.advanced.sessionSearch.storageLevel` setting
26-
* (workspace-scoped) instead of inline chat consent.
27-
*
28-
* When the setting is 'none' (default), a one-time notification is shown
29-
* on first `/chronicle` use, with buttons to set the value.
20+
* Two settings control behavior:
21+
* - `chat.sessionSearch.localIndex.enabled` (experimental, ExP) — enables local
22+
* SQLite tracking and /chronicle commands
23+
* - `chat.advanced.sessionSearch.cloudSync.enabled` (team-internal) — enables
24+
* cloud upload to Mission Control
25+
* - `chat.advanced.sessionSearch.cloudSync.excludeRepositories` — repo patterns
26+
* to exclude from cloud sync
3027
*/
3128
export class SessionIndexingPreference {
3229

33-
/** Track whether we've shown the notification in this session to avoid spamming. */
34-
private _notificationShown = false;
35-
3630
constructor(
3731
private readonly _configService: IConfigurationService,
3832
) { }
3933

4034
/**
41-
* Get the current storage level from settings.
42-
* Returns the level, or undefined if set to 'none' (not yet configured).
35+
* Get the effective storage level for a given repo. *
36+
* - If cloud sync is enabled and repo is not excluded → 'user'
37+
* - Otherwise → 'local'
4338
*/
44-
getStorageLevel(): SessionIndexingLevel | undefined {
45-
const value = this._configService.getConfig(ConfigKey.TeamInternal.SessionSearchStorageLevel);
46-
if (value === 'none' || !value) {
47-
return undefined;
39+
getStorageLevel(repoNwo?: string): SessionIndexingLevel {
40+
if (this.hasCloudConsent(repoNwo)) {
41+
return 'user';
4842
}
49-
return value as SessionIndexingLevel;
50-
}
51-
52-
/**
53-
* Check if the user needs to be prompted (setting is 'none').
54-
*/
55-
needsPrompt(): boolean {
56-
const value = this._configService.getConfig(ConfigKey.TeamInternal.SessionSearchStorageLevel);
57-
return value === 'none' || !value;
43+
return 'local';
5844
}
5945

6046
/**
61-
* Show a one-time notification asking the user to configure session storage.
62-
* Uses vscode.window.showInformationMessage with action buttons.
63-
*
64-
* Returns the chosen level, or undefined if dismissed.
47+
* Check if cloud sync is enabled for a given repo.
48+
* Returns true if cloudSync.enabled is true AND the repo is not excluded.
6549
*/
66-
async showFirstUseNotification(): Promise<SessionIndexingLevel | undefined> {
67-
if (this._notificationShown) {
68-
return undefined;
50+
hasCloudConsent(repoNwo?: string): boolean {
51+
if (!this._configService.getConfig(ConfigKey.TeamInternal.SessionSearchCloudSyncEnabled)) {
52+
return false;
6953
}
70-
this._notificationShown = true;
71-
72-
// Dynamic import to avoid circular deps — vscode is only available at runtime
73-
const vscode = await import('vscode');
74-
75-
const openSettings = l10n.t('Open Settings');
76-
77-
const choice = await vscode.window.showInformationMessage(
78-
l10n.t('Configure how Copilot stores your session history. This enables features like /chronicle.'),
79-
openSettings,
80-
);
8154

82-
if (choice === openSettings) {
83-
await vscode.commands.executeCommand('workbench.action.openSettings', 'github.copilot.chat.advanced.sessionSearch.storageLevel');
55+
if (repoNwo) {
56+
const excludePatterns = this._configService.getConfig(ConfigKey.TeamInternal.SessionSearchCloudSyncExcludeRepositories);
57+
if (excludePatterns && excludePatterns.length > 0) {
58+
for (const pattern of excludePatterns) {
59+
if (pattern === repoNwo || picomatch.isMatch(repoNwo, pattern)) {
60+
return false;
61+
}
62+
}
63+
}
8464
}
8565

86-
return undefined;
87-
}
88-
89-
/**
90-
* Check if the current storage level enables cloud sync.
91-
*/
92-
hasCloudConsent(): boolean {
93-
const level = this.getStorageLevel();
94-
return level === 'user' || level === 'repo_and_user';
66+
return true;
9567
}
9668
}

extensions/copilot/src/extension/chronicle/common/test/sessionIndexingPreference.spec.ts

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,75 +6,79 @@
66
import { describe, expect, it } from 'vitest';
77
import { SessionIndexingPreference } from '../sessionIndexingPreference';
88

9-
function createMockConfigService(storageLevel: string = 'none') {
9+
function createMockConfigService(opts: {
10+
localIndexEnabled?: boolean;
11+
cloudSyncEnabled?: boolean;
12+
excludeRepositories?: string[];
13+
} = {}) {
14+
const configs: Record<string, unknown> = {};
15+
// Map by fullyQualifiedId
16+
configs['github.copilot.chat.sessionSearch.localIndex.enabled'] = opts.localIndexEnabled ?? false;
17+
configs['github.copilot.chat.advanced.sessionSearch.cloudSync.enabled'] = opts.cloudSyncEnabled ?? false;
18+
configs['github.copilot.chat.advanced.sessionSearch.cloudSync.excludeRepositories'] = opts.excludeRepositories ?? [];
19+
1020
return {
11-
getConfig: (key: unknown) => {
12-
if (typeof key === 'object' && key !== null && 'key' in key) {
13-
return storageLevel;
14-
}
15-
return storageLevel;
16-
},
21+
getConfig: (key: { fullyQualifiedId: string }) => configs[key.fullyQualifiedId],
1722
} as unknown as import('../../../../platform/configuration/common/configurationService').IConfigurationService;
1823
}
1924

2025
describe('SessionIndexingPreference', () => {
21-
it('returns undefined when storage level is none', () => {
22-
const config = createMockConfigService('none');
23-
const pref = new SessionIndexingPreference(config);
24-
expect(pref.getStorageLevel()).toBeUndefined();
25-
});
26-
27-
it('returns local when configured', () => {
28-
const config = createMockConfigService('local');
29-
const pref = new SessionIndexingPreference(config);
26+
it('getStorageLevel returns local when no cloud sync', () => {
27+
const pref = new SessionIndexingPreference(createMockConfigService({ localIndexEnabled: true }));
3028
expect(pref.getStorageLevel()).toBe('local');
3129
});
3230

33-
it('returns user when configured', () => {
34-
const config = createMockConfigService('user');
35-
const pref = new SessionIndexingPreference(config);
31+
it('getStorageLevel returns user when cloud sync enabled', () => {
32+
const pref = new SessionIndexingPreference(createMockConfigService({
33+
localIndexEnabled: true,
34+
cloudSyncEnabled: true,
35+
}));
3636
expect(pref.getStorageLevel()).toBe('user');
3737
});
3838

39-
it('returns repo_and_user when configured', () => {
40-
const config = createMockConfigService('repo_and_user');
41-
const pref = new SessionIndexingPreference(config);
42-
expect(pref.getStorageLevel()).toBe('repo_and_user');
43-
});
44-
45-
it('needsPrompt returns true when none', () => {
46-
const config = createMockConfigService('none');
47-
const pref = new SessionIndexingPreference(config);
48-
expect(pref.needsPrompt()).toBe(true);
39+
it('getStorageLevel returns local for excluded repo', () => {
40+
const pref = new SessionIndexingPreference(createMockConfigService({
41+
localIndexEnabled: true,
42+
cloudSyncEnabled: true,
43+
excludeRepositories: ['my-org/private-repo'],
44+
}));
45+
expect(pref.getStorageLevel('my-org/private-repo')).toBe('local');
4946
});
5047

51-
it('needsPrompt returns false when configured', () => {
52-
const config = createMockConfigService('user');
53-
const pref = new SessionIndexingPreference(config);
54-
expect(pref.needsPrompt()).toBe(false);
48+
it('getStorageLevel returns user for non-excluded repo', () => {
49+
const pref = new SessionIndexingPreference(createMockConfigService({
50+
localIndexEnabled: true,
51+
cloudSyncEnabled: true,
52+
excludeRepositories: ['my-org/private-repo'],
53+
}));
54+
expect(pref.getStorageLevel('microsoft/vscode')).toBe('user');
5555
});
5656

57-
it('hasCloudConsent returns true for user', () => {
58-
const config = createMockConfigService('user');
59-
const pref = new SessionIndexingPreference(config);
60-
expect(pref.hasCloudConsent()).toBe(true);
57+
it('hasCloudConsent returns false when cloud sync disabled', () => {
58+
const pref = new SessionIndexingPreference(createMockConfigService({ cloudSyncEnabled: false }));
59+
expect(pref.hasCloudConsent()).toBe(false);
6160
});
6261

63-
it('hasCloudConsent returns true for repo_and_user', () => {
64-
const config = createMockConfigService('repo_and_user');
65-
const pref = new SessionIndexingPreference(config);
62+
it('hasCloudConsent returns true when cloud sync enabled', () => {
63+
const pref = new SessionIndexingPreference(createMockConfigService({ cloudSyncEnabled: true }));
6664
expect(pref.hasCloudConsent()).toBe(true);
6765
});
6866

69-
it('hasCloudConsent returns false for local', () => {
70-
const config = createMockConfigService('local');
71-
const pref = new SessionIndexingPreference(config);
72-
expect(pref.hasCloudConsent()).toBe(false);
67+
it('hasCloudConsent returns false for excluded repo', () => {
68+
const pref = new SessionIndexingPreference(createMockConfigService({
69+
cloudSyncEnabled: true,
70+
excludeRepositories: ['my-org/*'],
71+
}));
72+
expect(pref.hasCloudConsent('my-org/secret-repo')).toBe(false);
7373
});
7474

75-
it('hasCloudConsent returns false for none', () => {
76-
const config = createMockConfigService('none');
77-
const pref = new SessionIndexingPreference(config);
78-
expect(pref.hasCloudConsent()).toBe(false);
75+
it('hasCloudConsent supports glob patterns', () => {
76+
const pref = new SessionIndexingPreference(createMockConfigService({
77+
cloudSyncEnabled: true,
78+
excludeRepositories: ['private-org/*'],
79+
}));
80+
expect(pref.hasCloudConsent('private-org/repo-a')).toBe(false);
81+
expect(pref.hasCloudConsent('private-org/repo-b')).toBe(false);
82+
expect(pref.hasCloudConsent('public-org/repo-a')).toBe(true);
7983
});
8084
});

0 commit comments

Comments
 (0)