Skip to content

Commit 100aa44

Browse files
Copilotalexr00joshspicer
authored
Add "Start Coding Agent Session" code action for TODO comments (#7678)
* Initial plan * Add Start Coding Agent Session code action for TODO comments * Fix formatting issues and finalize TODO to coding agent feature * 💄 * Complete TODO coding agent feature by adding command registration to package.json Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> * Revert "Complete TODO coding agent feature by adding command registration to package.json" This reverts commit 8b31e74. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Alex Ross <38270282+alexr00@users.noreply.github.com> Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com>
1 parent a23d6f2 commit 100aa44

4 files changed

Lines changed: 117 additions & 7 deletions

File tree

src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ async function init(
235235
const layout = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<string>(FILE_LIST_LAYOUT);
236236
await vscode.commands.executeCommand('setContext', 'fileListLayout:flat', layout === 'flat');
237237

238-
const issuesFeatures = new IssueFeatureRegistrar(git, reposManager, reviewsManager, context, telemetry);
238+
const issuesFeatures = new IssueFeatureRegistrar(git, reposManager, reviewsManager, context, telemetry, copilotRemoteAgentManager);
239239
context.subscriptions.push(issuesFeatures);
240240
await issuesFeatures.initialize();
241241

src/issues/issueFeatureRegistrar.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { ITelemetry } from '../common/telemetry';
2424
import { fromRepoUri, RepoUriParams, Schemes, toNewIssueUri } from '../common/uri';
2525
import { EXTENSION_ID } from '../constants';
2626
import { OctokitCommon } from '../github/common';
27+
import { CopilotRemoteAgentManager } from '../github/copilotRemoteAgent';
2728
import { FolderRepositoryManager, PullRequestDefaults } from '../github/folderRepositoryManager';
2829
import { IProject } from '../github/interface';
2930
import { IssueModel } from '../github/issueModel';
@@ -91,6 +92,7 @@ export class IssueFeatureRegistrar extends Disposable {
9192
private reviewsManager: ReviewsManager,
9293
private context: vscode.ExtensionContext,
9394
private telemetry: ITelemetry,
95+
private copilotRemoteAgentManager: CopilotRemoteAgentManager,
9496
) {
9597
super();
9698
this._stateManager = new StateManager(gitAPI, this.manager, this.context);
@@ -140,6 +142,19 @@ export class IssueFeatureRegistrar extends Disposable {
140142
this,
141143
),
142144
);
145+
this._register(
146+
vscode.commands.registerCommand(
147+
'issue.startCodingAgentFromTodo',
148+
(todoInfo?: { document: vscode.TextDocument; lineNumber: number; line: string; insertIndex: number; range: vscode.Range }) => {
149+
/* __GDPR__
150+
"issue.startCodingAgentFromTodo" : {}
151+
*/
152+
this.telemetry.sendTelemetryEvent('issue.startCodingAgentFromTodo');
153+
return this.startCodingAgentFromTodo(todoInfo);
154+
},
155+
this,
156+
),
157+
);
143158
this._register(
144159
vscode.commands.registerCommand(
145160
'issue.copyGithubPermalink',
@@ -548,7 +563,7 @@ export class IssueFeatureRegistrar extends Disposable {
548563
vscode.languages.registerHoverProvider('*', new UserHoverProvider(this.manager, this.telemetry)),
549564
);
550565
this._register(
551-
vscode.languages.registerCodeActionsProvider('*', new IssueTodoProvider(this.context), { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }),
566+
vscode.languages.registerCodeActionsProvider('*', new IssueTodoProvider(this.context, this.copilotRemoteAgentManager), { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }),
552567
);
553568
});
554569
}
@@ -1453,4 +1468,34 @@ ${options?.body ?? ''}\n
14531468
}
14541469
return undefined;
14551470
}
1471+
1472+
async startCodingAgentFromTodo(todoInfo?: { document: vscode.TextDocument; lineNumber: number; line: string; insertIndex: number; range: vscode.Range }) {
1473+
if (!todoInfo) {
1474+
return;
1475+
}
1476+
1477+
const { document, line, insertIndex } = todoInfo;
1478+
1479+
// Extract the TODO text after the trigger word
1480+
const todoText = line.substring(insertIndex).trim();
1481+
1482+
if (!todoText) {
1483+
vscode.window.showWarningMessage(vscode.l10n.t('No task description found in TODO comment'));
1484+
return;
1485+
}
1486+
1487+
// Create a prompt for the coding agent
1488+
const relativePath = vscode.workspace.asRelativePath(document.uri);
1489+
const prompt = vscode.l10n.t('Work on TODO: {0} (from {1})', todoText, relativePath);
1490+
1491+
// Start the coding agent session
1492+
try {
1493+
await this.copilotRemoteAgentManager.commandImpl({
1494+
userPrompt: prompt,
1495+
source: 'todo'
1496+
});
1497+
} catch (error) {
1498+
vscode.window.showErrorMessage(vscode.l10n.t('Failed to start coding agent session: {0}', error.message));
1499+
}
1500+
}
14561501
}

