Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
672a750
agent mode client and types
jwang19-atlassian Jan 13, 2026
07c980b
feat: agent mode handler
jwang19-atlassian Jan 14, 2026
6b8a61c
init local agent mode state
jwang19-atlassian Jan 14, 2026
437480a
prompt box UI
jwang19-atlassian Jan 14, 2026
252a9f7
add ask mode ui to webview
jwang19-atlassian Jan 14, 2026
6a780c5
fix: set ask mode state
jwang19-atlassian Jan 14, 2026
969a0a9
Merge branch 'main' into axon-1596-ask-mode
jwang19-atlassian Jan 15, 2026
a0ae89e
remove debug console
jwang19-atlassian Jan 15, 2026
8c77cd5
remove duplicate state setter
jwang19-atlassian Jan 15, 2026
a282c38
ut
jwang19-atlassian Jan 15, 2026
c5785b2
comment
jwang19-atlassian Jan 15, 2026
5ebfe9c
fix: tool handler check
jwang19-atlassian Jan 15, 2026
969327b
add slash style cmd for ask mode and fix the copy
jwang19-atlassian Jan 15, 2026
8cf5c18
Merge branch 'main' into axon-1596-ask-mode
jwang19-atlassian Jan 16, 2026
d7bd7ce
fix: support key down event
jwang19-atlassian Jan 16, 2026
dbb436d
Merge branch 'main' into axon-1596-ask-mode
jwang19-atlassian Jan 19, 2026
aafc42c
add agent related message handlers
jwang19-atlassian Jan 21, 2026
ea33173
use agent hook
jwang19-atlassian Jan 21, 2026
81045e8
refactor: separate prompt settings item component
jwang19-atlassian Jan 21, 2026
92578bb
agent mode section in menu
jwang19-atlassian Jan 21, 2026
12d5d53
hoist agent mode fetch call
jwang19-atlassian Jan 21, 2026
da898c1
fix: style
jwang19-atlassian Jan 21, 2026
9b5b47a
fix: prompt settings popup max width
jwang19-atlassian Jan 21, 2026
28058c7
Merge branch 'main' into axon-1596-agent-mode
jwang19-atlassian Jan 21, 2026
e86daaf
fix: use atlaskit component
jwang19-atlassian Jan 21, 2026
b8731fb
cleanup
jwang19-atlassian Jan 21, 2026
66feca7
fix: ut
jwang19-atlassian Jan 21, 2026
c623975
cleanup
jwang19-atlassian Jan 22, 2026
1fc8182
fix: ui
jwang19-atlassian Jan 23, 2026
e6433cc
Merge branch 'main' into axon-1596-agent-mode
jwang19-atlassian Jan 23, 2026
8fdaa95
cleanup
jwang19-atlassian Jan 23, 2026
31edc68
agent mode tooltip icon
jwang19-atlassian Jan 23, 2026
03e0d45
minor fix
jwang19-atlassian Jan 23, 2026
b602cb1
loading status
jwang19-atlassian Jan 23, 2026
82157ea
improve pref for init agent modes fetching
jwang19-atlassian Jan 24, 2026
15b4f3d
Merge branch 'main' into axon-1596-agent-mode
jwang19-atlassian Feb 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,490 changes: 2,393 additions & 97 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,7 @@
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
"@atlaskit/pragmatic-drag-and-drop-live-region": "^1.3.1",
"@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^3.2.5",
"@atlaskit/primitives": "^17.1.1",
"@atlaskit/radio": "^8.3.0",
"@atlaskit/renderer": "^124.1.4",
"@atlaskit/section-message": "^8.5.1",
Expand Down
250 changes: 250 additions & 0 deletions src/rovo-dev/client/rovoDevApiClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1618,4 +1618,254 @@ describe('RovoDevApiClient', () => {
);
});
});

