vscode-terminal-mcp 0.1.4 → 0.1.6

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/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.1.6] - 2026-03-19 18:18 PDT
6
+
7
+ ### Added
8
+ - Screenshots in README for marketplace (run, exec, permission dialog)
9
+ - Custom terminal tab names with date format (e.g., `MCP: BashTerm-26-03-19-17-30`)
10
+ - `name` parameter in `run` tool for custom terminal names
11
+ - Unique IPC socket per workspace to prevent conflicts between multiple VSCode instances
12
+ - Large output handling documentation
13
+ - Development workflow docs for extension cache workaround
14
+
15
+ ### Fixed
16
+ - Clean output format for all tools (`run`, `exec`, `read`, `list`, `close`, `input`) — no more raw JSON responses
17
+ - `waitForCompletion: false` not working (`z.coerce.boolean()` converted string `"false"` to `true`)
18
+ - Idle reaper killing sessions with running commands — reaper disabled, user closes sessions manually
19
+
20
+ ## [0.1.5] - 2026-03-18 14:50 PDT
21
+
22
+ ### Added
23
+ - npm publish with `bin` entry for `npx vscode-terminal-mcp` support
24
+ - Published to VSCode Marketplace and MCP Registry
25
+
26
+ ## [0.1.3] - 2026-03-18 11:00 PDT
27
+
28
+ ### Added
29
+ - `run` tool combining create + exec in one step
30
+ - Session reuse: `run` finds idle sessions before creating new ones
31
+ - Busy session detection: won't reuse sessions with running commands
32
+
33
+ ### Fixed
34
+ - First-command timing fix with shell initialization delay
35
+
36
+ ## [0.1.0] - 2026-03-18 10:00 PDT
37
+
38
+ ### Added
39
+ - Initial release
40
+ - Tools: `create`, `exec`, `read`, `input`, `list`, `close`
41
+ - Shell Integration API for output capture and exit code detection
42
+ - Circular output buffer with pagination support
43
+ - Subagent isolation with `agentId`
44
+ - Command blocklist security
45
+ - IPC bridge for MCP stdio-to-socket communication
package/CLAUDE.md ADDED
@@ -0,0 +1,84 @@
1
+ # Project: vscode-terminal-mcp
2
+
3
+ MCP server that runs commands in visible VSCode terminal tabs.
4
+
5
+ ## Release Process
6
+
7
+ When publishing a new version, follow these steps in order:
8
+
9
+ ### 1. Update version
10
+
11
+ ```bash
12
+ # In package.json, bump the version
13
+ # e.g., "version": "0.1.6" → "version": "0.1.7"
14
+ ```
15
+
16
+ ### 2. Update CHANGELOG.md
17
+
18
+ Add a new entry at the top with the new version and date:
19
+
20
+ ```markdown
21
+ ## [0.1.7] - YYYY-MM-DD
22
+
23
+ ### Added
24
+ - ...
25
+
26
+ ### Fixed
27
+ - ...
28
+ ```
29
+
30
+ ### 3. Update README.md
31
+
32
+ Replace the "Latest Changes" section with the new version's changes. Keep only the latest version in README — full history lives in CHANGELOG.md.
33
+
34
+ ### 4. Build and publish
35
+
36
+ ```bash
37
+ # Build
38
+ npm run build
39
+
40
+ # Publish to npm
41
+ npm publish --access public
42
+
43
+ # Package vsix
44
+ npx vsce package --allow-missing-repository
45
+
46
+ # Install locally for testing
47
+ cp dist/extension.js dist/mcp-entry.js ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-<version>/dist/
48
+ ```
49
+
50
+ ### 5. Upload to VSCode Marketplace
51
+
52
+ 1. Go to https://marketplace.visualstudio.com/manage/publishers/sirlordt
53
+ 2. Click "..." next to Terminal MCP → "Update"
54
+ 3. Upload the `.vsix` file
55
+
56
+ ### 6. Commit and push
57
+
58
+ ```bash
59
+ git add -A
60
+ git commit -m "v0.1.7: <summary of changes>"
61
+ git push
62
+ ```
63
+
64
+ ## Extension Cache Workaround
65
+
66
+ VSCode aggressively caches extensions. When developing locally:
67
+
68
+ ```bash
69
+ # Quick update (after modifying source)
70
+ npm run build
71
+ cp dist/extension.js ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-<version>/dist/extension.js
72
+ # Then "Developer: Reload Window"
73
+
74
+ # If reload doesn't pick up changes, close and reopen VSCode completely
75
+ ```
76
+
77
+ ## Terminal Execution
78
+
79
+ Prefer the BashTerm MCP tools (`run`, `exec`, `read`, etc.) over the built-in Bash tool for executing commands. BashTerm runs commands in visible VSCode terminal tabs where the user can see output in real time.
80
+
81
+ For commands that may take longer than 30 seconds or produce large output, use pull mode:
82
+ 1. Call `run` with `waitForCompletion: false`
83
+ 2. Call `read` with `offset: -10` to check progress
84
+ 3. Repeat until done
package/README.md CHANGED
@@ -84,6 +84,20 @@ After installation, try asking:
84
84
 
