@vybestack/llxprt-code-core 0.5.0-nightly.251125.6be1c4fa2 → 0.5.0-nightly.251126.ec44835c5

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.
Files changed (30) hide show
  1. package/dist/src/agents/invocation.js +11 -3
  2. package/dist/src/agents/invocation.js.map +1 -1
  3. package/dist/src/agents/registry.d.ts +2 -0
  4. package/dist/src/agents/registry.js +9 -6
  5. package/dist/src/agents/registry.js.map +1 -1
  6. package/dist/src/config/config.d.ts +3 -9
  7. package/dist/src/config/config.js +0 -25
  8. package/dist/src/config/config.js.map +1 -1
  9. package/dist/src/index.d.ts +0 -1
  10. package/dist/src/index.js +0 -6
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/providers/anthropic/AnthropicProvider.js +24 -0
  13. package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
  14. package/dist/src/providers/openai/OpenAIProvider.d.ts +3 -0
  15. package/dist/src/providers/openai/OpenAIProvider.js +8 -2
  16. package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
  17. package/dist/src/providers/utils/localEndpoint.d.ts +39 -0
  18. package/dist/src/providers/utils/localEndpoint.js +117 -0
  19. package/dist/src/providers/utils/localEndpoint.js.map +1 -0
  20. package/dist/src/services/shellExecutionService.d.ts +3 -46
  21. package/dist/src/services/shellExecutionService.js +49 -189
  22. package/dist/src/services/shellExecutionService.js.map +1 -1
  23. package/dist/src/tools/shell.js +2 -7
  24. package/dist/src/tools/shell.js.map +1 -1
  25. package/dist/src/tools/tools.d.ts +1 -2
  26. package/dist/src/tools/tools.js.map +1 -1
  27. package/package.json +1 -1
  28. package/dist/src/utils/terminalSerializer.d.ts +0 -28
  29. package/dist/src/utils/terminalSerializer.js +0 -418
  30. package/dist/src/utils/terminalSerializer.js.map +0 -1
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Copyright 2025 Vybestack LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * @plan PLAN-20251125-LOCALAUTH.P01
18
+ * @requirement REQ-LOCAL-001
19
+ *
20
+ * Utility to detect local/private network endpoints that don't require authentication.
21
+ * Used to fix Issue #598 where Ollama and other local AI servers were incorrectly
22
+ * requiring authentication.
23
+ */
24
+ /**
25
+ * Checks if a URL points to a local or private network endpoint.
26
+ *
27
+ * Local endpoints include:
28
+ * - localhost
29
+ * - 127.0.0.0/8 (IPv4 loopback range - 127.0.0.0 - 127.255.255.255)
30
+ * - [::1] (IPv6 loopback)
31
+ * - Private IP ranges (RFC 1918):
32
+ * - 10.0.0.0/8 (10.0.0.0 - 10.255.255.255)
33
+ * - 172.16.0.0/12 (172.16.0.0 - 172.31.255.255)
34
+ * - 192.168.0.0/16 (192.168.0.0 - 192.168.255.255)
35
+ *
36
+ * @param url - The URL to check (can be undefined or empty)
37
+ * @returns true if the URL points to a local/private endpoint, false otherwise
38
+ */
39
+ export declare function isLocalEndpoint(url: string | undefined): boolean;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Copyright 2025 Vybestack LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * @plan PLAN-20251125-LOCALAUTH.P01
18
+ * @requirement REQ-LOCAL-001
19
+ *
20
+ * Utility to detect local/private network endpoints that don't require authentication.
21
+ * Used to fix Issue #598 where Ollama and other local AI servers were incorrectly
22
+ * requiring authentication.
23
+ */
24
+ /**
25
+ * Checks if a URL points to a local or private network endpoint.
26
+ *
27
+ * Local endpoints include:
28
+ * - localhost
29
+ * - 127.0.0.0/8 (IPv4 loopback range - 127.0.0.0 - 127.255.255.255)
30
+ * - [::1] (IPv6 loopback)
31
+ * - Private IP ranges (RFC 1918):
32
+ * - 10.0.0.0/8 (10.0.0.0 - 10.255.255.255)
33
+ * - 172.16.0.0/12 (172.16.0.0 - 172.31.255.255)
34
+ * - 192.168.0.0/16 (192.168.0.0 - 192.168.255.255)
35
+ *
36
+ * @param url - The URL to check (can be undefined or empty)
37
+ * @returns true if the URL points to a local/private endpoint, false otherwise
38
+ */
39
+ export function isLocalEndpoint(url) {
40
+ if (!url || url.trim() === '') {
41
+ return false;
42
+ }
43
+ try {
44
+ const parsed = new URL(url);
45
+ const hostname = parsed.hostname.toLowerCase();
46
+ // Check for localhost
47
+ if (hostname === 'localhost') {
48
+ return true;
49
+ }
50
+ // Check for IPv4 loopback range (127.0.0.0/8)
51
+ // The entire 127.x.x.x range is reserved for loopback
52
+ if (isIPv4LoopbackRange(hostname)) {
53
+ return true;
54
+ }
55
+ // Check for IPv6 loopback (::1)
56
+ // URL parser represents [::1] as just ::1 in hostname
57
+ if (hostname === '::1' || hostname === '[::1]') {
58
+ return true;
59
+ }
60
+ // Check for private IP ranges
61
+ if (isPrivateIPv4(hostname)) {
62
+ return true;
63
+ }
64
+ return false;
65
+ }
66
+ catch {
67
+ // Invalid URL
68
+ return false;
69
+ }
70
+ }
71
+ /**
72
+ * Checks if an IPv4 address is in the loopback range (127.0.0.0/8).
73
+ * The entire 127.x.x.x range is reserved for loopback (RFC 1122).
74
+ */
75
+ function isIPv4LoopbackRange(ip) {
76
+ const ipv4Match = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
77
+ if (!ipv4Match) {
78
+ return false;
79
+ }
80
+ const firstOctet = Number(ipv4Match[1]);
81
+ return firstOctet === 127;
82
+ }
83
+ /**
84
+ * Checks if an IPv4 address is in a private range.
85
+ *
86
+ * Private ranges (RFC 1918):
87
+ * - 10.0.0.0/8 (10.0.0.0 - 10.255.255.255)
88
+ * - 172.16.0.0/12 (172.16.0.0 - 172.31.255.255)
89
+ * - 192.168.0.0/16 (192.168.0.0 - 192.168.255.255)
90
+ */
91
+ function isPrivateIPv4(ip) {
92
+ // Match IPv4 pattern
93
+ const ipv4Match = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
94
+ if (!ipv4Match) {
95
+ return false;
96
+ }
97
+ const octets = ipv4Match.slice(1).map(Number);
98
+ // Validate octets are in valid range
99
+ if (octets.some((octet) => octet < 0 || octet > 255)) {
100
+ return false;
101
+ }
102
+ const [first, second] = octets;
103
+ // 10.0.0.0/8
104
+ if (first === 10) {
105
+ return true;
106
+ }
107
+ // 172.16.0.0/12 (172.16.x.x - 172.31.x.x)
108
+ if (first === 172 && second >= 16 && second <= 31) {
109
+ return true;
110
+ }
111
+ // 192.168.0.0/16
112
+ if (first === 192 && second === 168) {
113
+ return true;
114
+ }
115
+ return false;
116
+ }
117
+ //# sourceMappingURL=localEndpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localEndpoint.js","sourceRoot":"","sources":["../../../../src/providers/utils/localEndpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAE/C,sBAAsB;QACtB,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,8CAA8C;QAC9C,sDAAsD;QACtD,IAAI,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,gCAAgC;QAChC,sDAAsD;QACtD,IAAI,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YAC/C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,8BAA8B;QAC9B,IAAI,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;QACd,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAAC,EAAU;IACrC,MAAM,SAAS,GAAG,EAAE,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAC3E,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,OAAO,UAAU,KAAK,GAAG,CAAC;AAC5B,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,EAAU;IAC/B,qBAAqB;IACrB,MAAM,SAAS,GAAG,EAAE,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAC3E,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAE9C,qCAAqC;IACrC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,GAAG,CAAC,EAAE,CAAC;QACrD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC;IAE/B,aAAa;IACb,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0CAA0C;IAC1C,IAAI,KAAK,KAAK,GAAG,IAAI,MAAM,IAAI,EAAE,IAAI,MAAM,IAAI,EAAE,EAAE,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iBAAiB;IACjB,IAAI,KAAK,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -3,7 +3,6 @@
3
3
  * Copyright 2025 Vybestack LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { type AnsiOutput } from '../utils/terminalSerializer.js';