describe('getAgentMode method', () => {
it('should return current agent mode successfully', async () => {
const mockAgentModeResponse = {
mode: 'ask' as const,
message: 'Agent mode is set to ask',
};
const mockResponse = {
status: 200,
json: jest.fn().mockResolvedValue(mockAgentModeResponse),
headers: mockStandardResponseHeaders(),
} as unknown as Response;

mockFetch.mockResolvedValue(mockResponse);

const result = await client.getAgentMode();

expect(mockFetch).toHaveBeenCalledWith('http://localhost:8080/v3/agent-mode', {
method: 'GET',
headers: {
accept: 'text/event-stream',
'Content-Type': 'application/json',
Authorization: 'Bearer sessionToken',
},
body: undefined,
});
expect(result).toEqual(mockAgentModeResponse);
expect(result.mode).toBe('ask');
});

it('should return default mode', async () => {
const mockAgentModeResponse = {
mode: 'default' as const,
message: 'Agent mode is set to default',
};
const mockResponse = {
status: 200,
json: jest.fn().mockResolvedValue(mockAgentModeResponse),
headers: mockStandardResponseHeaders(),
} as unknown as Response;

mockFetch.mockResolvedValue(mockResponse);

const result = await client.getAgentMode();

expect(result.mode).toBe('default');
expect(result.message).toBe('Agent mode is set to default');
});

it('should return plan mode', async () => {
const mockAgentModeResponse = {
mode: 'plan' as const,
message: 'Agent mode is set to plan',
};
const mockResponse = {
status: 200,
json: jest.fn().mockResolvedValue(mockAgentModeResponse),
headers: mockStandardResponseHeaders(),
} as unknown as Response;

mockFetch.mockResolvedValue(mockResponse);

const result = await client.getAgentMode();

expect(result.mode).toBe('plan');
});

it('should throw error when API call fails', async () => {
const mockResponse = {
status: 500,
statusText: 'Internal Server Error',
headers: mockStandardResponseHeaders(),
} as Response;

mockFetch.mockResolvedValue(mockResponse);

await expect(client.getAgentMode()).rejects.toThrow("Failed to fetch '/v3/agent-mode API: HTTP 500");
});

it('should throw error when API returns 404', async () => {
const mockResponse = {
status: 404,
statusText: 'Not Found',
headers: mockStandardResponseHeaders(),
} as Response;

mockFetch.mockResolvedValue(mockResponse);

await expect(client.getAgentMode()).rejects.toThrow("Failed to fetch '/v3/agent-mode API: HTTP 404");
});
});

describe('setAgentMode method', () => {
it('should set agent mode to ask successfully', async () => {
const mockSetAgentModeResponse = {
mode: 'ask' as const,
message: 'Agent mode set to ask',
};
const mockResponse = {
status: 200,
json: jest.fn().mockResolvedValue(mockSetAgentModeResponse),
headers: mockStandardResponseHeaders(),
} as unknown as Response;

mockFetch.mockResolvedValue(mockResponse);

const result = await client.setAgentMode('ask');

expect(mockFetch).toHaveBeenCalledWith('http://localhost:8080/v3/agent-mode', {
method: 'PUT',
headers: {
accept: 'text/event-stream',
'Content-Type': 'application/json',
Authorization: 'Bearer sessionToken',
},
body: JSON.stringify({ mode: 'ask' }),
});
expect(result).toEqual(mockSetAgentModeResponse);
expect(result.mode).toBe('ask');
});

it('should set agent mode to default successfully', async () => {
const mockSetAgentModeResponse = {
mode: 'default' as const,
message: 'Agent mode set to default',
};
const mockResponse = {
status: 200,
json: jest.fn().mockResolvedValue(mockSetAgentModeResponse),
headers: mockStandardResponseHeaders(),
} as unknown as Response;

mockFetch.mockResolvedValue(mockResponse);

const result = await client.setAgentMode('default');

expect(mockFetch).toHaveBeenCalledWith('http://localhost:8080/v3/agent-mode', {
method: 'PUT',
headers: {
accept: 'text/event-stream',
'Content-Type': 'application/json',
Authorization: 'Bearer sessionToken',
},
body: JSON.stringify({ mode: 'default' }),
});
expect(result.mode).toBe('default');
});

it('should set agent mode to plan successfully', async () => {
const mockSetAgentModeResponse = {
mode: 'plan' as const,
message: 'Agent mode set to plan',
};
const mockResponse = {
status: 200,
json: jest.fn().mockResolvedValue(mockSetAgentModeResponse),
headers: mockStandardResponseHeaders(),
} as unknown as Response;

mockFetch.mockResolvedValue(mockResponse);

const result = await client.setAgentMode('plan');

expect(mockFetch).toHaveBeenCalledWith('http://localhost:8080/v3/agent-mode', {
method: 'PUT',
headers: {
accept: 'text/event-stream',
'Content-Type': 'application/json',
Authorization: 'Bearer sessionToken',
},
body: JSON.stringify({ mode: 'plan' }),
});
expect(result.mode).toBe('plan');
});

it('should throw error when API call fails', async () => {
const mockResponse = {
status: 500,
statusText: 'Internal Server Error',
headers: mockStandardResponseHeaders(),
} as Response;

mockFetch.mockResolvedValue(mockResponse);

await expect(client.setAgentMode('ask')).rejects.toThrow("Failed to fetch '/v3/agent-mode API: HTTP 500");
});

it('should throw error when API returns 400 (invalid mode)', async () => {
const mockResponse = {
status: 400,
statusText: 'Bad Request',
headers: mockStandardResponseHeaders(),
} as Response;

mockFetch.mockResolvedValue(mockResponse);

await expect(client.setAgentMode('invalid' as any)).rejects.toThrow(
"Failed to fetch '/v3/agent-mode API: HTTP 400",
);
});
});