85
85
  You should see a new terminal tab open in VSCode with the command output.
86
86
 
87
+ ## Screenshots
88
+
89
+ ### Running a command with `run`
90
+
91
+ ![Run command output](docs/images/run_finished.png)
92
+
93
+ ### Permission dialog for `exec`
94
+
95
+ ![Exec permission dialog](docs/images/ask_exec_permission.png)
96
+
97
+ ### Exec result with clean output
98
+
99
+ ![Exec finished](docs/images/exec_finished.png)
100
+
87
101
  ## Tools
88
102
 
89
103
  ### Quick Execution
@@ -202,6 +216,76 @@ This prevents conversation timeouts and lets the user watch progress in the term
202
216
  | Session state | Each command is isolated | Persistent sessions with history |
203
217
  | Interactive commands | Not supported | Send input to prompts/REPLs |
204
218
 
219
+ ## Development: Updating the Extension
220
+
221
+ VSCode aggressively caches extensions in memory. When developing locally, `code --install-extension` and even "Developer: Reload Window" may **not** reload your changes. Use this workflow:
222
+
223
+ ### Quick update (no restart needed)
224
+
225
+ After modifying source files, build and copy directly into the installed extension directory:
226
+
227
+ ```bash
228
+ cd /path/to/vscode-terminal-mcp
229
+ npm run build
230
+ cp dist/extension.js ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-<version>/dist/extension.js
231
+ ```
232
+
233
+ Then run **"Developer: Reload Window"** (`Ctrl+Shift+P`).
234
+
235
+ ### Full reinstall (when quick update doesn't work)
236
+
237
+ If VSCode still uses old code:
238
+
239
+ ```bash
240
+ # 1. Uninstall and remove all copies
241
+ code --uninstall-extension sirlordt.vscode-terminal-mcp
242
+ rm -rf ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-*
243
+
244
+ # 2. Check for ghost entries with old publisher names
245
+ # Look in ~/.vscode/extensions/extensions.json for stale entries
246
+ # Remove any entries with old publisher IDs (e.g., "terminal-mcp.vscode-terminal-mcp")
247
+
248
+ # 3. Close VSCode completely (not just reload)
249
+
250
+ # 4. Rebuild and install
251
+ npm run build
252
+ npx vsce package --allow-missing-repository
253
+ code --install-extension vscode-terminal-mcp-<version>.vsix --force
254
+
255
+ # 5. Open VSCode
256
+ ```
257
+
258
+ ### Verify the correct version is loaded
259
+
260
+ ```bash
261
+ # Check which extension directories exist
262
+ ls ~/.vscode/extensions/ | grep terminal
263
+
264
+ # Verify your changes are in the installed extension
265
+ grep "YOUR_UNIQUE_STRING" ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-*/dist/extension.js
266
+
267
+ # Compare checksums
268
+ md5sum dist/extension.js ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-*/dist/extension.js
269
+ ```
270
+
271
+ ## Large Output Handling
272
+
273
+ When `read` returns output that exceeds the MCP client's token limit, the system automatically saves the full output to a temporary JSON file and returns the file path in the error message.
274
+
275
+ To extract the relevant content:
276
+
277
+ ```bash
278
+ # Get the last 50 lines (most relevant for status)
279
+ tail -50 /path/to/saved/file.txt
280
+
281
+ # Or parse the JSON to extract the text content
282
+ python3 -c "import json; data=json.load(open('/path/to/file.txt')); print(data[0]['text'][-2000:])"
283
+ ```
284
+
285
+ The file format is JSON: `[{"type": "text", "text": "..."}]`
286
+
287
+ This commonly happens with commands that produce heavy TUI output (progress bars, ANSI escape codes). Use smaller `offset` values (e.g., `offset: -20` instead of `offset: -100`) to reduce the captured output size.
288
+
205
289
  ## How It Works
