feat(pty): added PTY-backed interactive command execution with streaming output support

- Added PTY (pseudo-terminal) backed interactive command execution with streaming output support via PtySession class in pi-natives.
- Added PtyStartOptions and PtyRunResult types to configure and report PTY session execution status.
- Added write(), resize(), and kill() methods to PtySession for interactive control of running commands.
- Added interactive bash execution via PTY with real-time terminal rendering and input forwarding in bash-interactive tool.
- Added --no-pty CLI flag and PI_NO_PTY environment variable to disable PTY-based interactive bash execution.
- Added bash.virtualTerminal setting to control PTY-backed interactive execution behavior.
This commit is contained in:
can1357
2026-02-12 04:34:24 +01:00
parent cf864bbf7e
commit 2c7a32f28c
19 changed files with 1076 additions and 88 deletions
+38 -22
View File
@@ -18,6 +18,19 @@ import { EditMatchError } from "./types";
// Diff String Generation
// ═══════════════════════════════════════════════════════════════════════════
function countContentLines(content: string): number {
const lines = content.split("\n");
if (lines.length > 1 && lines[lines.length - 1] === "") {
lines.pop();
}
return Math.max(1, lines.length);
}
function formatNumberedDiffLine(prefix: "+" | "-" | " ", lineNum: number, width: number, content: string): string {
const padded = String(lineNum).padStart(width, " ");
return `${prefix}${padded}|${content}`;
}
/**
* Generate a unified diff string with line numbers and context.
* Returns both the diff string and the first changed line number (in the new file).
@@ -26,15 +39,7 @@ export function generateDiffString(oldContent: string, newContent: string, conte
const parts = Diff.diffLines(oldContent, newContent);
const output: string[] = [];
const countLines = (content: string): number => {
const lines = content.split("\n");
if (lines.length > 1 && lines[lines.length - 1] === "") {
lines.pop();
}
return Math.max(1, lines.length);
};
const maxLineNum = Math.max(countLines(oldContent), countLines(newContent));
const maxLineNum = Math.max(countContentLines(oldContent), countContentLines(newContent));
const lineNumWidth = String(maxLineNum).length;
let oldLineNum = 1;
@@ -58,12 +63,10 @@ export function generateDiffString(oldContent: string, newContent: string, conte
// Show the change
for (const line of raw) {
if (part.added) {
const lineNum = String(newLineNum).padStart(lineNumWidth, " ");
output.push(`+${lineNum} ${line}`);
output.push(formatNumberedDiffLine("+", newLineNum, lineNumWidth, line));
newLineNum++;
} else {
const lineNum = String(oldLineNum).padStart(lineNumWidth, " ");
output.push(`-${lineNum} ${line}`);
output.push(formatNumberedDiffLine("-", oldLineNum, lineNumWidth, line));
oldLineNum++;
}
}
@@ -91,21 +94,20 @@ export function generateDiffString(oldContent: string, newContent: string, conte
// Add ellipsis if we skipped lines at start
if (skipStart > 0) {
output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
output.push(formatNumberedDiffLine(" ", oldLineNum, lineNumWidth, "..."));
oldLineNum += skipStart;
newLineNum += skipStart;
}
for (const line of linesToShow) {
const lineNum = String(oldLineNum).padStart(lineNumWidth, " ");
output.push(` ${lineNum} ${line}`);
output.push(formatNumberedDiffLine(" ", oldLineNum, lineNumWidth, line));
oldLineNum++;
newLineNum++;
}
// Add ellipsis if we skipped lines at end
if (skipEnd > 0) {
output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
output.push(formatNumberedDiffLine(" ", oldLineNum, lineNumWidth, "..."));
oldLineNum += skipEnd;
newLineNum += skipEnd;
}
@@ -150,18 +152,32 @@ export function generateUnifiedDiffString(oldContent: string, newContent: string
const patch = Diff.structuredPatch("", "", oldContent, newContent, "", "", { context: contextLines });
const output: string[] = [];
let firstChangedLine: number | undefined;
const maxLineNum = Math.max(countContentLines(oldContent), countContentLines(newContent));
const lineNumWidth = String(maxLineNum).length;
for (const hunk of patch.hunks) {
output.push(`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`);
let oldLine = hunk.oldStart;
let newLine = hunk.newStart;
for (const line of hunk.lines) {
output.push(line);
if (firstChangedLine === undefined && (line.startsWith("+") || line.startsWith("-"))) {
firstChangedLine = newLine;
if (line.startsWith("-")) {
if (firstChangedLine === undefined) firstChangedLine = newLine;
output.push(formatNumberedDiffLine("-", oldLine, lineNumWidth, line.slice(1)));
oldLine++;
continue;
}
if (line.startsWith("+") || line.startsWith(" ")) {
if (line.startsWith("+")) {
if (firstChangedLine === undefined) firstChangedLine = newLine;
output.push(formatNumberedDiffLine("+", newLine, lineNumWidth, line.slice(1)));
newLine++;
continue;
}
if (line.startsWith(" ")) {
output.push(formatNumberedDiffLine(" ", oldLine, lineNumWidth, line.slice(1)));
oldLine++;
newLine++;
continue;
}
output.push(line);
}
}