describe('getAvailableModes method', () => {
it('should return list of available modes successfully', async () => {
const mockAvailableModesResponse = {
modes: [
{ mode: 'ask', description: 'Ask questions without editing code' },
{ mode: 'default', description: 'Default agent mode' },
{ mode: 'plan', description: 'Generate plans before executing' },
],
};
const mockResponse = {
status: 200,
json: jest.fn().mockResolvedValue(mockAvailableModesResponse),
headers: mockStandardResponseHeaders(),
} as unknown as Response;

mockFetch.mockResolvedValue(mockResponse);

const result = await client.getAvailableModes();

expect(mockFetch).toHaveBeenCalledWith('http://localhost:8080/v3/available-modes', {
method: 'GET',
headers: {
accept: 'text/event-stream',
'Content-Type': 'application/json',
Authorization: 'Bearer sessionToken',
},
body: undefined,
});
expect(result).toEqual(mockAvailableModesResponse);
expect(result.modes).toHaveLength(3);
expect(result.modes[0].mode).toBe('ask');
expect(result.modes[0].description).toBe('Ask questions without editing code');
});

it('should throw error when API call fails', async () => {
const mockResponse = {
status: 500,
statusText: 'Internal Server Error',
headers: mockStandardResponseHeaders(),
} as Response;

mockFetch.mockResolvedValue(mockResponse);

await expect(client.getAvailableModes()).rejects.toThrow(
"Failed to fetch '/v3/available-modes API: HTTP 500",
);
});
});
});
38 changes: 37 additions & 1 deletion src/rovo-dev/client/rovoDevApiClient.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { RovoDevLogger } from '../util/rovoDevLogger';
import {
AgentMode,
RovoDevAvailableModesResponse,
RovoDevCancelResponse,
RovoDevChatRequest,
RovoDevGetAgentModeResponse,
RovoDevHealthcheckResponse,
RovoDevSetAgentModeRequest,
RovoDevSetAgentModeResponse,
RovoDevStatusAPIResponse,
ToolPermissionChoice,
} from './rovoDevApiClientInterfaces';
Expand Down Expand Up @@ -68,7 +73,12 @@ export class RovoDevApiClient {
): Promise<Response>;
private async fetchApi(
restApi: string,
method: 'GET' | 'DELETE' | 'POST' | 'PATCH',
method: 'POST' | 'PATCH' | 'PUT',
body?: BodyInit | null,
): Promise<Response>;
private async fetchApi(
restApi: string,
method: 'GET' | 'DELETE' | 'POST' | 'PATCH' | 'PUT',
body?: BodyInit | null,
abortSignal?: AbortSignal | null,
): Promise<Response> {
Expand Down Expand Up @@ -289,4 +299,30 @@ export class RovoDevApiClient {

await this.fetchApi('/accept-mcp-terms', 'POST', JSON.stringify(message));
}

/** Invokes the GET `/v3/agent-mode` API.
* @returns {Promise<RovoDevGetAgentModeResponse>} An object representing the current agent mode.
*/
public async getAgentMode(): Promise<RovoDevGetAgentModeResponse> {
const response = await this.fetchApi('/v3/agent-mode', 'GET');
return await response.json();
}

/** Invokes the PUT `/v3/agent-mode` API.
* @param {AgentMode} mode The agent mode to set ('ask', 'default', or 'plan').
* @returns {Promise<RovoDevSetAgentModeResponse>} An object representing the API response.
*/
public async setAgentMode(mode: AgentMode): Promise<RovoDevSetAgentModeResponse> {
const request: RovoDevSetAgentModeRequest = { mode };
const response = await this.fetchApi('/v3/agent-mode', 'PUT', JSON.stringify(request));
return await response.json();
}

/** Invokes the GET `/v3/available-modes` API.
* @returns {Promise<RovoDevAvailableModesResponse>} An object representing all available agent modes.
*/
public async getAvailableModes(): Promise<RovoDevAvailableModesResponse> {
const response = await this.fetchApi('/v3/available-modes', 'GET');
return await response.json();
}
}
25 changes: 25 additions & 0 deletions src/rovo-dev/client/rovoDevApiClientInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,28 @@ export interface RovoDevStatusAPIResponse {
}

export type ToolPermissionChoice = 'allow' | 'deny';

export type AgentMode = 'ask' | 'default' | 'plan';

export interface RovoDevGetAgentModeResponse {
mode: AgentMode;
message: string;
}

export interface RovoDevSetAgentModeRequest {
mode: AgentMode;
}

export interface RovoDevSetAgentModeResponse {
mode: AgentMode;
message: string;
}

export interface RovoDevModeInfo {
mode: string; // Use string to allow new modes to be added on Rovo Dev side
description: string;
}

export interface RovoDevAvailableModesResponse {
modes: RovoDevModeInfo[];
}
Loading
Loading