206
290
 
207
291
  1. The **VSCode extension** activates and starts an IPC server on a Unix socket
@@ -209,6 +293,18 @@ This prevents conversation timeouts and lets the user watch progress in the term
209
293
  3. Commands execute in real VSCode terminals using the **Shell Integration API** for reliable output capture and exit code detection
210
294
  4. Output is stored in circular buffers with pagination support for efficient reading
211
295
 
296
+ ## Latest Changes (0.1.6)
297
+
298
+ - Screenshots in README for marketplace
299
+ - Clean output format for all tools — no more raw JSON
300
+ - Fixed `waitForCompletion: false` not working
301
+ - Disabled idle reaper — user closes sessions manually
302
+ - Unique IPC socket per workspace (multi-instance support)
303
+ - Custom terminal tab names with date format
304
+ - Large output handling documentation
305
+
306
+ See [CHANGELOG.md](CHANGELOG.md) for full history.
307
+
212
308
  ## License
213
309
 
214
310
  MIT
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"),
@@ -5439,21 +5446,13 @@ async function handleTerminalCreate(params, sessionManager2) {
5439
5446
  shell: input.shell,
5440
5447
  agentId: input.agentId
5441
5448
  });
5449
+ const parts = [`Terminal created: ${sessionInfo.name}`, `session: ${sessionInfo.sessionId}`, `cwd: ${sessionInfo.cwd}`];
5450
+ if (sessionInfo.agentId) parts.push(`agent: ${sessionInfo.agentId}`);
5442
5451
  return {
5443
5452
  content: [
5444
5453
  {
5445
5454
  type: "text",
5446
- text: JSON.stringify(
5447
- {
5448
- status: "created",
5449
- sessionId: sessionInfo.sessionId,
5450
- name: sessionInfo.name,
5451
- cwd: sessionInfo.cwd,
5452
- agentId: sessionInfo.agentId
5453
- },
5454
- null,
5455
- 2
5456
- )
5455
+ text: parts.join(" | ")
5457
5456
  }
5458
5457
  ]
5459
5458
  };
@@ -5493,24 +5492,29 @@ async function handleTerminalExecute(params, sessionManager2) {
5493
5492
  timeoutMs,
5494
5493
  waitForCompletion
5495
5494
  );
5496
- const response = {
5497
- sessionId: input.sessionId,
5498
- commandId: result.commandId,
5499
- exitCode: result.exitCode,
5500
- timedOut: result.timedOut,
5501
- durationMs: result.durationMs,
5502
- output: result.output
5503
- };
5495
+ 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();
5496
+ const lines = cleanOutput.split("\n");
5497
+ if (lines.length > 0 && lines[0].trim() === input.command.trim()) {
5498
+ lines.shift();
5499
+ cleanOutput = lines.join("\n").trim();
5500
+ }
5501
+ const statusParts = [`exit: ${result.exitCode ?? "n/a"}`, `${result.durationMs}ms`, input.sessionId];
5502
+ let text = `$ ${input.command}
5503
+ ${cleanOutput}
5504
+
5505
+ [${statusParts.join(" | ")}]`;
5504
5506
  if (result.timedOut) {
5505
- response.warning = `Command timed out after ${timeoutMs}ms. Output may be incomplete. The terminal session is still active.`;
5507
+ text += `
5508
+ [TIMED OUT after ${timeoutMs}ms - session still active, use read to get more output]`;
5506
5509
  }
