Skip to content

Commit e5ce106

Browse files
committed
Merge branch 'main' into dev/mjbvz/dashboard2
2 parents 070a04f + efbe564 commit e5ce106

23 files changed

+4122
-3374
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- **Webviews**: Place webview code in the `webviews/` directory. Use the shared `common/` code where possible.
1414
- **Commands**: Register new commands in `package.json` and implement them in `src/commands.ts` or a relevant module.
1515
- **Logging**: Use the `Logger` utility for all logging purposes. Don't use console.log or similar methods directly.
16+
- **Test Running**: Use tools over tasks over scripts to run tests.
1617

1718
## Specific Feature Practices
1819
- **Commands**: When adding a new command, consider whether it should be available in the command palette, context menus, or both. Add the appropriate menu entries in `package.json` to ensure the command is properly included, or excluded (command palette), from menus.

.vscode-test.mjs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
import { defineConfig } from "@vscode/test-cli";
3+
4+
/**
5+
* @param {string} label
6+
*/
7+
function generateConfig(label) {
8+
/** @type {import('@vscode/test-cli').TestConfiguration} */
9+
let config = {
10+
label,
11+
files: ["out/**/*.test.js"],
12+
version: "insiders",
13+
srcDir: "src",
14+
launchArgs: [
15+
"--enable-proposed-api",
16+
"--disable-extension=GitHub.vscode-pull-request-github-insiders",
17+
],
18+
// env,
19+
mocha: {
20+
ui: "bdd",
21+
color: true,
22+
timeout: 25000
23+
},
24+
};
25+
26+
return config;
27+
}
28+
29+
export default defineConfig(generateConfig("Local Tests"));

common/sessionParsing.ts

Lines changed: 124 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -160,177 +160,156 @@ export function parseToolCallDetails(
160160
},
161161
content: string
162162
): ParsedToolCallDetails {
163+
// Parse arguments once with graceful fallback
163164
let args: { command?: string, path?: string, prDescription?: string, commitMessage?: string, view_range?: unknown } = {};
164-
try {
165-
args = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {};
166-
} catch {
167-
// fallback to empty args
168-
}
165+
try { args = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {}; } catch { /* ignore */ }
169166

170167
const name = toolCall.function.name;
171168

172-
if (name === 'str_replace_editor') {
173-
if (args.command === 'view') {
174-
const parsedContent = parseDiff(content);
175-
const parsedRange = parseRange(args.view_range);
176-
if (parsedContent) {
177-
const file = parsedContent.fileA ?? parsedContent.fileB;
178-
const fileLabel = file && toFileLabel(file);
179-
return {
180-
toolName: fileLabel === '' ? 'Read repository' : 'Read',
181-
invocationMessage: fileLabel ? (`Read [](${fileLabel})` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')) : 'Read repository',
182-
pastTenseMessage: fileLabel ? (`Read [](${fileLabel})` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')) : 'Read repository',
183-
toolSpecificData: fileLabel ? {
184-
command: 'view',
185-
filePath: file,
186-
fileLabel: fileLabel,
187-
parsedContent: parsedContent,
188-
viewRange: parsedRange
189-
} : undefined
190-
};
191-
} else {
192-
const filePath = args.path;
193-
let fileLabel = filePath ? toFileLabel(filePath) : undefined;
194-
195-
if (fileLabel === undefined) {
196-
fileLabel = filePath;
197-
198-
return {
199-
toolName: fileLabel ? (`Read ${fileLabel}` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')) : 'Read repository',
200-
invocationMessage: fileLabel ? (`Read ${fileLabel}` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')) : 'Read repository',
201-
pastTenseMessage: fileLabel ? (`Read ${fileLabel}` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')) : 'Read repository',
202-
};
203-
} else if (fileLabel === '') {
204-
return {
205-
toolName: 'Read repository',
206-
invocationMessage: 'Read repository',
207-
pastTenseMessage: 'Read repository',
208-
};
209-
} else {
210-
return {
211-
toolName: `Read`,
212-
invocationMessage: (`Read ${fileLabel}` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')),
213-
pastTenseMessage: (`Read ${fileLabel}` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')),
214-
toolSpecificData: {
215-
command: 'view',
216-
filePath: filePath,
217-
fileLabel: fileLabel,
218-
viewRange: parsedRange
219-
}
220-
};
221-
}
222-
}
223-
} else {
224-
const filePath = args.path;
225-
const fileLabel = filePath && toFileLabel(filePath);
226-
const parsedRange = parseRange(args.view_range);
227-
228-
return {
229-
toolName: 'Edit',
230-
invocationMessage: fileLabel ? (`Edit [](${fileLabel})` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')) : 'Edit',
231-
pastTenseMessage: fileLabel ? (`Edit [](${fileLabel})` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')) : 'Edit',
232-
toolSpecificData: fileLabel ? {
233-
command: args.command || 'edit',
234-
filePath: filePath,
235-
fileLabel: fileLabel,
236-
viewRange: parsedRange
237-
} : undefined
238-
};
239-
}
240-
} else if (name === 'str_replace') {
241-
const filePath = args.path;
169+
// Small focused helpers to remove duplication while preserving behavior
170+
const buildReadDetails = (filePath: string | undefined, parsedRange: { start: number, end: number } | undefined, opts?: { parsedContent?: { content: string; fileA: string | undefined; fileB: string | undefined; } }): ParsedToolCallDetails => {
242171
const fileLabel = filePath && toFileLabel(filePath);
243-
244-
return {
245-
toolName: 'Edit',
246-
invocationMessage: fileLabel ? `Edit [](${fileLabel})` : `Edit ${filePath}`,
247-
pastTenseMessage: fileLabel ? `Edit [](${fileLabel})` : `Edit ${filePath}`,
248-
toolSpecificData: fileLabel ? {
249-
command: 'str_replace',
250-
filePath: filePath,
251-
fileLabel: fileLabel,
252-
} : undefined
172+
if (fileLabel === undefined || fileLabel === '') {
173+
return { toolName: 'Read repository', invocationMessage: 'Read repository', pastTenseMessage: 'Read repository' };
253174
}
254-
} else if (name === 'create') {
255-
const filePath = args.path;
256-
const fileLabel = filePath && toFileLabel(filePath);
257-
175+
const rangeSuffix = parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '';
176+
// Default helper returns bracket variant (used for generic view). Plain variant handled separately for str_replace_editor non-diff.
258177
return {
259-
toolName: 'Create',
260-
invocationMessage: fileLabel ? `Create [](${fileLabel})` : `Create File ${filePath}`,
261-
pastTenseMessage: fileLabel ? `Create [](${fileLabel})` : `Create File ${filePath}`,
262-
toolSpecificData: fileLabel ? {
263-
command: 'create',
178+
toolName: 'Read',
179+
invocationMessage: `Read [](${fileLabel})${rangeSuffix}`,
180+
pastTenseMessage: `Read [](${fileLabel})${rangeSuffix}`,
181+
toolSpecificData: {
182+
command: 'view',
264183
filePath: filePath,
265184
fileLabel: fileLabel,
266-
} : undefined
267-
}
268-
} else if (name === 'view') {
269-
const filePath = args.path;
185+
parsedContent: opts?.parsedContent,
186+
viewRange: parsedRange
187+
}
188+
};
189+
};
190+
191+
const buildEditDetails = (filePath: string | undefined, command: string, parsedRange: { start: number, end: number } | undefined, opts?: { defaultName?: string }): ParsedToolCallDetails => {
270192
const fileLabel = filePath && toFileLabel(filePath);
271-
const parsedRange = parseRange(args.view_range);
193+
const rangeSuffix = parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '';
194+
let invocationMessage: string;
195+
let pastTenseMessage: string;
196+
if (fileLabel) {
197+
invocationMessage = `Edit [](${fileLabel})${rangeSuffix}`;
198+
pastTenseMessage = `Edit [](${fileLabel})${rangeSuffix}`;
199+
} else {
200+
if (opts?.defaultName === 'Create') {
201+
invocationMessage = pastTenseMessage = `Create File ${filePath}`;
202+
} else {
203+
invocationMessage = pastTenseMessage = (opts?.defaultName || 'Edit');
204+
}
205+
invocationMessage += rangeSuffix;
206+
pastTenseMessage += rangeSuffix;
207+
}
272208

273209
return {
274-
toolName: fileLabel === '' ? 'Read repository' : 'Read',
275-
invocationMessage: fileLabel ? (`Read [](${fileLabel})` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')) : 'Read repository',
276-
pastTenseMessage: fileLabel ? (`Read [](${fileLabel})` + (parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '')) : 'Read repository',
210+
toolName: opts?.defaultName || 'Edit',
211+
invocationMessage,
212+
pastTenseMessage,
277213
toolSpecificData: fileLabel ? {
278-
command: 'view',
214+
command: command || (opts?.defaultName === 'Create' ? 'create' : (command || 'edit')),
279215
filePath: filePath,
280216
fileLabel: fileLabel,
281217
viewRange: parsedRange
282218
} : undefined
283219
};
284-
} else if (name === 'think') {
285-
const thought = (args as unknown as { thought?: string }).thought || content || 'Thought';
220+
};
221+
222+
const buildStrReplaceDetails = (filePath: string | undefined): ParsedToolCallDetails => {
223+
const fileLabel = filePath && toFileLabel(filePath);
224+
const message = fileLabel ? `Edit [](${fileLabel})` : `Edit ${filePath}`;
286225
return {
287-
toolName: 'think',
288-
invocationMessage: thought,
226+
toolName: 'Edit',
227+
invocationMessage: message,
228+
pastTenseMessage: message,
229+
toolSpecificData: fileLabel ? { command: 'str_replace', filePath, fileLabel } : undefined
289230
};
290-
} else if (name === 'report_progress') {
291-
const details: ParsedToolCallDetails = {
292-
toolName: 'Progress Update',
293-
invocationMessage: `${args.prDescription}` || content || 'Progress Update'
231+
};
232+
233+
const buildCreateDetails = (filePath: string | undefined): ParsedToolCallDetails => {
234+
const fileLabel = filePath && toFileLabel(filePath);
235+
const message = fileLabel ? `Create [](${fileLabel})` : `Create File ${filePath}`;
236+
return {
237+
toolName: 'Create',
238+
invocationMessage: message,
239+
pastTenseMessage: message,
240+
toolSpecificData: fileLabel ? { command: 'create', filePath, fileLabel } : undefined
294241
};
295-
if (args.commitMessage) {
296-
details.originMessage = `Commit: ${args.commitMessage}`;
297-
}
242+
};
243+
244+
const buildBashDetails = (bashArgs: typeof args, contentStr: string): ParsedToolCallDetails => {
245+
const command = bashArgs.command ? `$ ${bashArgs.command}` : undefined;
246+
const bashContent = [command, contentStr].filter(Boolean).join('\n');
247+
const details: ParsedToolCallDetails = { toolName: 'Run Bash command', invocationMessage: bashContent || 'Run Bash command' };
248+
if (bashArgs.command) { details.toolSpecificData = { commandLine: { original: bashArgs.command }, language: 'bash' }; }
298249
return details;
299-
} else if (name === 'bash') {
300-
const command = args.command ? `$ ${args.command}` : undefined;
301-
const bashContent = [command, content].filter(Boolean).join('\n');
302-
const details: ParsedToolCallDetails = {
303-
toolName: 'Run Bash command',
304-
invocationMessage: bashContent || 'Run Bash command',
305-
};
250+
};
306251

307-
// Use the terminal-specific data for bash commands
308-
if (args.command) {
309-
const bashToolData: BashToolData = {
310-
commandLine: {
311-
original: args.command,
312-
},
313-
language: 'bash'
314-
};
315-
details.toolSpecificData = bashToolData;
252+
switch (name) {
253+
case 'str_replace_editor': {
254+
if (args.command === 'view') {
255+
const parsedContent = parseDiff(content);
256+
const parsedRange = parseRange(args.view_range);
257+
if (parsedContent) {
258+
const file = parsedContent.fileA ?? parsedContent.fileB;
259+
const fileLabel = file && toFileLabel(file);
260+
if (fileLabel === '') {
261+
return { toolName: 'Read repository', invocationMessage: 'Read repository', pastTenseMessage: 'Read repository' };
262+
} else if (fileLabel === undefined) {
263+
return { toolName: 'Read', invocationMessage: 'Read repository', pastTenseMessage: 'Read repository' };
264+
} else {
265+
const rangeSuffix = parsedRange ? `, lines ${parsedRange.start} to ${parsedRange.end}` : '';
266+
return {
267+
toolName: 'Read',
268+
invocationMessage: `Read [](${fileLabel})${rangeSuffix}`,
269+
pastTenseMessage: `Read [](${fileLabel})${rangeSuffix}`,
270+
toolSpecificData: { command: 'view', filePath: file, fileLabel, parsedContent, viewRange: parsedRange }
271+
};
272+
}
273+
}
274+
// No diff parsed: use PLAIN (non-bracket) variant for str_replace_editor views
275+
const plainRange = parseRange(args.view_range);
276+
const fp = args.path; const fl = fp && toFileLabel(fp);
277+
if (fl === undefined || fl === '') {
278+
return { toolName: 'Read repository', invocationMessage: 'Read repository', pastTenseMessage: 'Read repository' };
279+
}
280+
const suffix = plainRange ? `, lines ${plainRange.start} to ${plainRange.end}` : '';
281+
return {
282+
toolName: 'Read',
283+
invocationMessage: `Read ${fl}${suffix}`,
284+
pastTenseMessage: `Read ${fl}${suffix}`,
285+
toolSpecificData: { command: 'view', filePath: fp, fileLabel: fl, viewRange: plainRange }
286+
};
287+
}
288+
return buildEditDetails(args.path, args.command || 'edit', parseRange(args.view_range));
316289
}
317-
return details;
318-
} else if (name === 'read_bash') {
319-
return {
320-
toolName: 'read_bash',
321-
invocationMessage: 'Read logs from Bash session'
290+
case 'str_replace':
291+
return buildStrReplaceDetails(args.path);
292+
case 'create':
293+
return buildCreateDetails(args.path);
294+
case 'view':
295+
return buildReadDetails(args.path, parseRange(args.view_range)); // generic view always bracket variant
296+
case 'think': {
297+
const thought = (args as unknown as { thought?: string }).thought || content || 'Thought';
298+
return { toolName: 'think', invocationMessage: thought };
322299
}
323-
} else if (name === 'stop_bash') {
324-
return {
325-
toolName: 'stop_bash',
326-
invocationMessage: 'Stop Bash session'
300+
case 'report_progress': {
301+
const details: ParsedToolCallDetails = { toolName: 'Progress Update', invocationMessage: `${args.prDescription}` || content || 'Progress Update' };
302+
if (args.commitMessage) { details.originMessage = `Commit: ${args.commitMessage}`; }
303+
return details;
327304
}
328-
} else {
329-
// Unknown tool type
330-
return {
331-
toolName: name || 'unknown',
332-
invocationMessage: content || name || 'unknown'
333-
};
305+
case 'bash':
306+
return buildBashDetails(args, content);
307+
case 'read_bash':
308+
return { toolName: 'read_bash', invocationMessage: 'Read logs from Bash session' };
309+
case 'stop_bash':
310+
return { toolName: 'stop_bash', invocationMessage: 'Stop Bash session' };
311+
default:
312+
return { toolName: name || 'unknown', invocationMessage: content || name || 'unknown' };
334313
}
335314
}
336315

0 commit comments

Comments
 (0)