vscode-terminal-mcp 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/extension.js CHANGED
@@ -5389,6 +5389,13 @@ var zodToJsonSchema = (schema, options) => {
5389
5389
  };
5390
5390
 
5391
5391
  // src/mcp/tools/schemas.ts
5392
+ var coerceBoolean = external_exports.preprocess(
5393
+ (val) => {
5394
+ if (typeof val === "string") return val.toLowerCase() === "true";
5395
+ return val;
5396
+ },
5397
+ external_exports.boolean()
5398
+ );
5392
5399
  var terminalCreateSchema = external_exports.object({
5393
5400
  name: external_exports.string().min(1).describe("Display name for the terminal tab"),
5394
5401
  cwd: external_exports.string().optional().describe("Working directory for the terminal"),
@@ -5400,7 +5407,7 @@ var terminalExecuteSchema = external_exports.object({
5400
5407
  sessionId: external_exports.string().min(1).describe("Session ID of the target terminal"),
5401
5408
  command: external_exports.string().min(1).describe("Command to execute"),
5402
5409
  timeoutMs: external_exports.coerce.number().min(1e3).max(3e5).optional().default(3e4).describe("Timeout in milliseconds (default: 30000, max: 300000)"),
5403
- waitForCompletion: external_exports.coerce.boolean().optional().default(true).describe("Wait for command to complete before returning (default: true)")
5410
+ waitForCompletion: coerceBoolean.optional().default(true).describe("Wait for command to complete before returning (default: true)")
5404
5411
  });
5405
5412
  var terminalReadOutputSchema = external_exports.object({
5406
5413
  sessionId: external_exports.string().min(1).describe("Session ID of the target terminal"),
@@ -5421,7 +5428,7 @@ var terminalRunSchema = external_exports.object({
5421
5428
  shell: external_exports.string().optional().describe("Override shell (e.g., /bin/zsh, /bin/bash)"),
5422
5429
  agentId: external_exports.string().optional().describe("Identifier for the owning agent/subagent"),
5423
5430
  timeoutMs: external_exports.coerce.number().min(1e3).max(3e5).optional().default(3e4).describe("Timeout in milliseconds (default: 30000, max: 300000)"),
5424
- waitForCompletion: external_exports.coerce.boolean().optional().default(true).describe("Wait for command to complete before returning (default: true)")
5431
+ waitForCompletion: coerceBoolean.optional().default(true).describe("Wait for command to complete before returning (default: true)")
5425
5432
  });
5426
5433
  var terminalSendInputSchema = external_exports.object({
5427
5434
  sessionId: external_exports.string().min(1).describe("Session ID of the target terminal"),
@@ -5518,11 +5525,12 @@ async function handleTerminalExecute(params, sessionManager2) {
5518
5525
  async function handleTerminalRun(params, sessionManager2) {
5519
5526
  const input = terminalRunSchema.parse(params);
5520
5527
  let sessionId;
5528
+ let isNewSession = false;
5521
5529
  const existing = sessionManager2.listSessions(input.agentId);
5522
5530
  for (const s of existing) {
5523
5531
  if (!s.isActive || input.cwd && s.cwd !== input.cwd) continue;
5524
- const session2 = sessionManager2.getSession(s.sessionId);
5525
- if (session2 && !session2.isBusy) {
5532
+ const session = sessionManager2.getSession(s.sessionId);
5533
+ if (session && !session.isBusy) {
5526
5534
  sessionId = s.sessionId;
5527
5535
  break;
5528
5536
  }
@@ -5536,49 +5544,43 @@ async function handleTerminalRun(params, sessionManager2) {
5536
5544
  agentId: input.agentId
5537
5545
  });
5538
5546
  sessionId = sessionInfo.sessionId;
5547
+ isNewSession = true;
5548
+ }
5549
+ if (isNewSession) {
5550
+ return new Promise((resolve) => {
5551
+ setTimeout(async () => {
5552
+ const result = await executeCommand(sessionId, input, sessionManager2);
5553
+ resolve(result);
5554
+ }, 500);
5555
+ });
5539
5556
  }
5557
+ return executeCommand(sessionId, input, sessionManager2);
5558
+ }
5559
+ async function executeCommand(sessionId, input, sessionManager2) {
5540
5560
  const session = sessionManager2.getSession(sessionId);
5541
5561
  if (!session) {
5542
5562
  return {
5543
- content: [
5544
- {
5545
- type: "text",
5546
- text: "Error: Failed to get terminal session."
5547
- }
5548
- ],
5563
+ content: [{ type: "text", text: "Error: Failed to get terminal session." }],
5549
5564
  isError: true
5550
5565
  };
5551
5566
  }
5552
5567
  const validation = sessionManager2.validateCommand(input.command);
5553
5568
  if (!validation.valid) {
5554
5569
  return {
5555
- content: [
5556
- {
5557
- type: "text",
5558
- text: `Command blocked: ${validation.reason}`
5559
- }
5560
- ],
5570
+ content: [{ type: "text", text: `Command blocked: ${validation.reason}` }],
5561
5571
  isError: true
5562
5572
  };
5563
5573
  }
5564
5574
  const timeoutMs = input.timeoutMs ?? sessionManager2.getDefaultTimeout();
5565
5575
  const waitForCompletion = input.waitForCompletion ?? true;
5566
- const result = await session.execute(
5567
- input.command,
5568
- timeoutMs,
5569
- waitForCompletion
5570
- );
5576
+ const result = await session.execute(input.command, timeoutMs, waitForCompletion);
5571
5577
  let cleanOutput = result.output.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "").trim();
5572
5578
  const lines = cleanOutput.split("\n");
5573
5579
  if (lines.length > 0 && lines[0].trim() === input.command.trim()) {
5574
5580
  lines.shift();
5575
5581
  cleanOutput = lines.join("\n").trim();
5576
5582
  }
5577
- const statusParts = [
5578
- `exit: ${result.exitCode ?? "n/a"}`,
5579
- `${result.durationMs}ms`,
5580
- sessionId
5581
- ];
5583
+ const statusParts = [`exit: ${result.exitCode ?? "n/a"}`, `${result.durationMs}ms`, sessionId];
5582
5584
  let text = `$ ${input.command}
5583
5585
  ${cleanOutput}
5584
5586
 
@@ -5588,12 +5590,7 @@ ${cleanOutput}
5588
5590
  [TIMED OUT after ${timeoutMs}ms - session still active, use read to get more output]`;
5589
5591
  }
5590
5592
  return {
5591
- content: [
5592
- {
5593
- type: "text",
5594
- text
5595
- }
5596
- ],
5593
+ content: [{ type: "text", text }],
5597
5594
  isError: result.exitCode !== null && result.exitCode !== 0
5598
5595
  };
5599
5596
  }
@@ -5614,23 +5611,20 @@ async function handleTerminalReadOutput(params, sessionManager2) {
5614
5611
  };
5615
5612
  }
5616
5613
  const result = session.readOutput(input.offset, input.lines);
5614
+ const cleanOutput = result.lines.join("\n").replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "").trim();
5615
+ const status = [
5616
+ `lines: ${result.readFrom}-${result.readFrom + result.readCount}/${result.totalLines}`,
5617
+ `remaining: ${result.remaining}`,
5618
+ input.sessionId
5619
+ ];
5620
+ const text = `${cleanOutput}
5621
+
5622
+ [${status.join(" | ")}]`;
5617
5623
  return {
5618
5624
  content: [
5619
5625
  {
5620
5626
  type: "text",
5621
- text: JSON.stringify(
5622
- {
5623
- sessionId: input.sessionId,
5624
- readFrom: result.readFrom,
5625
- readCount: result.readCount,
5626
- totalLines: result.totalLines,
5627
- remaining: result.remaining,
5628
- isComplete: result.isComplete,
5629
- output: result.lines.join("\n")
5630
- },
5631
- null,
5632
- 2
5633
- )
5627
+ text
5634
5628
  }
5635
5629
  ]
5636
5630
  };
@@ -5948,7 +5942,6 @@ var TerminalSession = class {
5948
5942
  isActive = true;
5949
5943
  lastCommandAt;
5950
5944
  shellReady;
5951
- resolveShellReady;
5952
5945
  constructor(config, maxOutputLines) {
5953
5946
  this.sessionId = generateSessionId();
5954
5947
  this.name = config.name;
@@ -5964,27 +5957,12 @@ var TerminalSession = class {
5964
5957
  if (config.shell) {
5965
5958
  terminalOptions.shellPath = config.shell;
5966
5959
  }
5967
- this.shellReady = new Promise((resolve) => {
5968
- this.resolveShellReady = resolve;
5969
- });
5970
5960
  this.terminal = vscode2.window.createTerminal(terminalOptions);
5971
5961
  this.terminal.show(true);
5962
+ this.shellReady = new Promise((resolve) => {
5963
+ resolve();
5964
+ });
5972
5965
  this.setupShellIntegrationCapture();
5973
- if (vscode2.window.onDidChangeTerminalShellIntegration) {
5974
- const disposable = vscode2.window.onDidChangeTerminalShellIntegration((e) => {
5975
- if (e.terminal === this.terminal) {
5976
- disposable.dispose();
5977
- log(`Shell integration ready for session ${this.sessionId}`);
5978
- this.resolveShellReady();
5979
- }
5980
- });
5981
- setTimeout(() => {
5982
- disposable.dispose();
5983
- this.resolveShellReady();
5984
- }, 3e3);
5985
- } else {
5986
- setTimeout(() => this.resolveShellReady(), 1500);
5987
- }
5988
5966
  log(`Session ${this.sessionId} created: ${config.name} (cwd: ${this.cwd})`);
5989
5967
  }
5990
5968
  setupShellIntegrationCapture() {
@@ -6016,6 +5994,7 @@ var TerminalSession = class {
6016
5994
  this.commandHistory.push(this.currentCommand);
6017
5995
  this.currentCommand = null;
6018
5996
  }
5997
+ this.lastCommandAt = Date.now();
6019
5998
  log(
6020
5999
  `Shell execution ended in session ${this.sessionId} with exit code: ${event.exitCode}`
6021
6000
  );
@@ -6023,11 +6002,10 @@ var TerminalSession = class {
6023
6002
  }
6024
6003
  }
6025
6004
  }
6026
- /**
6027
- * Execute a command in this terminal session.
6028
- */
6029
6005
  async execute(command, timeoutMs, waitForCompletion) {
6006
+ log(`Waiting for shell ready in session ${this.sessionId}...`);
6030
6007
  await this.shellReady;
6008
+ log(`Shell ready, executing command in session ${this.sessionId}`);
6031
6009
  const commandId = generateCommandId();
6032
6010
  const startedAt = Date.now();
6033
6011
  this.lastCommandAt = startedAt;
@@ -6114,9 +6092,6 @@ var TerminalSession = class {
6114
6092
  }
6115
6093
  });
6116
6094
  }
6117
- /**
6118
- * Send text input to the terminal (for interactive commands).
6119
- */
6120
6095
  sendInput(input, pressEnter) {
6121
6096
  this.terminal.sendText(input, pressEnter);
6122
6097
  this.lastCommandAt = Date.now();
@@ -6124,21 +6099,12 @@ var TerminalSession = class {
6124
6099
  `Input sent to session ${this.sessionId}: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`
6125
6100
  );
6126
6101
  }
6127
- /**
6128
- * Read output from the buffer with pagination.
6129
- */
6130
6102
  readOutput(offset = 0, maxLines = 500) {
6131
6103
  return readFromBuffer(this.outputBuffer, offset, maxLines);
6132
6104
  }
6133
- /**
6134
- * Check if a command is currently executing.
6135
- */
6136
6105
  get isBusy() {
6137
6106
  return this.currentCommand !== null;
6138
6107
  }
6139
- /**
6140
- * Get session info for listing.
6141
- */
6142
6108
  getInfo() {
6143
6109
  return {
6144
6110
  sessionId: this.sessionId,
@@ -6151,23 +6117,14 @@ var TerminalSession = class {
6151
6117
  outputLineCount: getBufferLineCount(this.outputBuffer)
6152
6118
  };
6153
6119
  }
6154
- /**
6155
- * Get the VSCode terminal instance (for matching events).
6156
- */
6157
6120
  getTerminal() {
6158
6121
  return this.terminal;
6159
6122
  }
6160
- /**
6161
- * Check if session has been idle longer than the given duration.
6162
- */
6163
6123
  isIdle(idleThresholdMs) {
6164
6124
  if (idleThresholdMs <= 0) return false;
6165
6125
  const lastActivity = this.lastCommandAt ?? this.createdAt;
6166
6126
  return Date.now() - lastActivity > idleThresholdMs;
6167
6127
  }
6168
- /**
6169
- * Close the terminal session.
6170
- */
6171
6128
  dispose() {
6172
6129
  this.isActive = false;
6173
6130
  this.shellExecutionDisposable?.dispose();
@@ -6183,7 +6140,6 @@ var SessionManager = class {
6183
6140
  onSessionsChanged = this.onSessionsChangedEmitter.event;
6184
6141
  idleReaperInterval = null;
6185
6142
  constructor() {
6186
- this.startIdleReaper();
6187
6143
  vscode3.window.onDidCloseTerminal((terminal) => {
6188
6144
  for (const [id, session] of this.sessions) {
6189
6145
  if (session.getTerminal() === terminal) {
@@ -6217,6 +6173,7 @@ var SessionManager = class {
6217
6173
  const config = this.getConfig();
6218
6174
  if (config.idleTimeoutMs <= 0) return;
6219
6175
  for (const [id, session] of this.sessions) {
6176
+ if (session.isBusy) continue;
6220
6177
  if (session.isIdle(config.idleTimeoutMs)) {
6221
6178
  log(`Reaping idle session ${id}`);
6222
6179
  session.dispose();
package/dist/mcp-entry.js CHANGED
File without changes
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "vscode-terminal-mcp",
3
3
  "displayName": "Terminal MCP Server",
4
4
  "description": "MCP server that provides visible terminal sessions in VSCode for Claude Code and subagents",
5
- "version": "0.1.4",
5
+ "version": "0.1.5",
6
6
  "publisher": "sirlordt",
7
7
  "license": "MIT",
8
8
  "author": "sirlordt",