MCPcopy Index your code
hub / github.com/Doorman11991/smallcode / start

Method start

src/tools/shell_session.js:54–149  ·  view source on GitHub ↗

* Spawn the shell subprocess. Idempotent — safe to call multiple times.

()

Source from the content-addressed store, hash-verified

52 * Spawn the shell subprocess. Idempotent — safe to call multiple times.
53 */
54 async start() {
55 if (this.proc && !this._dead) return true;
56 if (this.starting) return this.starting;
57
58 this.starting = (async () => {
59 // Fresh spawn — clear any leftover buffer + mark live before we attach
60 // handlers, so a restart after stop() starts from a clean slate.
61 this.buffer = '';
62 this._dead = false;
63 const isWin = process.platform === 'win32';
64 // POSIX: always use bash, never $SHELL. The sentinel command-wrapper
65 // below emits bash/POSIX syntax (`printf '\n%s_%d_\n' $?`) and passes
66 // bash-only flags (--norc --noprofile -i). Honouring $SHELL broke every
67 // zsh user: `zsh --norc ...` errors with "zsh: no such option: norc"
68 // and the shell exits immediately, so every bash tool call failed with
69 // "shell exited" (issue #58). cmd.exe on Windows.
70 const shellCmd = isWin ? 'cmd.exe' : 'bash';
71
72 const shellArgs = isWin ? ['/Q', '/K', 'echo off & prompt $G'] : ['--norc', '--noprofile', '-i'];
73
74 try {
75 this.proc = spawn(shellCmd, shellArgs, {
76 cwd: this.cwd,
77 env: { ...process.env, PS1: '', PROMPT_COMMAND: '' },
78 stdio: ['pipe', 'pipe', 'pipe'],
79 shell: false,
80 });
81 // Unref so this child process doesn't keep the parent's event loop
82 // alive after all other work is done (e.g. on --non-interactive exit).
83 // We still clean up explicitly via process.on('exit') and SIGINT.
84 try { this.proc.unref(); } catch {}
85 } catch (err) {
86 this._dead = true;
87 return false;
88 }
89
90 // Capture the specific child so a late event from a PREVIOUS shell can't
91 // stomp a freshly-spawned one. Without this guard, `stop()` immediately
92 // followed by `run()` races: the old proc's async 'exit' fires after the
93 // new shell spawned and flips this._dead=true on the live session,
94 // killing it (and producing empty output).
95 const proc = this.proc;
96 const isCurrent = () => this.proc === proc;
97
98 proc.on('error', () => { if (isCurrent()) this._dead = true; });
99 proc.on('exit', () => { if (isCurrent()) { this._dead = true; this._failPending('shell exited'); } });
100
101 // Guard against async EPIPE on stdin. When the shell dies mid-write,
102 // `proc.stdin` emits an async 'error' (EPIPE) event. With no listener,
103 // Node escalates it to an uncaught exception and crashes the whole
104 // process — the try/catch around stdin.write() only catches the
105 // synchronous throw, not the async event. Mark the shell dead and let
106 // the next run() auto-restart it (issue #58).
107 proc.stdin.on('error', () => { if (isCurrent()) { this._dead = true; this._failPending('shell stdin error'); } });
108
109 // Demux output via sentinel matching. Ignore late chunks from a
110 // superseded process so they can't corrupt a freshly-spawned shell's
111 // buffer (same race as the exit-handler guard above).

Callers 2

runMethod · 0.95
resetMethod · 0.95

Calls 1

_failPendingMethod · 0.95

Tested by

no test coverage detected