snow-ai 0.3.18 → 0.3.19

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/cli.js CHANGED
@@ -9,6 +9,7 @@ import App from './app.js';
9
9
  import { vscodeConnection } from './utils/vscodeConnection.js';
10
10
  import { resourceMonitor } from './utils/resourceMonitor.js';
11
11
  import { initializeProfiles } from './utils/configManager.js';
12
+ import { processManager } from './utils/processManager.js';
12
13
  const execAsync = promisify(exec);
13
14
  // Check for updates asynchronously
14
15
  async function checkForUpdates(currentVersion) {
@@ -125,6 +126,8 @@ process.stdout.write('\x1b[?2004l');
125
126
  // Re-enable on exit to avoid polluting parent shell
126
127
  const cleanup = () => {
127
128
  process.stdout.write('\x1b[?2004l');
129
+ // Kill all child processes first
130
+ processManager.killAll();
128
131
  // Stop resource monitoring
129
132
  resourceMonitor.stopMonitoring();
130
133
  // Disconnect VSCode connection before exit
@@ -2,6 +2,7 @@ import { promises as fs } from 'fs';
2
2
  import * as path from 'path';
3
3
  import { spawn } from 'child_process';
4
4
  import { AsyncFzf } from 'fzf';
5
+ import { processManager } from '../utils/processManager.js';
5
6
  // Utility functions
6
7
  import { detectLanguage } from './utils/aceCodeSearch/language.utils.js';
7
8
  import { loadExclusionPatterns, shouldExcludeDirectory, readFileWithCache, } from './utils/aceCodeSearch/filesystem.utils.js';
@@ -476,7 +477,10 @@ export class ACECodeSearchService {
476
477
  expandGlobBraces(glob) {
477
478
  // Match {a,b,c} pattern
478
479
  const braceMatch = glob.match(/^(.+)\{([^}]+)\}(.*)$/);
479
- if (!braceMatch || !braceMatch[1] || !braceMatch[2] || braceMatch[3] === undefined) {
480
+ if (!braceMatch ||
481
+ !braceMatch[1] ||
482
+ !braceMatch[2] ||
483
+ braceMatch[3] === undefined) {
480
484
  return [glob];
481
485
  }
482
486
  const prefix = braceMatch[1];
@@ -506,6 +510,8 @@ export class ACECodeSearchService {
506
510
  cwd: this.basePath,
507
511
  windowsHide: true,
508
512
  });
513
+ // Register child process for cleanup
514
+ processManager.register(child);
509
515
  const stdoutChunks = [];
510
516
  const stderrChunks = [];
511
517
  child.stdout.on('data', chunk => stdoutChunks.push(chunk));
@@ -572,6 +578,8 @@ export class ACECodeSearchService {
572
578
  cwd: this.basePath,
573
579
  windowsHide: true,
574
580
  });
581
+ // Register child process for cleanup
582
+ processManager.register(child);
575
583
  const stdoutChunks = [];
576
584
  const stderrChunks = [];
577
585
  child.stdout.on('data', chunk => stdoutChunks.push(chunk));
package/dist/mcp/bash.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { exec } from 'child_process';
2
- import { promisify } from 'util';
3
2
  // Utility functions
4
3
  import { isDangerousCommand, truncateOutput, } from './utils/bash/security.utils.js';
5
- const execAsync = promisify(exec);
4
+ import { processManager } from '../utils/processManager.js';
6
5
  /**
7
6
  * Terminal Command Execution Service
8
7
  * Executes terminal commands directly using the system's default shell
@@ -38,8 +37,8 @@ export class TerminalCommandService {
38
37
  if (isDangerousCommand(command)) {
39
38
  throw new Error(`Dangerous command detected and blocked: ${command.slice(0, 50)}`);
40
39
  }
41
- // Execute command using system default shell
42
- const { stdout, stderr } = await execAsync(command, {
40
+ // Execute command using system default shell and register the process
41
+ const childProcess = exec(command, {
43
42
  cwd: this.workingDirectory,
44
43
  timeout,
45
44
  maxBuffer: this.maxOutputLength,
@@ -51,6 +50,35 @@ export class TerminalCommandService {
51
50
  }),
52
51
  },
53
52
  });
53
+ // Register child process for cleanup
54
+ processManager.register(childProcess);
55
+ // Convert to promise
56
+ const { stdout, stderr } = await new Promise((resolve, reject) => {
57
+ let stdoutData = '';
58
+ let stderrData = '';
59
+ childProcess.stdout?.on('data', chunk => {
60
+ stdoutData += chunk;
61
+ });
62
+ childProcess.stderr?.on('data', chunk => {
63
+ stderrData += chunk;
64
+ });
65
+ childProcess.on('error', reject);
66
+ childProcess.on('close', (code, signal) => {
67
+ if (signal) {
68
+ reject(new Error(`Process killed by signal ${signal}`));
69
+ }
70
+ else if (code === 0) {
71
+ resolve({ stdout: stdoutData, stderr: stderrData });
72
+ }
73
+ else {
74
+ const error = new Error(`Process exited with code ${code}`);
75
+ error.code = code;
76
+ error.stdout = stdoutData;
77
+ error.stderr = stderrData;
78
+ reject(error);
79
+ }
80
+ });
81
+ });
54
82
  // Truncate output if too long
55
83
  return {
56
84
  stdout: truncateOutput(stdout, this.maxOutputLength),
@@ -0,0 +1,27 @@
1
+ import { ChildProcess } from 'child_process';
2
+ /**
3
+ * Process Manager
4
+ * Tracks and manages all child processes to ensure proper cleanup
5
+ */
6
+ declare class ProcessManager {
7
+ private processes;
8
+ private isShuttingDown;
9
+ /**
10
+ * Register a child process for tracking
11
+ */
12
+ register(process: ChildProcess): void;
13
+ /**
14
+ * Kill a specific process gracefully
15
+ */
16
+ private killProcess;
17
+ /**
18
+ * Kill all tracked processes
19
+ */
20
+ killAll(): void;
21
+ /**
22
+ * Get count of active processes
23
+ */
24
+ getActiveCount(): number;
25
+ }
26
+ export declare const processManager: ProcessManager;
27
+ export {};
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Process Manager
3
+ * Tracks and manages all child processes to ensure proper cleanup
4
+ */
5
+ class ProcessManager {
6
+ constructor() {
7
+ Object.defineProperty(this, "processes", {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value: new Set()
12
+ });
13
+ Object.defineProperty(this, "isShuttingDown", {
14
+ enumerable: true,
15
+ configurable: true,
16
+ writable: true,
17
+ value: false
18
+ });
19
+ }
20
+ /**
21
+ * Register a child process for tracking
22
+ */
23
+ register(process) {
24
+ if (this.isShuttingDown) {
25
+ // If we're already shutting down, kill immediately
26
+ this.killProcess(process);
27
+ return;
28
+ }
29
+ this.processes.add(process);
30
+ // Auto-remove when process exits
31
+ const cleanup = () => {
32
+ this.processes.delete(process);
33
+ };
34
+ process.once('exit', cleanup);
35
+ process.once('error', cleanup);
36
+ }
37
+ /**
38
+ * Kill a specific process gracefully
39
+ */
40
+ killProcess(process) {
41
+ try {
42
+ if (process.pid && !process.killed) {
43
+ // Try graceful termination first
44
+ process.kill('SIGTERM');
45
+ // Force kill after 1 second if still alive
46
+ setTimeout(() => {
47
+ if (process.pid && !process.killed) {
48
+ process.kill('SIGKILL');
49
+ }
50
+ }, 1000);
51
+ }
52
+ }
53
+ catch (error) {
54
+ // Process might already be dead, ignore errors
55
+ }
56
+ }
57
+ /**
58
+ * Kill all tracked processes
59
+ */
60
+ killAll() {
61
+ this.isShuttingDown = true;
62
+ for (const process of this.processes) {
63
+ this.killProcess(process);
64
+ }
65
+ this.processes.clear();
66
+ }
67
+ /**
68
+ * Get count of active processes
69
+ */
70
+ getActiveCount() {
71
+ return this.processes.size;
72
+ }
73
+ }
74
+ // Export singleton instance
75
+ export const processManager = new ProcessManager();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.3.18",
3
+ "version": "0.3.19",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {