Skip to content

Commit 88f11ff

Browse files
committed
Misc refinements
Signed-off-by: James Ostrander <jostrand@redhat.com>
1 parent 4638e8c commit 88f11ff

4 files changed

Lines changed: 63 additions & 30 deletions

File tree

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,30 @@ When a launch configuration has a `preLaunchTask` that uses input variables (`${
264264

265265
This is because background tasks and task dependencies require VS Code's native task orchestration.
266266

267+
### Go Debug Output Capture
268+
269+
The Go debug adapter (Delve) writes test output directly to stdout/stderr rather than routing it through the Debug Adapter Protocol. When Ignition MCP overrides `console: "integratedTerminal"` to `"internalConsole"` for output capture, this output is lost because there's no terminal to receive it.
270+
271+
**Symptoms**: Debug session runs successfully but no output appears in the debug console or in `get_debug_output` results, even though running the same configuration manually shows output.
272+
273+
**Workaround**: Disable the console override for Go launch configurations:
274+
275+
```json
276+
{
277+
"name": "Run Tests",
278+
"type": "go",
279+
"request": "launch",
280+
"mode": "test",
281+
"program": "${workspaceFolder}/...",
282+
"console": "integratedTerminal",
283+
"mcp": {
284+
"preserveConsole": true
285+
}
286+
}
287+
```
288+
289+
With `preserveConsole: true`, the terminal is created and output is visible, but Ignition MCP cannot capture it programmatically.
290+
267291
## 📄 License
268292

269293
MIT

src/launch/launchManager.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -229,19 +229,26 @@ export class LaunchManager implements vscode.Disposable {
229229

230230
private handleDebugEvent(sessionId: string, event: string | undefined, body: Record<string, unknown> | undefined) {
231231
const info = this.sessions.get(sessionId);
232-
if (!body) return;
233232
const previousState = info?.state;
234233
switch (event) {
235234
case 'initialized':
236235
if (info) {
237236
info.state = 'running';
238237
}
239238
break;
239+
case 'exited':
240+
case 'terminated':
241+
if (info) {
242+
info.state = 'terminated';
243+
}
244+
break;
240245
case 'output':
241-
this.captureOutput(sessionId, body);
246+
if (body) {
247+
this.captureOutput(sessionId, body);
248+
}
242249
break;
243250
case 'stopped':
244-
if (info) {
251+
if (info && body) {
245252
info.state = 'paused';
246253
info.stopReason = body.reason as string | undefined;
247254
info.stoppedThreadId = body.threadId as number | undefined;
@@ -260,12 +267,6 @@ export class LaunchManager implements vscode.Disposable {
260267
info.stoppedThreadId = undefined;
261268
}
262269
break;
263-
case 'exited':
264-
case 'terminated':
265-
if (info) {
266-
info.state = 'terminated';
267-
}
268-
break;
269270
}
270271
if (info && previousState !== info.state) {
271272
this.notifyStateChange(sessionId, info.state, info);

src/server/mcpServer.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ export class MCPServer {
413413
private registerTaskTool(task: TaskInfo, allInputs: InputDefinition[]) {
414414
const toolName = this.sanitizeToolName(task.name, 'task_');
415415
const isBackground = task.isBackground;
416+
const isInteractive = task.mcpOptions?.interactive ?? false;
416417
const description = this.buildTaskDescription(task);
417418
const hasInputs = allInputs.length > 0;
418419
const inputSchema = hasInputs ? this.buildInputSchema(allInputs) : {};
@@ -433,7 +434,9 @@ export class MCPServer {
433434
userWillBePrompted = true;
434435
}
435436
}
436-
if (isBackground) {
437+
// Background tasks and interactive tasks both return immediately
438+
// Interactive tasks can't capture output and may run for arbitrary durations
439+
if (isBackground || isInteractive) {
437440
const result = await this.taskManager.runTask(task.name, inputValues, task.mcpOptions);
438441
if (!result.success) {
439442
return {
@@ -446,13 +449,19 @@ export class MCPServer {
446449
}
447450
const response: Record<string, unknown> = {
448451
status: 'started',
449-
message: `Background task "${task.name}" started. Use await_task to wait for completion.`,
452+
message: isInteractive
453+
? `Interactive task "${task.name}" started. Use await_task to wait for completion.`
454+
: `Background task "${task.name}" started. Use await_task to wait for completion.`,
450455
executionId: result.executionId,
451-
isBackground: true
456+
isBackground: isBackground,
457+
isInteractive: isInteractive
452458
};
453459
if (userWillBePrompted) {
454460
response.note = 'User will be prompted for missing input values in VS Code.';
455461
}
462+
if (isInteractive) {
463+
response.outputNote = 'Output not captured in interactive mode.';
464+
}
456465
return {
457466
content: [{
458467
type: 'text' as const,
@@ -617,6 +626,10 @@ export class MCPServer {
617626
response.consoleOverridden = true;
618627
response.consoleNote = 'Console mode was changed to internalConsole for output capture.';
619628
}
629+
if (config.mcpOptions?.preserveConsole) {
630+
response.outputNotCaptured = true;
631+
response.outputNote = 'Output runs in terminal for human visibility. get_debug_output will not return results.';
632+
}
620633
if (userWillBePrompted) {
621634
response.userPrompted = true;
622635
response.note = 'User will be prompted for missing input values in VS Code. Use get_debug_status to check session state.';
@@ -646,6 +659,9 @@ export class MCPServer {
646659
parts.push(`Runs "${config.preLaunchTask}" task first.`);
647660
}
648661
parts.push('(starts and returns immediately)');
662+
if (config.mcpOptions?.preserveConsole) {
663+
parts.push('[OUTPUT NOT CAPTURED: runs in terminal for human visibility]');
664+
}
649665
return parts.join(' ');
650666
}
651667

src/tasks/taskManager.ts

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -470,8 +470,9 @@ export class TaskManager implements vscode.Disposable {
470470
inputValues?: Record<string, string>
471471
): Promise<TaskRunResult> {
472472
try {
473-
let taskToRun = task;
473+
let taskToRun: vscode.Task;
474474
if (inputValues && Object.keys(inputValues).length > 0) {
475+
// Need to substitute input values - create a modified task
475476
const rawCommand = this.getRawCommand(task);
476477
let resolvedCommand = rawCommand;
477478
for (const [inputId, value] of Object.entries(inputValues)) {
@@ -500,20 +501,10 @@ export class TaskManager implements vscode.Disposable {
500501
focus: true
501502
};
502503
} else {
503-
const shellExec = task.execution;
504-
taskToRun = new vscode.Task(
505-
task.definition,
506-
task.scope || vscode.TaskScope.Workspace,
507-
task.name,
508-
task.source,
509-
shellExec,
510-
task.problemMatchers
511-
);
512-
taskToRun.presentationOptions = {
513-
...task.presentationOptions,
514-
reveal: vscode.TaskRevealKind.Always,
515-
focus: true
516-
};
504+
// No input substitution needed - use the original task directly
505+
// This preserves VS Code's internal task definition and avoids
506+
// issues where recreating the task loses the command
507+
taskToRun = task;
517508
}
518509
this.interactiveExecutionIds.add(executionId);
519510
const execution = await vscode.tasks.executeTask(taskToRun);
@@ -739,9 +730,11 @@ export class TaskManager implements vscode.Disposable {
739730
const execId = this.findExecutionId(e.execution);
740731
if (execId) {
741732
this.log(`Found execution ID ${execId} for ended task "${taskName}"`);
733+
// Clean up tracking structures (only here, not in onTaskProcessEnded)
742734
this.activeExecutions.delete(execId);
743735
this.executionToId.delete(e.execution);
744736
this.interactiveExecutionIds.delete(execId);
737+
// Update status if not already set by onTaskProcessEnded
745738
const info = this.executions.get(execId);
746739
if (info && info.status === 'running') {
747740
info.status = 'completed';
@@ -759,6 +752,7 @@ export class TaskManager implements vscode.Disposable {
759752
const execId = this.findExecutionId(e.execution);
760753
if (execId) {
761754
this.log(`Found execution ID ${execId} for process-ended task "${taskName}"`);
755+
// Only update status/exitCode here - cleanup happens in onTaskEnded
762756
const info = this.executions.get(execId);
763757
if (info) {
764758
info.exitCode = e.exitCode;
@@ -768,9 +762,7 @@ export class TaskManager implements vscode.Disposable {
768762
this.log(`Updated status to '${info.status}' with exit code ${e.exitCode} for execution ${execId}`);
769763
}
770764
}
771-
this.activeExecutions.delete(execId);
772-
this.executionToId.delete(e.execution);
773-
this.interactiveExecutionIds.delete(execId);
765+
// Don't clean up here - let onTaskEnded do it to avoid race condition
774766
} else {
775767
this.log(`No execution ID found for process-ended task "${taskName}" (may be external task)`);
776768
}

0 commit comments

Comments
 (0)