7
6
  /** A structured result from a shell command execution. */
8
7
  export interface ShellExecutionResult {
9
8
  rawOutput: Buffer;
@@ -28,25 +27,11 @@ export interface ShellExecutionHandle {
28
27
  pid: number | undefined;
29
28
  result: Promise<ShellExecutionResult>;
30
29
  }
31
- export interface ShellExecutionConfig {
32
- terminalWidth?: number;
33
- terminalHeight?: number;
34
- pager?: string;
35
- showColor?: boolean;
36
- defaultFg?: string;
37
- defaultBg?: string;
38
- }
39
- /**
40
- * Describes a structured event emitted during shell command execution.
41
- */
42
30
  export type ShellOutputEvent = {
43
31
  /** The event contains a chunk of output data. */
44
32
  type: 'data';
45
- /**
46
- * The decoded string chunk (in plain mode) or structured AnsiOutput object
47
- * (in color/structured mode).
48
- */
49
- chunk: string | AnsiOutput;
33
+ /** The decoded string chunk. */
34
+ chunk: string;
50
35
  } | {
51
36
  /** Signals that the output stream has been identified as binary. */
52
37
  type: 'binary_detected';
@@ -56,13 +41,7 @@ export type ShellOutputEvent = {
56
41
  /** The total number of bytes received so far. */
57
42
  bytesReceived: number;
58
43
  };
59
- /**
60
- * A centralized service for executing shell commands with robust process
61
- * management, cross-platform compatibility, and streaming output capabilities.
62
- *
63
- */
64
44
  export declare class ShellExecutionService {
65
- private static activePtys;
66
45
  /**
67
46
  * Executes a shell command using `node-pty`, capturing all output and lifecycle events.
68
47
  *
@@ -75,29 +54,7 @@ export declare class ShellExecutionService {
75
54
  * @returns An object containing the process ID (pid) and a promise that
76
55
  * resolves with the complete execution result.
77
56
  */
78
- static execute(commandToExecute: string, cwd: string, onOutputEvent: (event: ShellOutputEvent) => void, abortSignal: AbortSignal, shouldUseNodePty: boolean, shellExecutionConfig: ShellExecutionConfig): Promise<ShellExecutionHandle>;
57
+ static execute(commandToExecute: string, cwd: string, onOutputEvent: (event: ShellOutputEvent) => void, abortSignal: AbortSignal, shouldUseNodePty: boolean, terminalColumns?: number, terminalRows?: number): Promise<ShellExecutionHandle>;
79
58
  private static childProcessFallback;
80
59
  private static executeWithPty;
81
- /**
82
- * Writes a string to the pseudo-terminal (PTY) of a running process.
83
- *
84
- * @param pid The process ID of the target PTY.
85
- * @param input The string to write to the terminal.
86
- */
87
- static writeToPty(pid: number, input: string): void;
88
- /**
89
- * Resizes the pseudo-terminal (PTY) of a running process.
90
- *
91
- * @param pid The process ID of the target PTY.
92
- * @param cols The new number of columns.
93
- * @param rows The new number of rows.
94
- */
95
- static resizePty(pid: number, cols: number, rows: number): void;
96
- /**
97
- * Scrolls the pseudo-terminal (PTY) of a running process.
98
- *
99
- * @param pid The process ID of the target PTY.
100
- * @param lines The number of lines to scroll.
101
- */
102
- static scrollPty(pid: number, lines: number): void;
103
60
  }
@@ -3,44 +3,27 @@
3
3
  * Copyright 2025 Vybestack LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import stripAnsi from 'strip-ansi';
7
6
  import { getPty } from '../utils/getPty.js';
8
- import { spawn as cpSpawn } from 'node:child_process';
9
- import { TextDecoder } from 'node:util';
10
- import os from 'node:os';
7
+ import { spawn as cpSpawn } from 'child_process';
8
+ import { TextDecoder } from 'util';
9
+ import os from 'os';
11
10
  import { getCachedEncodingForBuffer } from '../utils/systemEncoding.js';
12
11
  import { isBinary } from '../utils/textUtils.js';
13
12
  import pkg from '@xterm/headless';
14
- import { serializeTerminalToObject, } from '../utils/terminalSerializer.js';
13
+ import stripAnsi from 'strip-ansi';
15
14
  const { Terminal } = pkg;
16
15
  const SIGKILL_TIMEOUT_MS = 200;
17
- const getVisibleText = (terminal) => {
18
- const buffer = terminal.buffer.active;
19
- const lines = [];
20
- for (let i = 0; i < terminal.rows; i++) {
21
- const line = buffer.getLine(buffer.viewportY + i);
22
- const lineContent = line ? line.translateToString(true) : '';
23
- lines.push(lineContent);
24
- }
25
- return lines.join('\n').trimEnd();
26
- };
27
- const getFullBufferText = (terminal) => {
16
+ // @ts-expect-error getFullText is not a public API.
17
+ const getFullText = (terminal) => {
28
18
  const buffer = terminal.buffer.active;
29
19
  const lines = [];
30
20
  for (let i = 0; i < buffer.length; i++) {
31
21
  const line = buffer.getLine(i);
32
- const lineContent = line ? line.translateToString() : '';
33
- lines.push(lineContent);
22
+ lines.push(line ? line.translateToString(true) : '');
34
23
  }
35
- return lines.join('\n').trimEnd();
24
+ return lines.join('\n').trim();
36
25
  };
37
- /**
38
- * A centralized service for executing shell commands with robust process
39
- * management, cross-platform compatibility, and streaming output capabilities.
40
- *
41
- */
42
26
  export class ShellExecutionService {
43
- static activePtys = new Map();
44
27
  /**
45
28
  * Executes a shell command using `node-pty`, capturing all output and lifecycle events.
46
29
  *
@@ -53,28 +36,28 @@ export class ShellExecutionService {
53
36
  * @returns An object containing the process ID (pid) and a promise that
54
37
  * resolves with the complete execution result.
55
38
  */
56
- static async execute(commandToExecute, cwd, onOutputEvent, abortSignal, shouldUseNodePty, shellExecutionConfig) {
39
+ static async execute(commandToExecute, cwd, onOutputEvent, abortSignal, shouldUseNodePty, terminalColumns, terminalRows) {
57
40
  if (shouldUseNodePty) {
58
41
  const ptyInfo = await getPty();
59
42
  if (ptyInfo) {
60
43
  try {
61
- return this.executeWithPty(commandToExecute, cwd, onOutputEvent, abortSignal, shellExecutionConfig, ptyInfo);
44
+ return this.executeWithPty(commandToExecute, cwd, onOutputEvent, abortSignal, terminalColumns, terminalRows, ptyInfo);
62
45
  }
63
46
  catch (_e) {
64
47
  // Fallback to child_process
65
48
  }
66
49
  }
67
50
  }
68
- return this.childProcessFallback(commandToExecute, cwd, onOutputEvent, abortSignal, shellExecutionConfig);
51
+ return this.childProcessFallback(commandToExecute, cwd, onOutputEvent, abortSignal);
69
52
  }
70
- static childProcessFallback(commandToExecute, cwd, onOutputEvent, abortSignal, shellExecutionConfig) {
53
+ static childProcessFallback(commandToExecute, cwd, onOutputEvent, abortSignal) {
71
54
  try {
72
55
  const isWindows = os.platform() === 'win32';
73
56
  const envVars = {
74
57
  ...process.env,
75
58
  LLXPRT_CODE: '1',
76
59
  TERM: 'xterm-256color',
77
- PAGER: shellExecutionConfig?.pager ?? 'cat',
60
+ PAGER: 'cat',
78
61
  };
79
62
  delete envVars.BASH_ENV;
80
63
  const child = cpSpawn(commandToExecute, [], {
@@ -114,17 +97,27 @@ export class ShellExecutionService {
114
97
  sniffedBytes = sniffBuffer.length;
115
98
  if (isBinary(sniffBuffer)) {
116
99
  isStreamingRawContent = false;
100
+ onOutputEvent({ type: 'binary_detected' });
117
101
  }
118
102
  }
103
+ const decoder = stream === 'stdout' ? stdoutDecoder : stderrDecoder;
104
+ const decodedChunk = decoder.decode(data, { stream: true });
105
+ const strippedChunk = stripAnsi(decodedChunk);
106
+ if (stream === 'stdout') {
107
+ stdout += strippedChunk;
108
+ }
109
+ else {
110
+ stderr += strippedChunk;
111
+ }
119
112
  if (isStreamingRawContent) {
120
- const decoder = stream === 'stdout' ? stdoutDecoder : stderrDecoder;
121
- const decodedChunk = decoder.decode(data, { stream: true });
122
- if (stream === 'stdout') {
123
- stdout += decodedChunk;
124
- }
125
- else {
126
- stderr += decodedChunk;
127
- }
113
+ onOutputEvent({ type: 'data', chunk: strippedChunk });
114
+ }
115
+ else {
116
+ const totalBytes = outputChunks.reduce((sum, chunk) => sum + chunk.length, 0);
117
+ onOutputEvent({
118
+ type: 'binary_progress',
119
+ bytesReceived: totalBytes,
120
+ });
128
121
  }
129
122
  };
130
123
  const handleExit = (code, signal) => {
@@ -132,25 +125,16 @@ export class ShellExecutionService {
132
125
  // Ensure we don't add an extra newline if stdout already ends with one.
133
126
  const separator = stdout.endsWith('\n') ? '' : '\n';
134
127
  const combinedOutput = stdout + (stderr ? (stdout ? separator : '') + stderr : '');
135
- const finalStrippedOutput = stripAnsi(combinedOutput).trim();
136
- if (isStreamingRawContent) {
137
- if (finalStrippedOutput) {
138
- onOutputEvent({ type: 'data', chunk: finalStrippedOutput });
139
- }
140
- }
141
- else {
142
- onOutputEvent({ type: 'binary_detected' });
143
- }
144
128
  resolve({
145
129
  rawOutput: finalBuffer,
146
- output: finalStrippedOutput,
130
+ output: combinedOutput.trim(),
147
131
  stdout,
148
132
  stderr,
149
133
  exitCode: code,
150
134
  signal: signal ? (os.constants.signals[signal] ?? null) : null,
151
135
  error,
152
136
  aborted: abortSignal.aborted,
153
- pid: undefined,
137
+ pid: child.pid,
154
138
  executionMethod: 'child_process',
155
139
  });
156
140
  };
@@ -182,9 +166,6 @@ export class ShellExecutionService {
182
166
  };
183
167
  abortSignal.addEventListener('abort', abortHandler, { once: true });
184
168
  child.on('exit', (code, signal) => {
185
- if (child.pid) {
186
- this.activePtys.delete(child.pid);
187
- }
188
169
  handleExit(code, signal);
189
170
  });
190
171
  function cleanup() {
@@ -193,20 +174,20 @@ export class ShellExecutionService {
193
174
  if (stdoutDecoder) {
194
175
  const remaining = stdoutDecoder.decode();
195
176
  if (remaining) {
196
- stdout += remaining;
177
+ stdout += stripAnsi(remaining);
197
178
  }
198
179
  }
199
180
  if (stderrDecoder) {
200
181
  const remaining = stderrDecoder.decode();
201
182
  if (remaining) {
202
- stderr += remaining;
183
+ stderr += stripAnsi(remaining);
203
184
  }
204
185
  }
205
186
  const finalBuffer = Buffer.concat(outputChunks);
206
187
  return { stdout, stderr, finalBuffer };
207
188
  }
208
189
  });
209
- return { pid: undefined, result };
190
+ return { pid: child.pid, result };
210
191
  }
211
192
  catch (e) {
212
193
  const error = e;
@@ -227,14 +208,10 @@ export class ShellExecutionService {
227
208
  };
228
209
  }
229
210
  }
230
- static executeWithPty(commandToExecute, cwd, onOutputEvent, abortSignal, shellExecutionConfig, ptyInfo) {
231
- if (!ptyInfo) {
232
- // This should not happen, but as a safeguard...
233
- throw new Error('PTY implementation not found');
234
- }
211
+ static executeWithPty(commandToExecute, cwd, onOutputEvent, abortSignal, terminalColumns, terminalRows, ptyInfo) {
235
212
  try {
236
- const cols = shellExecutionConfig.terminalWidth ?? 80;
237
- const rows = shellExecutionConfig.terminalHeight ?? 30;
213
+ const cols = terminalColumns ?? 80;
214
+ const rows = terminalRows ?? 30;
238
215
  const isWindows = os.platform() === 'win32';
239
216
  const shell = isWindows ? 'cmd.exe' : 'bash';
240
217
  const args = isWindows
@@ -243,14 +220,13 @@ export class ShellExecutionService {
243
220
  const envVars = {
244
221
  ...process.env,
245
222
  LLXPRT_CODE: '1',
246
- GEMINI_CLI: '1',
247
223
  TERM: 'xterm-256color',
248
- PAGER: shellExecutionConfig.pager ?? 'cat',
224
+ PAGER: 'cat',
249
225
  };
250
226
  delete envVars.BASH_ENV;
251
- const ptyProcess = ptyInfo.module.spawn(shell, args, {
227
+ const ptyProcess = ptyInfo?.module.spawn(shell, args, {
252
228
  cwd,
253
- name: 'xterm',
229
+ name: 'xterm-color',
254
230
  cols,
255
231
  rows,
256
232
  env: envVars,
@@ -262,54 +238,14 @@ export class ShellExecutionService {
262
238
  cols,
263
239
  rows,
264
240
  });
265
- this.activePtys.set(ptyProcess.pid, { ptyProcess, headlessTerminal });
266
241
  let processingChain = Promise.resolve();
267
242
  let decoder = null;
268
- let output = null;
269
243
  const outputChunks = [];
270
244
  const error = null;
271
245
  let exited = false;
272
246
  let isStreamingRawContent = true;
273
247
  const MAX_SNIFF_SIZE = 4096;
274
248
  let sniffedBytes = 0;
275
- let isWriting = false;
276
- let renderTimeout = null;
277
- const render = (finalRender = false) => {
278
- if (renderTimeout) {
279
- clearTimeout(renderTimeout);
280
- }
281
- const renderFn = () => {
282
- if (!isStreamingRawContent) {
283
- return;
284
- }
285
- const newOutput = shellExecutionConfig.showColor
286
- ? serializeTerminalToObject(headlessTerminal, {
287
- defaultFg: shellExecutionConfig.defaultFg,
288
- defaultBg: shellExecutionConfig.defaultBg,
289
- })
290
- : getVisibleText(headlessTerminal);
291
- // console.log(newOutput)
292
- // Using stringify for a quick deep comparison.
293
- if (JSON.stringify(output) !== JSON.stringify(newOutput)) {
294
- output = newOutput;
295
- onOutputEvent({
296
- type: 'data',
297
- chunk: newOutput,
298
- });
299
- }
300
- };
301
- if (finalRender) {
302
- renderFn();
303
- }
304
- else {
305
- renderTimeout = setTimeout(renderFn, 17);
306
- }
307
- };
308
- headlessTerminal.onScroll(() => {
309
- if (!isWriting) {
310
- render();
311
- }
312
- });
313
249
  const handleOutput = (data) => {
314
250
  processingChain = processingChain.then(() => new Promise((resolve) => {
315
251
  if (!decoder) {
@@ -332,10 +268,11 @@ export class ShellExecutionService {
332
268
  }
333
269
  if (isStreamingRawContent) {
334
270
  const decodedChunk = decoder.decode(data, { stream: true });
335
- isWriting = true;
336
271
  headlessTerminal.write(decodedChunk, () => {
337
- render();
338
- isWriting = false;
272
+ onOutputEvent({
273
+ type: 'data',
274
+ chunk: stripAnsi(decodedChunk),
275
+ });
339
276
  resolve();
340
277
  });
341
278
  }
@@ -356,11 +293,9 @@ export class ShellExecutionService {
356
293
  ptyProcess.onExit(({ exitCode, signal }) => {
357
294
  exited = true;
358
295
  abortSignal.removeEventListener('abort', abortHandler);
359
- this.activePtys.delete(ptyProcess.pid);
360
296
  processingChain.then(() => {
361
- render(true);
362
297
  const finalBuffer = Buffer.concat(outputChunks);
363
- const fullOutput = getFullBufferText(headlessTerminal);
298
+ const fullOutput = getFullText(headlessTerminal);
364
299
  resolve({
365
300
  rawOutput: finalBuffer,
366
301
  output: fullOutput,
@@ -371,26 +306,13 @@ export class ShellExecutionService {
371
306
  error,
372
307
  aborted: abortSignal.aborted,
373
308
  pid: ptyProcess.pid,
374
- executionMethod: ptyInfo?.name ??
375
- 'node-pty',
309
+ executionMethod: ptyInfo?.name ?? 'node-pty',
376
310
  });
377
311
  });
378
312
  });
379
313
  const abortHandler = async () => {
380
314
  if (ptyProcess.pid && !exited) {
381
- if (os.platform() === 'win32') {
382
- ptyProcess.kill();
383
- }
384
- else {
385
- try {
386
- // Kill the entire process group
387
- process.kill(-ptyProcess.pid, 'SIGINT');
388
- }
389
- catch (_e) {
390
- // Fallback to killing just the process if the group kill fails
391
- ptyProcess.kill('SIGINT');
392
- }
393
- }
315
+ ptyProcess.kill('SIGHUP');
394
316
  }
395
317
  };
396
318
  abortSignal.addEventListener('abort', abortHandler, { once: true });
@@ -416,67 +338,5 @@ export class ShellExecutionService {
416
338
  };
417
339
  }
418
340
  }
419
- /**
420
- * Writes a string to the pseudo-terminal (PTY) of a running process.
421
- *
422
- * @param pid The process ID of the target PTY.
423
- * @param input The string to write to the terminal.
424
- */
425
- static writeToPty(pid, input) {
426
- const activePty = this.activePtys.get(pid);
427
- if (activePty) {
428
- activePty.ptyProcess.write(input);
429
- }
430
- }
431
- /**
432
- * Resizes the pseudo-terminal (PTY) of a running process.
433
- *
434
- * @param pid The process ID of the target PTY.
435
- * @param cols The new number of columns.
436
- * @param rows The new number of rows.
437
- */
438
- static resizePty(pid, cols, rows) {
439
- const activePty = this.activePtys.get(pid);
440
- if (activePty) {
441
- try {
442
- activePty.ptyProcess.resize(cols, rows);
443
- activePty.headlessTerminal.resize(cols, rows);
444
- }
445
- catch (e) {
446
- // Ignore errors if the pty has already exited, which can happen
447
- // due to a race condition between the exit event and this call.
448
- if (e instanceof Error && 'code' in e && e.code === 'ESRCH') {
449
- // ignore
450
- }
451
- else {
452
- throw e;
453
- }
454
- }
455
- }
456
- }
457
- /**
458
- * Scrolls the pseudo-terminal (PTY) of a running process.
459
- *
460
- * @param pid The process ID of the target PTY.
461
- * @param lines The number of lines to scroll.
462
- */
463
- static scrollPty(pid, lines) {
464
- const activePty = this.activePtys.get(pid);
465
- if (activePty) {
466
- try {
467
- activePty.headlessTerminal.scrollLines(lines);
468
- }
469
- catch (e) {
470
- // Ignore errors if the pty has already exited, which can happen
471
- // due to a race condition between the exit event and this call.
472
- if (e instanceof Error && 'code' in e && e.code === 'ESRCH') {
473
- // ignore
474
- }
475
- else {
476
- throw e;
477
- }
478
- }
479
- }
480
- }
481
341
  }
482
342
  //# sourceMappingURL=shellExecutionService.js.map