@code-yeongyu/senpi-pty 2026.8.6 → 2026.8.9-2
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/pipe-fallback.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
import type { NativePtyLoadResult } from "./native-loader.ts";
|
|
2
3
|
export interface PipeFallbackSessionOptions {
|
|
3
4
|
readonly command: string;
|
|
@@ -24,10 +25,21 @@ export type PipeFallbackOperationResult = {
|
|
|
24
25
|
readonly code: "exited" | "not_pty" | "not_started" | "stdin_unavailable";
|
|
25
26
|
readonly note: string;
|
|
26
27
|
};
|
|
28
|
+
type ChildProcessHandle = ReturnType<typeof spawn>;
|
|
27
29
|
type DataHandler = (chunk: Buffer) => void;
|
|
28
30
|
type ExitHandler = (exit: PipeFallbackSessionExit) => void;
|
|
29
31
|
export declare function isPipeFallbackForced(env?: Readonly<Record<string, string | undefined>>): boolean;
|
|
30
32
|
export declare function shouldUsePipeFallback(nativeLoadResult: NativePtyLoadResult, env?: Readonly<Record<string, string | undefined>>): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Terminate the child, preferring its whole process group on POSIX. The
|
|
35
|
+
* detached child is its own group leader and grandchildren inherit the stdio
|
|
36
|
+
* pipes, so leaving them alive keeps 'close' from ever firing (the POSIX twin
|
|
37
|
+
* of the Windows taskkill /T branch below). Every path that stops a child must
|
|
38
|
+
* go through here: when the group signal is unavailable (Windows, no pid) or
|
|
39
|
+
* the group is already gone, it still falls back to a direct kill, so the
|
|
40
|
+
* child is never left running.
|
|
41
|
+
*/
|
|
42
|
+
export declare function terminateChildTree(child: Pick<ChildProcessHandle, "pid" | "kill">, signal: NodeJS.Signals): "group" | "direct";
|
|
31
43
|
export declare class PipeFallbackSession {
|
|
32
44
|
readonly backend = "pipe-fallback";
|
|
33
45
|
readonly note = "Running with child_process pipe fallback because no PTY backend is active; terminal screen state and resize are unavailable.";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipe-fallback.d.ts","sourceRoot":"","sources":["../src/pipe-fallback.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pipe-fallback.d.ts","sourceRoot":"","sources":["../src/pipe-fallback.ts"],"names":[],"mappings":"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"}
|
package/dist/pipe-fallback.js
CHANGED
|
@@ -7,6 +7,29 @@ export function isPipeFallbackForced(env = process.env) {
|
|
|
7
7
|
export function shouldUsePipeFallback(nativeLoadResult, env = process.env) {
|
|
8
8
|
return isPipeFallbackForced(env) || nativeLoadResult.native === null;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Terminate the child, preferring its whole process group on POSIX. The
|
|
12
|
+
* detached child is its own group leader and grandchildren inherit the stdio
|
|
13
|
+
* pipes, so leaving them alive keeps 'close' from ever firing (the POSIX twin
|
|
14
|
+
* of the Windows taskkill /T branch below). Every path that stops a child must
|
|
15
|
+
* go through here: when the group signal is unavailable (Windows, no pid) or
|
|
16
|
+
* the group is already gone, it still falls back to a direct kill, so the
|
|
17
|
+
* child is never left running.
|
|
18
|
+
*/
|
|
19
|
+
export function terminateChildTree(child, signal) {
|
|
20
|
+
const pid = child.pid;
|
|
21
|
+
if (pid !== undefined && process.platform !== "win32") {
|
|
22
|
+
try {
|
|
23
|
+
process.kill(-pid, signal);
|
|
24
|
+
return "group";
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Group already gone — fall through to the direct kill.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
child.kill(signal);
|
|
31
|
+
return "direct";
|
|
32
|
+
}
|
|
10
33
|
function normalizeSpawnError(command, error) {
|
|
11
34
|
return {
|
|
12
35
|
code: "spawn_error",
|
|
@@ -53,6 +76,13 @@ export class PipeFallbackSession {
|
|
|
53
76
|
cwd: this.options.cwd,
|
|
54
77
|
env: this.options.env ? { ...process.env, ...this.options.env } : process.env,
|
|
55
78
|
stdio: ["pipe", "pipe", "pipe"],
|
|
79
|
+
// Detach on POSIX so the child leads its own session with no
|
|
80
|
+
// controlling terminal. Without this, a tty-reading program (e.g. a
|
|
81
|
+
// sudo password prompt) opens /dev/tty — the user's real terminal —
|
|
82
|
+
// and races the TUI's raw-mode stdin reader byte-by-byte, splitting
|
|
83
|
+
// keystrokes and Kitty escape sequences between the two readers.
|
|
84
|
+
// Detached, the /dev/tty open fails with a clear error instead.
|
|
85
|
+
detached: process.platform !== "win32",
|
|
56
86
|
windowsHide: true,
|
|
57
87
|
});
|
|
58
88
|
this.child = child;
|
|
@@ -73,7 +103,7 @@ export class PipeFallbackSession {
|
|
|
73
103
|
if (this.options.timeoutMs !== undefined) {
|
|
74
104
|
this.timeoutHandle = setTimeout(() => {
|
|
75
105
|
this.timedOut = true;
|
|
76
|
-
child
|
|
106
|
+
terminateChildTree(child, "SIGTERM");
|
|
77
107
|
}, this.options.timeoutMs);
|
|
78
108
|
}
|
|
79
109
|
}
|
|
@@ -164,7 +194,9 @@ export class PipeFallbackSession {
|
|
|
164
194
|
// Fall through to the direct kill if taskkill is unavailable.
|
|
165
195
|
}
|
|
166
196
|
}
|
|
167
|
-
this.child
|
|
197
|
+
if (terminateChildTree(this.child, signal) === "group") {
|
|
198
|
+
return { ok: true, note: `Sent ${signal} to child_process pipe fallback process group (pgid ${pid}).` };
|
|
199
|
+
}
|
|
168
200
|
return { ok: true, note: `Sent ${signal} to child_process pipe fallback session.` };
|
|
169
201
|
}
|
|
170
202
|
waitExit() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipe-fallback.js","sourceRoot":"","sources":["../src/pipe-fallback.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport type { NativePtyLoadResult } from \"./native-loader.ts\";\n\nexport interface PipeFallbackSessionOptions {\n\treadonly command: string;\n\treadonly args?: readonly string[];\n\treadonly cwd?: string;\n\treadonly env?: Readonly<Record<string, string | undefined>>;\n\treadonly timeoutMs?: number;\n}\n\nexport interface PipeFallbackSessionError {\n\treadonly code: \"spawn_error\";\n\treadonly message: string;\n}\n\nexport interface PipeFallbackSessionExit {\n\treadonly exitCode: number | null;\n\treadonly signal: NodeJS.Signals | null;\n\treadonly timedOut: boolean;\n\treadonly error?: PipeFallbackSessionError;\n}\n\nexport type PipeFallbackOperationResult =\n\t| {\n\t\t\treadonly ok: true;\n\t\t\treadonly note: string;\n\t }\n\t| {\n\t\t\treadonly ok: false;\n\t\t\treadonly code: \"exited\" | \"not_pty\" | \"not_started\" | \"stdin_unavailable\";\n\t\t\treadonly note: string;\n\t };\n\ntype ChildProcessHandle = ReturnType<typeof spawn>;\ntype DataHandler = (chunk: Buffer) => void;\ntype ExitHandler = (exit: PipeFallbackSessionExit) => void;\n\nconst PIPE_FALLBACK_NOTE =\n\t\"Running with child_process pipe fallback because no PTY backend is active; terminal screen state and resize are unavailable.\";\n\nexport function isPipeFallbackForced(env: Readonly<Record<string, string | undefined>> = process.env): boolean {\n\tconst value = env.SENPI_PTY_FORCE_PIPE;\n\treturn value === \"1\" || value === \"true\";\n}\n\nexport function shouldUsePipeFallback(\n\tnativeLoadResult: NativePtyLoadResult,\n\tenv: Readonly<Record<string, string | undefined>> = process.env,\n): boolean {\n\treturn isPipeFallbackForced(env) || nativeLoadResult.native === null;\n}\n\nfunction normalizeSpawnError(command: string, error: Error): PipeFallbackSessionError {\n\treturn {\n\t\tcode: \"spawn_error\",\n\t\tmessage: `Failed to spawn pipe fallback command \"${command}\": ${error.message}`,\n\t};\n}\n\nfunction toBuffer(chunk: string | Buffer): Buffer {\n\treturn Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n}\n\nfunction validateTimeout(timeoutMs: number | undefined): void {\n\tif (timeoutMs === undefined) return;\n\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\tthrow new Error(\"Invalid timeoutMs: must be a finite positive number\");\n\t}\n}\n\nexport class PipeFallbackSession {\n\treadonly backend = \"pipe-fallback\";\n\treadonly note = PIPE_FALLBACK_NOTE;\n\treadonly options: PipeFallbackSessionOptions;\n\tprivate child: ChildProcessHandle | null = null;\n\tprivate exitResult: PipeFallbackSessionExit | null = null;\n\tprivate exitPromise: Promise<PipeFallbackSessionExit>;\n\tprivate resolveExit: ((exit: PipeFallbackSessionExit) => void) | null = null;\n\tprivate spawnError: PipeFallbackSessionError | undefined;\n\tprivate timedOut = false;\n\tprivate timeoutHandle: NodeJS.Timeout | undefined;\n\tprivate readonly dataHandlers = new Set<DataHandler>();\n\tprivate readonly exitHandlers = new Set<ExitHandler>();\n\n\tconstructor(options: PipeFallbackSessionOptions) {\n\t\tthis.options = {\n\t\t\t...options,\n\t\t\targs: options.args ? [...options.args] : undefined,\n\t\t\tenv: options.env ? { ...options.env } : undefined,\n\t\t};\n\t\tthis.exitPromise = new Promise((resolve) => {\n\t\t\tthis.resolveExit = resolve;\n\t\t});\n\t}\n\n\tstart(): this {\n\t\tif (this.exitResult !== null) throw new Error(\"Cannot restart exited pipe fallback session\");\n\t\tif (this.child !== null) throw new Error(\"Pipe fallback session has already been started\");\n\t\tvalidateTimeout(this.options.timeoutMs);\n\n\t\ttry {\n\t\t\tconst child = spawn(this.options.command, [...(this.options.args ?? [])], {\n\t\t\t\tcwd: this.options.cwd,\n\t\t\t\tenv: this.options.env ? { ...process.env, ...this.options.env } : process.env,\n\t\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tthis.child = child;\n\t\t\tchild.stdout?.on(\"data\", (chunk: string | Buffer) => this.emitData(toBuffer(chunk)));\n\t\t\tchild.stderr?.on(\"data\", (chunk: string | Buffer) => this.emitData(toBuffer(chunk)));\n\t\t\tchild.stdin?.on(\"error\", () => {});\n\t\t\tchild.on(\"error\", (error: Error) => {\n\t\t\t\tthis.spawnError = normalizeSpawnError(this.options.command, error);\n\t\t\t});\n\t\t\tchild.on(\"close\", (exitCode, signal) => {\n\t\t\t\tthis.settleExit({\n\t\t\t\t\texitCode: this.spawnError ? null : exitCode,\n\t\t\t\t\tsignal,\n\t\t\t\t\ttimedOut: this.timedOut,\n\t\t\t\t\terror: this.spawnError,\n\t\t\t\t});\n\t\t\t});\n\t\t\tif (this.options.timeoutMs !== undefined) {\n\t\t\t\tthis.timeoutHandle = setTimeout(() => {\n\t\t\t\t\tthis.timedOut = true;\n\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t}, this.options.timeoutMs);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst cause = error instanceof Error ? error : new Error(String(error));\n\t\t\tthis.settleExit({\n\t\t\t\texitCode: null,\n\t\t\t\tsignal: null,\n\t\t\t\ttimedOut: false,\n\t\t\t\terror: normalizeSpawnError(this.options.command, cause),\n\t\t\t});\n\t\t}\n\t\treturn this;\n\t}\n\n\tonData(handler: DataHandler): () => void {\n\t\tthis.dataHandlers.add(handler);\n\t\treturn () => this.dataHandlers.delete(handler);\n\t}\n\n\tonExit(handler: ExitHandler): () => void {\n\t\tconst exitResult = this.exitResult;\n\t\tif (exitResult !== null) queueMicrotask(() => handler(exitResult));\n\t\telse this.exitHandlers.add(handler);\n\t\treturn () => this.exitHandlers.delete(handler);\n\t}\n\n\twrite(data: string | Uint8Array): PipeFallbackOperationResult {\n\t\tif (this.exitResult !== null) {\n\t\t\treturn { ok: false, code: \"exited\", note: \"Cannot write to pipe fallback stdin: session has exited.\" };\n\t\t}\n\t\tif (this.child === null) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tcode: \"not_started\",\n\t\t\t\tnote: \"Cannot write to pipe fallback stdin: session has not started.\",\n\t\t\t};\n\t\t}\n\t\tif (!this.child.stdin || this.child.stdin.destroyed || this.child.stdin.writableEnded) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tcode: \"stdin_unavailable\",\n\t\t\t\tnote: \"Cannot write to pipe fallback stdin: child stdin is unavailable.\",\n\t\t\t};\n\t\t}\n\t\tconst accepted = this.child.stdin.write(data);\n\t\treturn {\n\t\t\tok: true,\n\t\t\tnote: accepted\n\t\t\t\t? \"Wrote stdin through child_process pipe fallback; this is not a PTY.\"\n\t\t\t\t: \"Queued stdin through child_process pipe fallback with stream backpressure; this is not a PTY.\",\n\t\t};\n\t}\n\n\tcloseInput(): PipeFallbackOperationResult {\n\t\tif (this.exitResult !== null) {\n\t\t\treturn { ok: false, code: \"exited\", note: \"Cannot close pipe fallback stdin: session has exited.\" };\n\t\t}\n\t\tif (this.child === null) {\n\t\t\treturn { ok: false, code: \"not_started\", note: \"Cannot close pipe fallback stdin: session has not started.\" };\n\t\t}\n\t\tthis.child.stdin?.end();\n\t\treturn { ok: true, note: \"Closed child_process pipe fallback stdin.\" };\n\t}\n\n\tresize(cols: number, rows: number): PipeFallbackOperationResult {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tcode: \"not_pty\",\n\t\t\tnote: `Cannot resize pipe fallback session to ${cols}x${rows}: child_process pipes are not a PTY; session is still valid.`,\n\t\t};\n\t}\n\n\tkill(signal: NodeJS.Signals = \"SIGTERM\"): PipeFallbackOperationResult {\n\t\tif (this.exitResult !== null) {\n\t\t\treturn { ok: false, code: \"exited\", note: \"Cannot kill pipe fallback session: session has exited.\" };\n\t\t}\n\t\tif (this.child === null) {\n\t\t\treturn { ok: false, code: \"not_started\", note: \"Cannot kill pipe fallback session: session has not started.\" };\n\t\t}\n\t\tconst pid = this.child.pid;\n\t\tif (process.platform === \"win32\" && pid !== undefined) {\n\t\t\t// On Windows child.kill() TerminateProcess-es only the direct child, leaving\n\t\t\t// grandchildren (e.g. `sleep` under `bash.exe -c`) alive and holding the stdout\n\t\t\t// pipe open — so 'close' never fires and waitExit() hangs teardown. taskkill /T /F\n\t\t\t// terminates the whole tree so the pipe EOFs and the session settles.\n\t\t\ttry {\n\t\t\t\tspawn(\"taskkill\", [\"/pid\", String(pid), \"/T\", \"/F\"], { stdio: \"ignore\", windowsHide: true });\n\t\t\t\treturn { ok: true, note: `Terminated child_process pipe fallback process tree (pid ${pid}).` };\n\t\t\t} catch {\n\t\t\t\t// Fall through to the direct kill if taskkill is unavailable.\n\t\t\t}\n\t\t}\n\t\tthis.child.kill(signal);\n\t\treturn { ok: true, note: `Sent ${signal} to child_process pipe fallback session.` };\n\t}\n\n\twaitExit(): Promise<PipeFallbackSessionExit> {\n\t\treturn this.exitPromise;\n\t}\n\n\tprivate emitData(chunk: Buffer): void {\n\t\tfor (const handler of this.dataHandlers) handler(chunk);\n\t}\n\n\tprivate settleExit(exit: PipeFallbackSessionExit): void {\n\t\tif (this.exitResult !== null) return;\n\t\tif (this.timeoutHandle) clearTimeout(this.timeoutHandle);\n\t\tthis.exitResult = exit;\n\t\tthis.resolveExit?.(exit);\n\t\tfor (const handler of this.exitHandlers) handler(exit);\n\t\tthis.exitHandlers.clear();\n\t}\n}\n\nexport function createPipeFallbackSession(options: PipeFallbackSessionOptions): PipeFallbackSession {\n\treturn new PipeFallbackSession(options).start();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pipe-fallback.js","sourceRoot":"","sources":["../src/pipe-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAsC3C,MAAM,kBAAkB,GACvB,8HAA8H,CAAC;AAEhI,MAAM,UAAU,oBAAoB,CAAC,GAAG,GAAiD,OAAO,CAAC,GAAG;IACnG,MAAM,KAAK,GAAG,GAAG,CAAC,oBAAoB,CAAC;IACvC,OAAO,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,MAAM,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,qBAAqB,CACpC,gBAAqC,EACrC,GAAG,GAAiD,OAAO,CAAC,GAAG;IAE/D,OAAO,oBAAoB,CAAC,GAAG,CAAC,IAAI,gBAAgB,CAAC,MAAM,KAAK,IAAI,CAAC;AACtE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CACjC,KAA+C,EAC/C,MAAsB;IAEtB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;IACtB,IAAI,GAAG,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvD,IAAI,CAAC;YACJ,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC3B,OAAO,OAAO,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACR,wDAAwD;QACzD,CAAC;IACF,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnB,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,SAAS,mBAAmB,CAAC,OAAe,EAAE,KAAY;IACzD,OAAO;QACN,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,0CAA0C,OAAO,MAAM,KAAK,CAAC,OAAO,EAAE;KAC/E,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAAsB;IACvC,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,eAAe,CAAC,SAA6B;IACrD,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO;IACpC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACxE,CAAC;AACF,CAAC;AAED,MAAM,OAAO,mBAAmB;IAc/B,YAAY,OAAmC;QAbtC,YAAO,GAAG,eAAe,CAAC;QAC1B,SAAI,GAAG,kBAAkB,CAAC;QAE3B,UAAK,GAA8B,IAAI,CAAC;QACxC,eAAU,GAAmC,IAAI,CAAC;QAElD,gBAAW,GAAqD,IAAI,CAAC;QAErE,aAAQ,GAAG,KAAK,CAAC;QAER,iBAAY,GAAG,IAAI,GAAG,EAAe,CAAC;QACtC,iBAAY,GAAG,IAAI,GAAG,EAAe,CAAC;QAGtD,IAAI,CAAC,OAAO,GAAG;YACd,GAAG,OAAO;YACV,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;YAClD,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;SACjD,CAAC;QACF,IAAI,CAAC,WAAW,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC1C,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;QAC5B,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,KAAK;QACJ,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAC7F,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAC3F,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAExC,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,EAAE;gBACzE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;gBACrB,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG;gBAC7E,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;gBAC/B,6DAA6D;gBAC7D,oEAAoE;gBACpE,oEAAoE;gBACpE,oEAAoE;gBACpE,iEAAiE;gBACjE,gEAAgE;gBAChE,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;gBACtC,WAAW,EAAE,IAAI;aACjB,CAAC,CAAC;YACH,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAsB,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACrF,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAsB,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACrF,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACnC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;gBAClC,IAAI,CAAC,UAAU,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACpE,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;gBACtC,IAAI,CAAC,UAAU,CAAC;oBACf,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ;oBAC3C,MAAM;oBACN,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,KAAK,EAAE,IAAI,CAAC,UAAU;iBACtB,CAAC,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC1C,IAAI,CAAC,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;oBACpC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;oBACrB,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;gBACtC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAC5B,CAAC;QACF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACxE,IAAI,CAAC,UAAU,CAAC;gBACf,QAAQ,EAAE,IAAI;gBACd,MAAM,EAAE,IAAI;gBACZ,QAAQ,EAAE,KAAK;gBACf,KAAK,EAAE,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC;aACvD,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,CAAC,OAAoB;QAC1B,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC/B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,CAAC,OAAoB;QAC1B,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QACnC,IAAI,UAAU,KAAK,IAAI;YAAE,cAAc,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;;YAC9D,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACpC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,IAAyB;QAC9B,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC9B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,0DAA0D,EAAE,CAAC;QACxG,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACzB,OAAO;gBACN,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,+DAA+D;aACrE,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;YACvF,OAAO;gBACN,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,mBAAmB;gBACzB,IAAI,EAAE,kEAAkE;aACxE,CAAC;QACH,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO;YACN,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,QAAQ;gBACb,CAAC,CAAC,qEAAqE;gBACvE,CAAC,CAAC,+FAA+F;SAClG,CAAC;IACH,CAAC;IAED,UAAU;QACT,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC9B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,uDAAuD,EAAE,CAAC;QACrG,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACzB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,4DAA4D,EAAE,CAAC;QAC/G,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC;QACxB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,2CAA2C,EAAE,CAAC;IACxE,CAAC;IAED,MAAM,CAAC,IAAY,EAAE,IAAY;QAChC,OAAO;YACN,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,0CAA0C,IAAI,IAAI,IAAI,8DAA8D;SAC1H,CAAC;IACH,CAAC;IAED,IAAI,CAAC,MAAM,GAAmB,SAAS;QACtC,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC9B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,wDAAwD,EAAE,CAAC;QACtG,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACzB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,6DAA6D,EAAE,CAAC;QAChH,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;QAC3B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACvD,6EAA6E;YAC7E,gFAAgF;YAChF,mFAAmF;YACnF,sEAAsE;YACtE,IAAI,CAAC;gBACJ,KAAK,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC7F,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,4DAA4D,GAAG,IAAI,EAAE,CAAC;YAChG,CAAC;YAAC,MAAM,CAAC;gBACR,8DAA8D;YAC/D,CAAC;QACF,CAAC;QACD,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,EAAE,CAAC;YACxD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,MAAM,uDAAuD,GAAG,IAAI,EAAE,CAAC;QACzG,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,MAAM,0CAA0C,EAAE,CAAC;IACrF,CAAC;IAED,QAAQ;QACP,OAAO,IAAI,CAAC,WAAW,CAAC;IACzB,CAAC;IAEO,QAAQ,CAAC,KAAa;QAC7B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACzD,CAAC;IAEO,UAAU,CAAC,IAA6B;QAC/C,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI;YAAE,OAAO;QACrC,IAAI,IAAI,CAAC,aAAa;YAAE,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACzD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC;QACzB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACvD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;CACD;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAmC;IAC5E,OAAO,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC;AACjD,CAAC","sourcesContent":["import { spawn } from \"node:child_process\";\nimport type { NativePtyLoadResult } from \"./native-loader.ts\";\n\nexport interface PipeFallbackSessionOptions {\n\treadonly command: string;\n\treadonly args?: readonly string[];\n\treadonly cwd?: string;\n\treadonly env?: Readonly<Record<string, string | undefined>>;\n\treadonly timeoutMs?: number;\n}\n\nexport interface PipeFallbackSessionError {\n\treadonly code: \"spawn_error\";\n\treadonly message: string;\n}\n\nexport interface PipeFallbackSessionExit {\n\treadonly exitCode: number | null;\n\treadonly signal: NodeJS.Signals | null;\n\treadonly timedOut: boolean;\n\treadonly error?: PipeFallbackSessionError;\n}\n\nexport type PipeFallbackOperationResult =\n\t| {\n\t\t\treadonly ok: true;\n\t\t\treadonly note: string;\n\t }\n\t| {\n\t\t\treadonly ok: false;\n\t\t\treadonly code: \"exited\" | \"not_pty\" | \"not_started\" | \"stdin_unavailable\";\n\t\t\treadonly note: string;\n\t };\n\ntype ChildProcessHandle = ReturnType<typeof spawn>;\ntype DataHandler = (chunk: Buffer) => void;\ntype ExitHandler = (exit: PipeFallbackSessionExit) => void;\n\nconst PIPE_FALLBACK_NOTE =\n\t\"Running with child_process pipe fallback because no PTY backend is active; terminal screen state and resize are unavailable.\";\n\nexport function isPipeFallbackForced(env: Readonly<Record<string, string | undefined>> = process.env): boolean {\n\tconst value = env.SENPI_PTY_FORCE_PIPE;\n\treturn value === \"1\" || value === \"true\";\n}\n\nexport function shouldUsePipeFallback(\n\tnativeLoadResult: NativePtyLoadResult,\n\tenv: Readonly<Record<string, string | undefined>> = process.env,\n): boolean {\n\treturn isPipeFallbackForced(env) || nativeLoadResult.native === null;\n}\n\n/**\n * Terminate the child, preferring its whole process group on POSIX. The\n * detached child is its own group leader and grandchildren inherit the stdio\n * pipes, so leaving them alive keeps 'close' from ever firing (the POSIX twin\n * of the Windows taskkill /T branch below). Every path that stops a child must\n * go through here: when the group signal is unavailable (Windows, no pid) or\n * the group is already gone, it still falls back to a direct kill, so the\n * child is never left running.\n */\nexport function terminateChildTree(\n\tchild: Pick<ChildProcessHandle, \"pid\" | \"kill\">,\n\tsignal: NodeJS.Signals,\n): \"group\" | \"direct\" {\n\tconst pid = child.pid;\n\tif (pid !== undefined && process.platform !== \"win32\") {\n\t\ttry {\n\t\t\tprocess.kill(-pid, signal);\n\t\t\treturn \"group\";\n\t\t} catch {\n\t\t\t// Group already gone — fall through to the direct kill.\n\t\t}\n\t}\n\tchild.kill(signal);\n\treturn \"direct\";\n}\n\nfunction normalizeSpawnError(command: string, error: Error): PipeFallbackSessionError {\n\treturn {\n\t\tcode: \"spawn_error\",\n\t\tmessage: `Failed to spawn pipe fallback command \"${command}\": ${error.message}`,\n\t};\n}\n\nfunction toBuffer(chunk: string | Buffer): Buffer {\n\treturn Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n}\n\nfunction validateTimeout(timeoutMs: number | undefined): void {\n\tif (timeoutMs === undefined) return;\n\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\tthrow new Error(\"Invalid timeoutMs: must be a finite positive number\");\n\t}\n}\n\nexport class PipeFallbackSession {\n\treadonly backend = \"pipe-fallback\";\n\treadonly note = PIPE_FALLBACK_NOTE;\n\treadonly options: PipeFallbackSessionOptions;\n\tprivate child: ChildProcessHandle | null = null;\n\tprivate exitResult: PipeFallbackSessionExit | null = null;\n\tprivate exitPromise: Promise<PipeFallbackSessionExit>;\n\tprivate resolveExit: ((exit: PipeFallbackSessionExit) => void) | null = null;\n\tprivate spawnError: PipeFallbackSessionError | undefined;\n\tprivate timedOut = false;\n\tprivate timeoutHandle: NodeJS.Timeout | undefined;\n\tprivate readonly dataHandlers = new Set<DataHandler>();\n\tprivate readonly exitHandlers = new Set<ExitHandler>();\n\n\tconstructor(options: PipeFallbackSessionOptions) {\n\t\tthis.options = {\n\t\t\t...options,\n\t\t\targs: options.args ? [...options.args] : undefined,\n\t\t\tenv: options.env ? { ...options.env } : undefined,\n\t\t};\n\t\tthis.exitPromise = new Promise((resolve) => {\n\t\t\tthis.resolveExit = resolve;\n\t\t});\n\t}\n\n\tstart(): this {\n\t\tif (this.exitResult !== null) throw new Error(\"Cannot restart exited pipe fallback session\");\n\t\tif (this.child !== null) throw new Error(\"Pipe fallback session has already been started\");\n\t\tvalidateTimeout(this.options.timeoutMs);\n\n\t\ttry {\n\t\t\tconst child = spawn(this.options.command, [...(this.options.args ?? [])], {\n\t\t\t\tcwd: this.options.cwd,\n\t\t\t\tenv: this.options.env ? { ...process.env, ...this.options.env } : process.env,\n\t\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\t\t// Detach on POSIX so the child leads its own session with no\n\t\t\t\t// controlling terminal. Without this, a tty-reading program (e.g. a\n\t\t\t\t// sudo password prompt) opens /dev/tty — the user's real terminal —\n\t\t\t\t// and races the TUI's raw-mode stdin reader byte-by-byte, splitting\n\t\t\t\t// keystrokes and Kitty escape sequences between the two readers.\n\t\t\t\t// Detached, the /dev/tty open fails with a clear error instead.\n\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tthis.child = child;\n\t\t\tchild.stdout?.on(\"data\", (chunk: string | Buffer) => this.emitData(toBuffer(chunk)));\n\t\t\tchild.stderr?.on(\"data\", (chunk: string | Buffer) => this.emitData(toBuffer(chunk)));\n\t\t\tchild.stdin?.on(\"error\", () => {});\n\t\t\tchild.on(\"error\", (error: Error) => {\n\t\t\t\tthis.spawnError = normalizeSpawnError(this.options.command, error);\n\t\t\t});\n\t\t\tchild.on(\"close\", (exitCode, signal) => {\n\t\t\t\tthis.settleExit({\n\t\t\t\t\texitCode: this.spawnError ? null : exitCode,\n\t\t\t\t\tsignal,\n\t\t\t\t\ttimedOut: this.timedOut,\n\t\t\t\t\terror: this.spawnError,\n\t\t\t\t});\n\t\t\t});\n\t\t\tif (this.options.timeoutMs !== undefined) {\n\t\t\t\tthis.timeoutHandle = setTimeout(() => {\n\t\t\t\t\tthis.timedOut = true;\n\t\t\t\t\tterminateChildTree(child, \"SIGTERM\");\n\t\t\t\t}, this.options.timeoutMs);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst cause = error instanceof Error ? error : new Error(String(error));\n\t\t\tthis.settleExit({\n\t\t\t\texitCode: null,\n\t\t\t\tsignal: null,\n\t\t\t\ttimedOut: false,\n\t\t\t\terror: normalizeSpawnError(this.options.command, cause),\n\t\t\t});\n\t\t}\n\t\treturn this;\n\t}\n\n\tonData(handler: DataHandler): () => void {\n\t\tthis.dataHandlers.add(handler);\n\t\treturn () => this.dataHandlers.delete(handler);\n\t}\n\n\tonExit(handler: ExitHandler): () => void {\n\t\tconst exitResult = this.exitResult;\n\t\tif (exitResult !== null) queueMicrotask(() => handler(exitResult));\n\t\telse this.exitHandlers.add(handler);\n\t\treturn () => this.exitHandlers.delete(handler);\n\t}\n\n\twrite(data: string | Uint8Array): PipeFallbackOperationResult {\n\t\tif (this.exitResult !== null) {\n\t\t\treturn { ok: false, code: \"exited\", note: \"Cannot write to pipe fallback stdin: session has exited.\" };\n\t\t}\n\t\tif (this.child === null) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tcode: \"not_started\",\n\t\t\t\tnote: \"Cannot write to pipe fallback stdin: session has not started.\",\n\t\t\t};\n\t\t}\n\t\tif (!this.child.stdin || this.child.stdin.destroyed || this.child.stdin.writableEnded) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tcode: \"stdin_unavailable\",\n\t\t\t\tnote: \"Cannot write to pipe fallback stdin: child stdin is unavailable.\",\n\t\t\t};\n\t\t}\n\t\tconst accepted = this.child.stdin.write(data);\n\t\treturn {\n\t\t\tok: true,\n\t\t\tnote: accepted\n\t\t\t\t? \"Wrote stdin through child_process pipe fallback; this is not a PTY.\"\n\t\t\t\t: \"Queued stdin through child_process pipe fallback with stream backpressure; this is not a PTY.\",\n\t\t};\n\t}\n\n\tcloseInput(): PipeFallbackOperationResult {\n\t\tif (this.exitResult !== null) {\n\t\t\treturn { ok: false, code: \"exited\", note: \"Cannot close pipe fallback stdin: session has exited.\" };\n\t\t}\n\t\tif (this.child === null) {\n\t\t\treturn { ok: false, code: \"not_started\", note: \"Cannot close pipe fallback stdin: session has not started.\" };\n\t\t}\n\t\tthis.child.stdin?.end();\n\t\treturn { ok: true, note: \"Closed child_process pipe fallback stdin.\" };\n\t}\n\n\tresize(cols: number, rows: number): PipeFallbackOperationResult {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tcode: \"not_pty\",\n\t\t\tnote: `Cannot resize pipe fallback session to ${cols}x${rows}: child_process pipes are not a PTY; session is still valid.`,\n\t\t};\n\t}\n\n\tkill(signal: NodeJS.Signals = \"SIGTERM\"): PipeFallbackOperationResult {\n\t\tif (this.exitResult !== null) {\n\t\t\treturn { ok: false, code: \"exited\", note: \"Cannot kill pipe fallback session: session has exited.\" };\n\t\t}\n\t\tif (this.child === null) {\n\t\t\treturn { ok: false, code: \"not_started\", note: \"Cannot kill pipe fallback session: session has not started.\" };\n\t\t}\n\t\tconst pid = this.child.pid;\n\t\tif (process.platform === \"win32\" && pid !== undefined) {\n\t\t\t// On Windows child.kill() TerminateProcess-es only the direct child, leaving\n\t\t\t// grandchildren (e.g. `sleep` under `bash.exe -c`) alive and holding the stdout\n\t\t\t// pipe open — so 'close' never fires and waitExit() hangs teardown. taskkill /T /F\n\t\t\t// terminates the whole tree so the pipe EOFs and the session settles.\n\t\t\ttry {\n\t\t\t\tspawn(\"taskkill\", [\"/pid\", String(pid), \"/T\", \"/F\"], { stdio: \"ignore\", windowsHide: true });\n\t\t\t\treturn { ok: true, note: `Terminated child_process pipe fallback process tree (pid ${pid}).` };\n\t\t\t} catch {\n\t\t\t\t// Fall through to the direct kill if taskkill is unavailable.\n\t\t\t}\n\t\t}\n\t\tif (terminateChildTree(this.child, signal) === \"group\") {\n\t\t\treturn { ok: true, note: `Sent ${signal} to child_process pipe fallback process group (pgid ${pid}).` };\n\t\t}\n\t\treturn { ok: true, note: `Sent ${signal} to child_process pipe fallback session.` };\n\t}\n\n\twaitExit(): Promise<PipeFallbackSessionExit> {\n\t\treturn this.exitPromise;\n\t}\n\n\tprivate emitData(chunk: Buffer): void {\n\t\tfor (const handler of this.dataHandlers) handler(chunk);\n\t}\n\n\tprivate settleExit(exit: PipeFallbackSessionExit): void {\n\t\tif (this.exitResult !== null) return;\n\t\tif (this.timeoutHandle) clearTimeout(this.timeoutHandle);\n\t\tthis.exitResult = exit;\n\t\tthis.resolveExit?.(exit);\n\t\tfor (const handler of this.exitHandlers) handler(exit);\n\t\tthis.exitHandlers.clear();\n\t}\n}\n\nexport function createPipeFallbackSession(options: PipeFallbackSessionOptions): PipeFallbackSession {\n\treturn new PipeFallbackSession(options).start();\n}\n"]}
|
|
Binary file
|