src/issues/issueTodoProvider.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
import * as vscode from 'vscode';
77
import { CREATE_ISSUE_TRIGGERS, ISSUES_SETTINGS_NAMESPACE } from '../common/settingKeys';
88
import { escapeRegExp } from '../common/utils';
9+
import { CopilotRemoteAgentManager } from '../github/copilotRemoteAgent';
910
import { ISSUE_OR_URL_EXPRESSION } from '../github/utils';
1011
import { MAX_LINE_LENGTH } from './util';
1112

1213
export class IssueTodoProvider implements vscode.CodeActionProvider {
1314
private expression: RegExp | undefined;
1415

15-
constructor(context: vscode.ExtensionContext) {
16+
constructor(
17+
context: vscode.ExtensionContext,
18+
private copilotRemoteAgentManager: CopilotRemoteAgentManager
19+
) {
1620
context.subscriptions.push(
1721
vscode.workspace.onDidChangeConfiguration(() => {
1822
this.updateTriggers();
@@ -45,21 +49,37 @@ export class IssueTodoProvider implements vscode.CodeActionProvider {
4549
const match = truncatedLine.match(this.expression);
4650
const search = match?.index ?? -1;
4751
if (search >= 0 && match) {
48-
const codeAction: vscode.CodeAction = new vscode.CodeAction(
52+
// Create GitHub Issue action
53+
const createIssueAction: vscode.CodeAction = new vscode.CodeAction(
4954
vscode.l10n.t('Create GitHub Issue'),
5055
vscode.CodeActionKind.QuickFix,
5156
);
52-
codeAction.ranges = [new vscode.Range(lineNumber, search, lineNumber, search + match[0].length)];
57+
createIssueAction.ranges = [new vscode.Range(lineNumber, search, lineNumber, search + match[0].length)];
5358
const indexOfWhiteSpace = truncatedLine.substring(search).search(/\s/);
5459
const insertIndex =
5560
search +
5661
(indexOfWhiteSpace > 0 ? indexOfWhiteSpace : truncatedLine.match(this.expression)![0].length);
57-
codeAction.command = {
62+
createIssueAction.command = {
5863
title: vscode.l10n.t('Create GitHub Issue'),
5964
command: 'issue.createIssueFromSelection',
6065
arguments: [{ document, lineNumber, line, insertIndex, range }],
6166
};
62-
codeActions.push(codeAction);
67+
codeActions.push(createIssueAction);
68+
69+
// Start Coding Agent Session action (if copilot manager is available)
70+
if (this.copilotRemoteAgentManager) {
71+
const startAgentAction: vscode.CodeAction = new vscode.CodeAction(
72+
vscode.l10n.t('Start Coding Agent Session'),
73+
vscode.CodeActionKind.QuickFix,
74+
);
75+
startAgentAction.ranges = [new vscode.Range(lineNumber, search, lineNumber, search + match[0].length)];
76+
startAgentAction.command = {
77+
title: vscode.l10n.t('Start Coding Agent Session'),
78+
command: 'issue.startCodingAgentFromTodo',
79+
arguments: [{ document, lineNumber, line, insertIndex, range }],
80+
};
81+
codeActions.push(startAgentAction);
82+
}
6383
break;
6484
}
6585
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { default as assert } from 'assert';
7+
import * as vscode from 'vscode';
8+
import { IssueTodoProvider } from '../../issues/issueTodoProvider';
9+
10+
describe('IssueTodoProvider', function () {
11+
it('should provide both actions when CopilotRemoteAgentManager is available', async function () {
12+
const mockContext = {
13+
subscriptions: []
14+
} as any as vscode.ExtensionContext;
15+
16+
const mockCopilotManager = {} as any; // Mock CopilotRemoteAgentManager
17+
18+
const provider = new IssueTodoProvider(mockContext, mockCopilotManager);
19+
20+
// Create a mock document with TODO comment
21+
const document = {
22+
lineAt: (line: number) => ({ text: line === 1 ? ' // TODO: Fix this' : 'function test() {' }),
23+
lineCount: 4
24+
} as vscode.TextDocument;
25+
26+
const range = new vscode.Range(1, 0, 1, 20);
27+
const context = {
28+
only: vscode.CodeActionKind.QuickFix
29+
} as vscode.CodeActionContext;
30+
31+
const actions = await provider.provideCodeActions(document, range, context, new vscode.CancellationTokenSource().token);
32+
33+
assert.strictEqual(actions.length, 2);
34+
35+
// Find the actions
36+
const createIssueAction = actions.find(a => a.title === 'Create GitHub Issue');
37+
const startAgentAction = actions.find(a => a.title === 'Start Coding Agent Session');
38+
39+
assert.ok(createIssueAction, 'Should have Create GitHub Issue action');
40+
assert.ok(startAgentAction, 'Should have Start Coding Agent Session action');
41+
42+
assert.strictEqual(createIssueAction?.command?.command, 'issue.createIssueFromSelection');
43+
assert.strictEqual(startAgentAction?.command?.command, 'issue.startCodingAgentFromTodo');
44+
});
45+
});

0 commit comments

Comments
 (0)