5507
5510
  return {
5508
5511
  content: [
5509
5512
  {
5510
5513
  type: "text",
5511
- text: JSON.stringify(response, null, 2)
5514
+ text
5512
5515
  }
5513
- ]
5516
+ ],
5517
+ isError: result.exitCode !== null && result.exitCode !== 0
5514
5518
  };
5515
5519
  }
5516
5520
 
@@ -5518,67 +5522,66 @@ async function handleTerminalExecute(params, sessionManager2) {
5518
5522
  async function handleTerminalRun(params, sessionManager2) {
5519
5523
  const input = terminalRunSchema.parse(params);
5520
5524
  let sessionId;
5525
+ let isNewSession = false;
5521
5526
  const existing = sessionManager2.listSessions(input.agentId);
5522
5527
  for (const s of existing) {
5523
5528
  if (!s.isActive || input.cwd && s.cwd !== input.cwd) continue;
5524
- const session2 = sessionManager2.getSession(s.sessionId);
5525
- if (session2 && !session2.isBusy) {
5529
+ const session = sessionManager2.getSession(s.sessionId);
5530
+ if (session && !session.isBusy) {
5526
5531
  sessionId = s.sessionId;
5527
5532
  break;
5528
5533
  }
5529
5534
  }
5530
5535
  if (!sessionId) {
5531
5536
  const sessionInfo = sessionManager2.createSession({
5532
- name: input.name ?? `run-${Date.now()}`,
5537
+ name: input.name ?? (() => {
5538
+ const d = /* @__PURE__ */ new Date();
5539
+ const pad = (n) => String(n).padStart(2, "0");
5540
+ return `BashTerm-${pad(d.getFullYear() % 100)}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}-${pad(d.getHours())}-${pad(d.getMinutes())}`;
5541
+ })(),
5533
5542
  cwd: input.cwd,
5534
5543
  env: input.env,
5535
5544
  shell: input.shell,
5536
5545
  agentId: input.agentId
5537
5546
  });
5538
5547
  sessionId = sessionInfo.sessionId;
5548
+ isNewSession = true;
5549
+ }
5550
+ if (isNewSession) {
5551
+ return new Promise((resolve) => {
5552
+ setTimeout(async () => {
5553
+ const result = await executeCommand(sessionId, input, sessionManager2);
5554
+ resolve(result);
5555
+ }, 500);
5556
+ });
5539
5557
  }
5558
+ return executeCommand(sessionId, input, sessionManager2);
5559
+ }
5560
+ async function executeCommand(sessionId, input, sessionManager2) {
5540
5561
  const session = sessionManager2.getSession(sessionId);
5541
5562
  if (!session) {
5542
5563
  return {
5543
- content: [
5544
- {
5545
- type: "text",
5546
- text: "Error: Failed to get terminal session."
5547
- }
5548
- ],
5564
+ content: [{ type: "text", text: "Error: Failed to get terminal session." }],
5549
5565
  isError: true
5550
5566
  };
5551
5567
  }
5552
5568
  const validation = sessionManager2.validateCommand(input.command);
5553
5569
  if (!validation.valid) {
5554
5570
  return {
5555
- content: [
5556
- {
5557
- type: "text",
5558
- text: `Command blocked: ${validation.reason}`
5559
- }
5560
- ],
5571
+ content: [{ type: "text", text: `Command blocked: ${validation.reason}` }],
5561
5572
  isError: true
5562
5573
  };
5563
5574
  }
5564
5575
  const timeoutMs = input.timeoutMs ?? sessionManager2.getDefaultTimeout();
5565
5576
  const waitForCompletion = input.waitForCompletion ?? true;
5566
- const result = await session.execute(
5567
- input.command,
5568
- timeoutMs,
5569
- waitForCompletion
5570
- );
5577
+ const result = await session.execute(input.command, timeoutMs, waitForCompletion);
5571
5578
  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
5579
  const lines = cleanOutput.split("\n");
5573
5580
  if (lines.length > 0 && lines[0].trim() === input.command.trim()) {
5574
5581
  lines.shift();
5575
5582
  cleanOutput = lines.join("\n").trim();
5576
5583
  }
5577
- const statusParts = [
5578
- `exit: ${result.exitCode ?? "n/a"}`,
5579
- `${result.durationMs}ms`,
5580
- sessionId
5581
- ];
5584
+ const statusParts = [`exit: ${result.exitCode ?? "n/a"}`, `${result.durationMs}ms`, sessionId];
5582
5585
  let text = `$ ${input.command}
5583
5586
  ${cleanOutput}
5584
5587
 
@@ -5588,12 +5591,7 @@ ${cleanOutput}
5588
5591
  [TIMED OUT after ${timeoutMs}ms - session still active, use read to get more output]`;
5589
5592
  }
5590
5593
  return {
5591
- content: [
5592
- {
5593
- type: "text",
5594
- text
5595
- }
5596
- ],
5594
+ content: [{ type: "text", text }],
5597
5595
  isError: result.exitCode !== null && result.exitCode !== 0
5598
5596
  };
5599
5597
  }
@@ -5614,23 +5612,20 @@ async function handleTerminalReadOutput(params, sessionManager2) {
5614
5612
  };
5615
5613
  }
