Skip to content

Commit 9d8ed5b

Browse files
authored
Merge branch 'main' into feature/mcp
2 parents 526ed18 + 5e4435d commit 9d8ed5b

File tree

4 files changed

+139
-7
lines changed

4 files changed

+139
-7
lines changed

server/aws-lsp-codewhisperer/src/language-server/workspaceContext/IdleWorkspaceManager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { WorkspaceFolderManager } from './workspaceFolderManager'
22

33
export class IdleWorkspaceManager {
44
private static readonly idleThreshold = 30 * 60 * 1000 // 30 minutes
5-
private static lastActivityTimestamp = 0 // treat session as idle as the start
5+
private static lastActivityTimestamp = 0 // treat session as idle at the start
66

77
private constructor() {}
88

@@ -30,6 +30,10 @@ export class IdleWorkspaceManager {
3030
}
3131
}
3232

33+
public static setSessionAsIdle(): void {
34+
IdleWorkspaceManager.lastActivityTimestamp = 0
35+
}
36+
3337
public static isSessionIdle(): boolean {
3438
const currentTime = Date.now()
3539
const timeSinceLastActivity = currentTime - IdleWorkspaceManager.lastActivityTimestamp

server/aws-lsp-codewhisperer/src/language-server/workspaceContext/workspaceContextServer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ export const WorkspaceContextServer = (): Server => features => {
221221
isLoggedInUsingBearerToken(credentialsProvider) &&
222222
abTestingEnabled &&
223223
!workspaceFolderManager.getOptOutStatus() &&
224+
!workspaceFolderManager.isFeatureDisabled() &&
224225
workspaceIdentifier
225226
)
226227
}
@@ -302,7 +303,7 @@ export const WorkspaceContextServer = (): Server => features => {
302303
await evaluateABTesting()
303304
isWorkflowInitialized = true
304305

305-
workspaceFolderManager.resetAdminOptOutStatus()
306+
workspaceFolderManager.resetAdminOptOutAndFeatureDisabledStatus()
306307
if (!isUserEligibleForWorkspaceContext()) {
307308
return
308309
}

server/aws-lsp-codewhisperer/src/language-server/workspaceContext/workspaceFolderManager.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ArtifactManager } from './artifactManager'
88
import { CodeWhispererServiceToken } from '../../shared/codeWhispererService'
99
import { ListWorkspaceMetadataResponse } from '../../client/token/codewhispererbearertokenclient'
1010
import { IdleWorkspaceManager } from './IdleWorkspaceManager'
11+
import { AWSError } from 'aws-sdk'
1112

1213
describe('WorkspaceFolderManager', () => {
1314
let mockServiceManager: StubbedInstance<AmazonQTokenServiceManager>
@@ -135,4 +136,93 @@ describe('WorkspaceFolderManager', () => {
135136
)
136137
})
137138
})
139+
140+
describe('isFeatureDisabled', () => {
141+
it('should return true when feature is disabled', async () => {
142+
// Setup
143+
const workspaceFolders: WorkspaceFolder[] = [
144+
{
145+
uri: 'file:///test/workspace',
146+
name: 'test-workspace',
147+
},
148+
]
149+
150+
// Mock listWorkspaceMetadata to throw AccessDeniedException with feature not supported
151+
const mockError: AWSError = {
152+
name: 'AccessDeniedException',
153+
message: 'Feature is not supported',
154+
code: 'AccessDeniedException',
155+
time: new Date(),
156+
retryable: false,
157+
statusCode: 403,
158+
}
159+
160+
mockCodeWhispererService.listWorkspaceMetadata.rejects(mockError)
161+
162+
// Create the WorkspaceFolderManager instance
163+
workspaceFolderManager = WorkspaceFolderManager.createInstance(
164+
mockServiceManager,
165+
mockLogging,
166+
mockArtifactManager,
167+
mockDependencyDiscoverer,
168+
workspaceFolders,
169+
mockCredentialsProvider,
170+
'test-workspace-identifier'
171+
)
172+
173+
// Spy on clearAllWorkspaceResources and related methods
174+
const clearAllWorkspaceResourcesSpy = sinon.stub(
175+
workspaceFolderManager as any,
176+
'clearAllWorkspaceResources'
177+
)
178+
179+
// Act - trigger listWorkspaceMetadata which sets feature disabled state
180+
await (workspaceFolderManager as any).listWorkspaceMetadata()
181+
182+
// Assert
183+
expect(workspaceFolderManager.isFeatureDisabled()).toBe(true)
184+
185+
// Verify that clearAllWorkspaceResources was called
186+
sinon.assert.calledOnce(clearAllWorkspaceResourcesSpy)
187+
})
188+
189+
it('should return false when feature is not disabled', async () => {
190+
// Setup
191+
const workspaceFolders: WorkspaceFolder[] = [
192+
{
193+
uri: 'file:///test/workspace',
194+
name: 'test-workspace',
195+
},
196+
]
197+
198+
// Mock successful response
199+
const mockResponse: ListWorkspaceMetadataResponse = {
200+
workspaces: [
201+
{
202+
workspaceId: 'test-workspace-id',
203+
workspaceStatus: 'RUNNING',
204+
},
205+
],
206+
}
207+
208+
mockCodeWhispererService.listWorkspaceMetadata.resolves(mockResponse as any)
209+
210+
// Create the WorkspaceFolderManager instance
211+
workspaceFolderManager = WorkspaceFolderManager.createInstance(
212+
mockServiceManager,
213+
mockLogging,
214+
mockArtifactManager,
215+
mockDependencyDiscoverer,
216+
workspaceFolders,
217+
mockCredentialsProvider,
218+
'test-workspace-identifier'
219+
)
220+
221+
// Act - trigger listWorkspaceMetadata
222+
await (workspaceFolderManager as any).listWorkspaceMetadata()
223+
224+
// Assert
225+
expect(workspaceFolderManager.isFeatureDisabled()).toBe(false)
226+
})
227+
})
138228
})