5616
5614
  const result = session.readOutput(input.offset, input.lines);
5615
+ 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();
5616
+ const status = [
5617
+ `lines: ${result.readFrom}-${result.readFrom + result.readCount}/${result.totalLines}`,
5618
+ `remaining: ${result.remaining}`,
5619
+ input.sessionId
5620
+ ];
5621
+ const text = `${cleanOutput}
5622
+
5623
+ [${status.join(" | ")}]`;
5617
5624
  return {
5618
5625
  content: [
5619
5626
  {
5620
5627
  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
- )
5628
+ text
5634
5629
  }
5635
5630
  ]
5636
5631
  };
@@ -5640,18 +5635,23 @@ async function handleTerminalReadOutput(params, sessionManager2) {
5640
5635
  async function handleTerminalList(params, sessionManager2) {
5641
5636
  const input = terminalListSchema.parse(params ?? {});
5642
5637
  const sessions = sessionManager2.listSessions(input.agentId);
5638
+ if (sessions.length === 0) {
5639
+ return {
5640
+ content: [{ type: "text", text: "No active sessions." }]
5641
+ };
5642
+ }
5643
+ const lines = sessions.map((s) => {
5644
+ const age = Math.round((Date.now() - s.createdAt) / 1e3);
5645
+ const parts = [s.sessionId, s.name, `cwd: ${s.cwd}`, `${age}s old`, `${s.outputLineCount} lines`];
5646
+ if (s.agentId) parts.push(`agent: ${s.agentId}`);
5647
+ return parts.join(" | ");
5648
+ });
5643
5649
  return {
5644
5650
  content: [
5645
5651
  {
5646
5652
  type: "text",
5647
- text: JSON.stringify(
5648
- {
5649
- count: sessions.length,
5650
- sessions
5651
- },
5652
- null,
5653
- 2
5654
- )
5653
+ text: `${sessions.length} active session(s):
5654
+ ${lines.join("\n")}`
5655
5655
  }
5656
5656
  ]
5657
5657
  };
@@ -5676,10 +5676,7 @@ async function handleTerminalClose(params, sessionManager2) {
5676
5676
  content: [
5677
5677
  {
5678
5678
  type: "text",
5679
- text: JSON.stringify({
5680
- status: "closed",
5681
- sessionId: input.sessionId
5682
- })
5679
+ text: `Session closed: ${input.sessionId}`
5683
5680
  }
5684
5681
  ]
5685
5682
  };
@@ -5705,12 +5702,7 @@ async function handleTerminalSendInput(params, sessionManager2) {
5705
5702
  content: [
5706
5703
  {
5707
5704
  type: "text",
5708
- text: JSON.stringify({
5709
- status: "sent",
5710
- sessionId: input.sessionId,
5711
- inputLength: input.input.length,
5712
- pressedEnter: input.pressEnter ?? true
5713
- })
5705
+ text: `Input sent to ${input.sessionId} (${input.input.length} chars${input.pressEnter ?? true ? " + Enter" : ""})`
5714
5706
  }
5715
5707
  ]
5716
5708
  };
@@ -5948,7 +5940,6 @@ var TerminalSession = class {
5948
5940
  isActive = true;
5949
5941
  lastCommandAt;
5950
5942
  shellReady;
5951
- resolveShellReady;
5952
5943
  constructor(config, maxOutputLines) {
5953
5944
  this.sessionId = generateSessionId();
5954
5945
  this.name = config.name;
@@ -5964,27 +5955,12 @@ var TerminalSession = class {
5964
5955
  if (config.shell) {
5965
5956
  terminalOptions.shellPath = config.shell;
5966
5957
  }
5967
- this.shellReady = new Promise((resolve) => {
5968
- this.resolveShellReady = resolve;
5969
- });
5970
5958
  this.terminal = vscode2.window.createTerminal(terminalOptions);
5971
5959
  this.terminal.show(true);
5960
+ this.shellReady = new Promise((resolve) => {
5961
+ resolve();
5962
+ });
5972
5963
  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
5964
  log(`Session ${this.sessionId} created: ${config.name} (cwd: ${this.cwd})`);
5989
5965
  }
5990
5966
  setupShellIntegrationCapture() {
@@ -6016,6 +5992,7 @@ var TerminalSession = class {
6016
5992
  this.commandHistory.push(this.currentCommand);
6017
5993
  this.currentCommand = null;
6018
5994
  }
5995
+ this.lastCommandAt = Date.now();
6019
5996
  log(
6020
5997
  `Shell execution ended in session ${this.sessionId} with exit code: ${event.exitCode}`
6021
5998
  );
@@ -6023,11 +6000,10 @@ var TerminalSession = class {
6023
6000
  }
6024
6001
  }
6025
6002
  }
6026
- /**
6027
- * Execute a command in this terminal session.
6028
- */
6029
6003
  async execute(command, timeoutMs, waitForCompletion) {
6004
+ log(`Waiting for shell ready in session ${this.sessionId}...`);
6030
6005
  await this.shellReady;
6006
+ log(`Shell ready, executing command in session ${this.sessionId}`);
6031
6007
  const commandId = generateCommandId();
6032
6008
  const startedAt = Date.now();
6033
6009
  this.lastCommandAt = startedAt;
@@ -6114,9 +6090,6 @@ var TerminalSession = class {
6114
6090
  }
6115
6091
  });
6116
6092
  }
6117
- /**
6118
- * Send text input to the terminal (for interactive commands).
6119
- */
6120
6093
  sendInput(input, pressEnter) {
6121
6094
  this.terminal.sendText(input, pressEnter);
6122
6095
  this.lastCommandAt = Date.now();
@@ -6124,21 +6097,12 @@ var TerminalSession = class {
6124
6097
  `Input sent to session ${this.sessionId}: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`
6125
6098
  );
6126
6099
  }
6127
- /**
6128
- * Read output from the buffer with pagination.
6129
- */
6130
6100
  readOutput(offset = 0, maxLines = 500) {
6131
6101
  return readFromBuffer(this.outputBuffer, offset, maxLines);
6132
6102
  }
6133
- /**
6134
- * Check if a command is currently executing.
6135
- */
6136
6103
  get isBusy() {
6137
6104
  return this.currentCommand !== null;
6138
6105
  }
6139
- /**
6140
- * Get session info for listing.
6141
- */
6142
6106
  getInfo() {
6143
6107
  return {
6144
6108
  sessionId: this.sessionId,
@@ -6151,23 +6115,14 @@ var TerminalSession = class {
6151
6115
  outputLineCount: getBufferLineCount(this.outputBuffer)
6152
6116
  };
6153
6117
  }
6154
- /**
6155
- * Get the VSCode terminal instance (for matching events).
6156
- */
6157
6118
  getTerminal() {
6158
6119
  return this.terminal;
6159
6120
  }
6160
- /**
6161
- * Check if session has been idle longer than the given duration.
6162
- */
6163
6121
  isIdle(idleThresholdMs) {
6164
6122
  if (idleThresholdMs <= 0) return false;
6165
6123
  const lastActivity = this.lastCommandAt ?? this.createdAt;
6166
6124
  return Date.now() - lastActivity > idleThresholdMs;
6167
6125
  }
6168
- /**
6169
- * Close the terminal session.
6170
- */
6171
6126
  dispose() {
6172
6127
  this.isActive = false;
6173
6128
  this.shellExecutionDisposable?.dispose();
@@ -6183,7 +6138,6 @@ var SessionManager = class {
6183
6138
  onSessionsChanged = this.onSessionsChangedEmitter.event;
6184
6139
  idleReaperInterval = null;
6185
6140
  constructor() {
6186
- this.startIdleReaper();
6187
6141
  vscode3.window.onDidCloseTerminal((terminal) => {
6188
6142
  for (const [id, session] of this.sessions) {
6189
6143
  if (session.getTerminal() === terminal) {
@@ -6217,6 +6171,7 @@ var SessionManager = class {
6217
6171
  const config = this.getConfig();
6218
6172
  if (config.idleTimeoutMs <= 0) return;
6219
6173
  for (const [id, session] of this.sessions) {
6174
+ if (session.isBusy) continue;
6220
6175
  if (session.isIdle(config.idleTimeoutMs)) {
6221
6176
  log(`Reaping idle session ${id}`);
6222
6177
  session.dispose();
@@ -6344,7 +6299,16 @@ var sessionManager;
6344
6299
  var statusBarItem;
6345
6300
  function getSocketPath() {
6346
6301
  const tmpDir = os.tmpdir();
6347
- return path.join(tmpDir, "vscode-terminal-mcp.sock");
6302
+ const crypto3 = require("crypto");
6303
+ const workspace4 = vscode4.workspace.workspaceFolders?.[0]?.uri.fsPath || "";
6304
+ const hash = crypto3.createHash("md5").update(workspace4).digest("hex").slice(0, 8);
6305
+ const socketPath = path.join(tmpDir, `vscode-terminal-mcp-${hash}.sock`);
6306
+ const discoveryPath = path.join(tmpDir, "vscode-terminal-mcp.discovery");
6307
+ try {
6308
+ fs.writeFileSync(discoveryPath, socketPath);
6309
+ } catch {
6310
+ }
6311
+ return socketPath;
6348
6312
  }
6349
6313
  function cleanupSocket(socketPath) {
6350
6314
  try {
package/dist/mcp-entry.js CHANGED
@@ -27,7 +27,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var net = __toESM(require("net"));
28
28
  var path = __toESM(require("path"));
29
29
  var os = __toESM(require("os"));
30
- var SOCKET_PATH = path.join(os.tmpdir(), "vscode-terminal-mcp.sock");
30
+ var fs = __toESM(require("fs"));
31
+ function getSocketPath() {
32
+ const tmpDir = os.tmpdir();
33
+ const discoveryPath = path.join(tmpDir, "vscode-terminal-mcp.discovery");
34
+ try {
35
+ const socketPath = fs.readFileSync(discoveryPath, "utf8").trim();
36
+ if (socketPath && fs.existsSync(socketPath)) {
37
+ return socketPath;
38
+ }
39
+ } catch {
40
+ }
41
+ return path.join(tmpDir, "vscode-terminal-mcp.sock");
42
+ }
43
+ var SOCKET_PATH = getSocketPath();
31
44
  var RECONNECT_DELAY_MS = 1e3;
32
45
  var MAX_RECONNECT_ATTEMPTS = 30;
33
46
  var StdioToIpcBridge = class {
Binary file
Binary file
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.6",
6
6
  "publisher": "sirlordt",
7
7
  "license": "MIT",
8
8
  "author": "sirlordt",