server/aws-lsp-codewhisperer/src/language-server/workspaceContext/workspaceFolderManager.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export class WorkspaceFolderManager {
5555
private optOutMonitorInterval: NodeJS.Timeout | undefined
5656
private messageQueueConsumerInterval: NodeJS.Timeout | undefined
5757
private isOptedOut: boolean = false
58+
private featureDisabled: boolean = false // Serve as a server-side control. If true, stop WCS features
5859
private isCheckingRemoteWorkspaceStatus: boolean = false
5960
private isArtifactUploadedToRemoteWorkspace: boolean = false
6061

@@ -139,8 +140,13 @@ export class WorkspaceFolderManager {
139140
return this.isOptedOut
140141
}
141142

142-
resetAdminOptOutStatus(): void {
143+
resetAdminOptOutAndFeatureDisabledStatus(): void {
143144
this.isOptedOut = false
145+
this.featureDisabled = false
146+
}
147+
148+
isFeatureDisabled(): boolean {
149+
return this.featureDisabled
144150
}
145151

146152
getWorkspaceState(): WorkspaceState {
@@ -326,6 +332,7 @@ export class WorkspaceFolderManager {
326332
// Reset workspace ID to force operations to wait for new remote workspace information
327333
this.resetRemoteWorkspaceId()
328334

335+
IdleWorkspaceManager.setSessionAsIdle()
329336
this.isArtifactUploadedToRemoteWorkspace = false
330337

331338
// Set up message queue consumer
@@ -371,7 +378,9 @@ export class WorkspaceFolderManager {
371378
return resolve(false)
372379
}
373380

374-
const { metadata, optOut } = await this.listWorkspaceMetadata(this.workspaceIdentifier)
381+
const { metadata, optOut, featureDisabled } = await this.listWorkspaceMetadata(
382+
this.workspaceIdentifier
383+
)
375384

376385
if (optOut) {
377386
this.logging.log(`User opted out during initial connection`)
@@ -381,6 +390,13 @@ export class WorkspaceFolderManager {
381390
return resolve(false)
382391
}
383392

393+
if (featureDisabled) {
394+
this.logging.log(`Feature disabled during initial connection`)
395+
this.featureDisabled = true
396+
this.clearAllWorkspaceResources()
397+
return resolve(false)
398+
}
399+
384400
if (!metadata) {
385401
// Continue polling by exiting only this iteration
386402
return
@@ -437,7 +453,9 @@ export class WorkspaceFolderManager {
437453
}
438454

439455
this.logging.log(`Checking remote workspace status for workspace [${this.workspaceIdentifier}]`)
440-
const { metadata, optOut, error } = await this.listWorkspaceMetadata(this.workspaceIdentifier)
456+
const { metadata, optOut, featureDisabled, error } = await this.listWorkspaceMetadata(
457+
this.workspaceIdentifier
458+
)
441459

442460
if (optOut) {
443461
this.logging.log('User opted out, clearing all resources and starting opt-out monitor')
@@ -447,6 +465,13 @@ export class WorkspaceFolderManager {
447465
return
448466
}
449467

468+
if (featureDisabled) {
469+
this.logging.log('Feature disabled, clearing all resources and stoping server-side indexing features')
470+
this.featureDisabled = true
471+
this.clearAllWorkspaceResources()
472+
return
473+
}
474+
450475
if (error) {
451476
// Do not do anything if we received an exception but not caused by optOut
452477
return
@@ -528,7 +553,14 @@ export class WorkspaceFolderManager {
528553
if (this.optOutMonitorInterval === undefined) {
529554
const intervalId = setInterval(async () => {
530555
try {
531-
const { optOut } = await this.listWorkspaceMetadata()
556+
const { optOut, featureDisabled } = await this.listWorkspaceMetadata()
557+
558+
if (featureDisabled) {
559+
// Stop opt-out monitor when WCS feature is disabled from server-side
560+
this.featureDisabled = true
561+
clearInterval(intervalId)
562+
this.optOutMonitorInterval = undefined
563+
}
532564

533565
if (!optOut) {
534566
this.isOptedOut = false
@@ -735,10 +767,12 @@ export class WorkspaceFolderManager {
735767
private async listWorkspaceMetadata(workspaceRoot?: WorkspaceRoot): Promise<{
736768
metadata: WorkspaceMetadata | undefined | null
737769
optOut: boolean
770+
featureDisabled: boolean
738771
error: any
739772
}> {
740773
let metadata: WorkspaceMetadata | undefined | null
741774
let optOut = false
775+
let featureDisabled = false
742776
let error: any
743777
try {
744778
const params = workspaceRoot ? { workspaceRoot } : {}
@@ -754,8 +788,11 @@ export class WorkspaceFolderManager {
754788
this.logging.log(`User's administrator opted out server-side workspace context`)
755789
optOut = true
756790
}
791+
if (isAwsError(e) && e.code === 'AccessDeniedException' && e.message.includes('Feature is not supported')) {
792+
featureDisabled = true
793+
}
757794
}
758-
return { metadata, optOut, error }
795+
return { metadata, optOut, featureDisabled, error }
759796
}
760797

761798
private async createWorkspace(workspaceRoot: WorkspaceRoot): Promise<{

0 commit comments

Comments
 (0)