@code-yeongyu/senpi-pty 2026.7.25-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/README.md +5 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +56 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +130 -0
- package/dist/loader.js.map +1 -0
- package/dist/native-loader.d.ts +2 -0
- package/dist/native-loader.d.ts.map +1 -0
- package/dist/native-loader.js +2 -0
- package/dist/native-loader.js.map +1 -0
- package/dist/pipe-fallback.d.ts +58 -0
- package/dist/pipe-fallback.d.ts.map +1 -0
- package/dist/pipe-fallback.js +192 -0
- package/dist/pipe-fallback.js.map +1 -0
- package/dist/registry-detached.d.ts +5 -0
- package/dist/registry-detached.d.ts.map +1 -0
- package/dist/registry-detached.js +57 -0
- package/dist/registry-detached.js.map +1 -0
- package/dist/registry-session.d.ts +7 -0
- package/dist/registry-session.d.ts.map +1 -0
- package/dist/registry-session.js +73 -0
- package/dist/registry-session.js.map +1 -0
- package/dist/registry-types.d.ts +94 -0
- package/dist/registry-types.d.ts.map +1 -0
- package/dist/registry-types.js +9 -0
- package/dist/registry-types.js.map +1 -0
- package/dist/registry.d.ts +41 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +232 -0
- package/dist/registry.js.map +1 -0
- package/dist/screen.d.ts +33 -0
- package/dist/screen.d.ts.map +1 -0
- package/dist/screen.js +155 -0
- package/dist/screen.js.map +1 -0
- package/dist/session-exit.d.ts +8 -0
- package/dist/session-exit.d.ts.map +1 -0
- package/dist/session-exit.js +103 -0
- package/dist/session-exit.js.map +1 -0
- package/dist/session-native.d.ts +4 -0
- package/dist/session-native.d.ts.map +1 -0
- package/dist/session-native.js +94 -0
- package/dist/session-native.js.map +1 -0
- package/dist/session-options.d.ts +10 -0
- package/dist/session-options.d.ts.map +1 -0
- package/dist/session-options.js +38 -0
- package/dist/session-options.js.map +1 -0
- package/dist/session-types.d.ts +64 -0
- package/dist/session-types.d.ts.map +1 -0
- package/dist/session-types.js +2 -0
- package/dist/session-types.js.map +1 -0
- package/dist/session.d.ts +47 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +187 -0
- package/dist/session.js.map +1 -0
- package/native/check-prebuild-fresh.mjs +171 -0
- package/native/index.d.ts +25 -0
- package/native/index.js +30 -0
- package/native/prebuilds/darwin-arm64/senpi_pty.darwin-arm64.node +0 -0
- package/package.json +61 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { DEFAULT_SIGNAL } from "./registry-session.js";
|
|
3
|
+
export function getRuntimePlatform() {
|
|
4
|
+
return process.platform;
|
|
5
|
+
}
|
|
6
|
+
export function defaultKillProcess(target, signal) {
|
|
7
|
+
process.kill(target, signal);
|
|
8
|
+
}
|
|
9
|
+
export async function cleanupDetachedChildren(session, platform, killProcess) {
|
|
10
|
+
for (const child of getTrackedDetachedChildren(session)) {
|
|
11
|
+
if (isTrackedDetachedChildExited(child))
|
|
12
|
+
continue;
|
|
13
|
+
if (child.kill) {
|
|
14
|
+
await child.kill(DEFAULT_SIGNAL);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const target = getDetachedChildKillTarget(child, platform);
|
|
18
|
+
if (target === null)
|
|
19
|
+
continue;
|
|
20
|
+
try {
|
|
21
|
+
killProcess(target, DEFAULT_SIGNAL);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (!isMissingProcessError(error))
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function getTrackedDetachedChildren(session) {
|
|
30
|
+
return session.getTrackedDetachedChildren?.() ?? session.trackedDetachedChildren ?? [];
|
|
31
|
+
}
|
|
32
|
+
function isTrackedDetachedChildExited(child) {
|
|
33
|
+
if (typeof child.exited === "boolean")
|
|
34
|
+
return child.exited;
|
|
35
|
+
if (typeof child.exited === "function")
|
|
36
|
+
return child.exited();
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function getDetachedChildKillTarget(child, platform) {
|
|
40
|
+
if (platform !== "win32" && isPositiveInteger(child.processGroupId))
|
|
41
|
+
return -child.processGroupId;
|
|
42
|
+
if (isPositiveInteger(child.pid))
|
|
43
|
+
return child.pid;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function isPositiveInteger(value) {
|
|
47
|
+
return value !== undefined && Number.isInteger(value) && value > 0;
|
|
48
|
+
}
|
|
49
|
+
function isMissingProcessError(error) {
|
|
50
|
+
if (!isRecord(error))
|
|
51
|
+
return false;
|
|
52
|
+
return error.code === "ESRCH";
|
|
53
|
+
}
|
|
54
|
+
function isRecord(value) {
|
|
55
|
+
return typeof value === "object" && value !== null;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=registry-detached.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-detached.js","sourceRoot":"","sources":["../src/registry-detached.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,MAAM,UAAU,kBAAkB;IACjC,OAAO,OAAO,CAAC,QAAQ,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,MAA6B;IAC/E,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC5C,OAA+B,EAC/B,QAAgB,EAChB,WAAoE;IAEpE,KAAK,MAAM,KAAK,IAAI,0BAA0B,CAAC,OAAO,CAAC,EAAE,CAAC;QACzD,IAAI,4BAA4B,CAAC,KAAK,CAAC;YAAE,SAAS;QAClD,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YAChB,MAAM,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACjC,SAAS;QACV,CAAC;QACD,MAAM,MAAM,GAAG,0BAA0B,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAC3D,IAAI,MAAM,KAAK,IAAI;YAAE,SAAS;QAC9B,IAAI,CAAC;YACJ,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC;gBAAE,MAAM,KAAK,CAAC;QAChD,CAAC;IACF,CAAC;AACF,CAAC;AAED,SAAS,0BAA0B,CAAC,OAA+B;IAClE,OAAO,OAAO,CAAC,0BAA0B,EAAE,EAAE,IAAI,OAAO,CAAC,uBAAuB,IAAI,EAAE,CAAC;AACxF,CAAC;AAED,SAAS,4BAA4B,CAAC,KAA2B;IAChE,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3D,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;IAC9D,OAAO,KAAK,CAAC;AACd,CAAC;AAED,SAAS,0BAA0B,CAAC,KAA2B,EAAE,QAAgB;IAChF,IAAI,QAAQ,KAAK,OAAO,IAAI,iBAAiB,CAAC,KAAK,CAAC,cAAc,CAAC;QAAE,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC;IAClG,IAAI,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC;IACnD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAyB;IACnD,OAAO,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAc;IAC5C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;AAC/B,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACpD,CAAC","sourcesContent":["import process from \"node:process\";\nimport { DEFAULT_SIGNAL } from \"./registry-session.ts\";\nimport type { SessionRegistrySession, TerminalSessionSignal, TrackedDetachedChild } from \"./registry-types.ts\";\n\nexport function getRuntimePlatform(): string {\n\treturn process.platform;\n}\n\nexport function defaultKillProcess(target: number, signal: TerminalSessionSignal): void {\n\tprocess.kill(target, signal);\n}\n\nexport async function cleanupDetachedChildren(\n\tsession: SessionRegistrySession,\n\tplatform: string,\n\tkillProcess: (target: number, signal: TerminalSessionSignal) => void,\n): Promise<void> {\n\tfor (const child of getTrackedDetachedChildren(session)) {\n\t\tif (isTrackedDetachedChildExited(child)) continue;\n\t\tif (child.kill) {\n\t\t\tawait child.kill(DEFAULT_SIGNAL);\n\t\t\tcontinue;\n\t\t}\n\t\tconst target = getDetachedChildKillTarget(child, platform);\n\t\tif (target === null) continue;\n\t\ttry {\n\t\t\tkillProcess(target, DEFAULT_SIGNAL);\n\t\t} catch (error) {\n\t\t\tif (!isMissingProcessError(error)) throw error;\n\t\t}\n\t}\n}\n\nfunction getTrackedDetachedChildren(session: SessionRegistrySession): readonly TrackedDetachedChild[] {\n\treturn session.getTrackedDetachedChildren?.() ?? session.trackedDetachedChildren ?? [];\n}\n\nfunction isTrackedDetachedChildExited(child: TrackedDetachedChild): boolean {\n\tif (typeof child.exited === \"boolean\") return child.exited;\n\tif (typeof child.exited === \"function\") return child.exited();\n\treturn false;\n}\n\nfunction getDetachedChildKillTarget(child: TrackedDetachedChild, platform: string): number | null {\n\tif (platform !== \"win32\" && isPositiveInteger(child.processGroupId)) return -child.processGroupId;\n\tif (isPositiveInteger(child.pid)) return child.pid;\n\treturn null;\n}\n\nfunction isPositiveInteger(value: number | undefined): value is number {\n\treturn value !== undefined && Number.isInteger(value) && value > 0;\n}\n\nfunction isMissingProcessError(error: unknown): boolean {\n\tif (!isRecord(error)) return false;\n\treturn error.code === \"ESRCH\";\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null;\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SessionRegistrySession, TerminalSessionSignal } from "./registry-types.ts";
|
|
2
|
+
export declare const DEFAULT_SIGNAL: TerminalSessionSignal;
|
|
3
|
+
export declare function sessionIdPrefix(command: string): string;
|
|
4
|
+
export declare function isTerminalSessionExited(session: SessionRegistrySession): boolean;
|
|
5
|
+
export declare function stopTerminalSession(session: SessionRegistrySession): Promise<void>;
|
|
6
|
+
export declare function waitForTerminalSessionExit(session: SessionRegistrySession, graceMs?: number): Promise<boolean>;
|
|
7
|
+
//# sourceMappingURL=registry-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-session.d.ts","sourceRoot":"","sources":["../src/registry-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAGzF,eAAO,MAAM,cAAc,EAAE,qBAAiC,CAAC;AAE/D,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAYvD;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAOhF;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxF;AAED,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,sBAAsB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA0BpH"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const DEFAULT_COMMAND = "bash";
|
|
2
|
+
export const DEFAULT_SIGNAL = "SIGTERM";
|
|
3
|
+
export function sessionIdPrefix(command) {
|
|
4
|
+
const parts = command.split(/[\\/]/).filter(Boolean);
|
|
5
|
+
const baseName = parts[parts.length - 1] ?? DEFAULT_COMMAND;
|
|
6
|
+
// Strip a Windows executable extension first so `bash.exe` collapses to the same
|
|
7
|
+
// `bash` prefix as POSIX `/bin/bash`; otherwise the `.exe` sanitizes to `_exe` and
|
|
8
|
+
// session ids become `bash_exe_1`, breaking the cross-platform `bash_N` scheme.
|
|
9
|
+
const withoutExt = baseName.replace(/\.(?:exe|com|bat|cmd|ps1)$/i, "");
|
|
10
|
+
const prefix = withoutExt
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
13
|
+
.replace(/^_+|_+$/g, "");
|
|
14
|
+
return prefix || "session";
|
|
15
|
+
}
|
|
16
|
+
export function isTerminalSessionExited(session) {
|
|
17
|
+
if (session.exited === true)
|
|
18
|
+
return true;
|
|
19
|
+
if (typeof session.isExited === "boolean")
|
|
20
|
+
return session.isExited;
|
|
21
|
+
if (typeof session.isExited === "function" && session.isExited())
|
|
22
|
+
return true;
|
|
23
|
+
if (session.exitState?.status === "exited")
|
|
24
|
+
return true;
|
|
25
|
+
if (session.status === "exited" || session.status === "closed" || session.status === "stopped")
|
|
26
|
+
return true;
|
|
27
|
+
return session.exitResult !== undefined && session.exitResult !== null;
|
|
28
|
+
}
|
|
29
|
+
export async function stopTerminalSession(session) {
|
|
30
|
+
// Call through `session` so class methods keep their `this` binding.
|
|
31
|
+
if (session.stop) {
|
|
32
|
+
await session.stop();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (session.kill) {
|
|
36
|
+
await session.kill(DEFAULT_SIGNAL);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (session.signal)
|
|
40
|
+
await session.signal(DEFAULT_SIGNAL);
|
|
41
|
+
}
|
|
42
|
+
export async function waitForTerminalSessionExit(session, graceMs) {
|
|
43
|
+
if (isTerminalSessionExited(session))
|
|
44
|
+
return true;
|
|
45
|
+
// Invoke via the session object (not a detached reference) so `this` is preserved.
|
|
46
|
+
const wait = session.waitExit ? session.waitExit() : session.wait ? session.wait() : null;
|
|
47
|
+
if (wait === null)
|
|
48
|
+
return false;
|
|
49
|
+
if (graceMs === undefined) {
|
|
50
|
+
await wait;
|
|
51
|
+
return isTerminalSessionExited(session);
|
|
52
|
+
}
|
|
53
|
+
let graceTimer;
|
|
54
|
+
try {
|
|
55
|
+
const exitSettled = await Promise.race([
|
|
56
|
+
Promise.resolve(wait).then(() => true),
|
|
57
|
+
new Promise((resolve) => {
|
|
58
|
+
graceTimer = setTimeout(() => resolve(false), graceMs);
|
|
59
|
+
}),
|
|
60
|
+
]);
|
|
61
|
+
if (!exitSettled) {
|
|
62
|
+
// The wait may still settle (or reject) long after we stopped caring.
|
|
63
|
+
void Promise.resolve(wait).catch(() => { });
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (graceTimer)
|
|
69
|
+
clearTimeout(graceTimer);
|
|
70
|
+
}
|
|
71
|
+
return isTerminalSessionExited(session);
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=registry-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-session.js","sourceRoot":"","sources":["../src/registry-session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionRegistrySession, TerminalSessionSignal } from \"./registry-types.ts\";\n\nconst DEFAULT_COMMAND = \"bash\";\nexport const DEFAULT_SIGNAL: TerminalSessionSignal = \"SIGTERM\";\n\nexport function sessionIdPrefix(command: string): string {\n\tconst parts = command.split(/[\\\\/]/).filter(Boolean);\n\tconst baseName = parts[parts.length - 1] ?? DEFAULT_COMMAND;\n\t// Strip a Windows executable extension first so `bash.exe` collapses to the same\n\t// `bash` prefix as POSIX `/bin/bash`; otherwise the `.exe` sanitizes to `_exe` and\n\t// session ids become `bash_exe_1`, breaking the cross-platform `bash_N` scheme.\n\tconst withoutExt = baseName.replace(/\\.(?:exe|com|bat|cmd|ps1)$/i, \"\");\n\tconst prefix = withoutExt\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"_\")\n\t\t.replace(/^_+|_+$/g, \"\");\n\treturn prefix || \"session\";\n}\n\nexport function isTerminalSessionExited(session: SessionRegistrySession): boolean {\n\tif (session.exited === true) return true;\n\tif (typeof session.isExited === \"boolean\") return session.isExited;\n\tif (typeof session.isExited === \"function\" && session.isExited()) return true;\n\tif (session.exitState?.status === \"exited\") return true;\n\tif (session.status === \"exited\" || session.status === \"closed\" || session.status === \"stopped\") return true;\n\treturn session.exitResult !== undefined && session.exitResult !== null;\n}\n\nexport async function stopTerminalSession(session: SessionRegistrySession): Promise<void> {\n\t// Call through `session` so class methods keep their `this` binding.\n\tif (session.stop) {\n\t\tawait session.stop();\n\t\treturn;\n\t}\n\tif (session.kill) {\n\t\tawait session.kill(DEFAULT_SIGNAL);\n\t\treturn;\n\t}\n\tif (session.signal) await session.signal(DEFAULT_SIGNAL);\n}\n\nexport async function waitForTerminalSessionExit(session: SessionRegistrySession, graceMs?: number): Promise<boolean> {\n\tif (isTerminalSessionExited(session)) return true;\n\t// Invoke via the session object (not a detached reference) so `this` is preserved.\n\tconst wait = session.waitExit ? session.waitExit() : session.wait ? session.wait() : null;\n\tif (wait === null) return false;\n\tif (graceMs === undefined) {\n\t\tawait wait;\n\t\treturn isTerminalSessionExited(session);\n\t}\n\tlet graceTimer: ReturnType<typeof setTimeout> | undefined;\n\ttry {\n\t\tconst exitSettled = await Promise.race([\n\t\t\tPromise.resolve(wait).then(() => true),\n\t\t\tnew Promise<false>((resolve) => {\n\t\t\t\tgraceTimer = setTimeout(() => resolve(false), graceMs);\n\t\t\t}),\n\t\t]);\n\t\tif (!exitSettled) {\n\t\t\t// The wait may still settle (or reject) long after we stopped caring.\n\t\t\tvoid Promise.resolve(wait).catch(() => {});\n\t\t\treturn false;\n\t\t}\n\t} finally {\n\t\tif (graceTimer) clearTimeout(graceTimer);\n\t}\n\treturn isTerminalSessionExited(session);\n}\n"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export type TerminalSessionState = "live" | "stopping" | "exited";
|
|
2
|
+
export type TerminalSessionSignal = NodeJS.Signals;
|
|
3
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
4
|
+
export interface TrackedDetachedChild {
|
|
5
|
+
readonly pid?: number;
|
|
6
|
+
readonly processGroupId?: number;
|
|
7
|
+
readonly exited?: boolean | (() => boolean);
|
|
8
|
+
readonly kill?: (signal?: TerminalSessionSignal) => MaybePromise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface SessionRegistrySession {
|
|
11
|
+
readonly command?: string;
|
|
12
|
+
readonly exited?: boolean;
|
|
13
|
+
readonly isExited?: boolean | (() => boolean);
|
|
14
|
+
readonly exitResult?: unknown;
|
|
15
|
+
readonly exitState?: {
|
|
16
|
+
readonly status?: string;
|
|
17
|
+
};
|
|
18
|
+
readonly status?: string;
|
|
19
|
+
readonly trackedDetachedChildren?: readonly TrackedDetachedChild[];
|
|
20
|
+
readonly getTrackedDetachedChildren?: () => readonly TrackedDetachedChild[];
|
|
21
|
+
readonly onExit?: (handler: () => void) => (() => void) | undefined;
|
|
22
|
+
readonly stop?: () => MaybePromise<unknown>;
|
|
23
|
+
readonly kill?: (signal?: TerminalSessionSignal) => MaybePromise<unknown>;
|
|
24
|
+
readonly signal?: (signal: TerminalSessionSignal) => MaybePromise<unknown>;
|
|
25
|
+
readonly waitExit?: () => Promise<unknown>;
|
|
26
|
+
readonly wait?: () => Promise<unknown>;
|
|
27
|
+
}
|
|
28
|
+
export interface SessionRegistryCreateOptions<TSession extends SessionRegistrySession = SessionRegistrySession> {
|
|
29
|
+
readonly command?: string;
|
|
30
|
+
readonly args?: readonly string[];
|
|
31
|
+
readonly cwd?: string;
|
|
32
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
33
|
+
readonly cols?: number;
|
|
34
|
+
readonly rows?: number;
|
|
35
|
+
readonly session?: TSession;
|
|
36
|
+
}
|
|
37
|
+
export interface SessionRegistryCreateContext {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly command: string;
|
|
40
|
+
readonly args?: readonly string[];
|
|
41
|
+
readonly cwd?: string;
|
|
42
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
43
|
+
readonly cols?: number;
|
|
44
|
+
readonly rows?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface InitialSessionRegistryEntry<TSession extends SessionRegistrySession = SessionRegistrySession> {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly session: TSession;
|
|
49
|
+
readonly command?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface SessionRegistryEntry<TSession extends SessionRegistrySession = SessionRegistrySession> {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly command: string;
|
|
54
|
+
readonly session: TSession;
|
|
55
|
+
readonly createdAt: number;
|
|
56
|
+
readonly lastUsedAt: number;
|
|
57
|
+
readonly state: TerminalSessionState;
|
|
58
|
+
readonly exitedAt: number | null;
|
|
59
|
+
}
|
|
60
|
+
export interface SessionRegistrySweepOptions {
|
|
61
|
+
readonly remove?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface SessionRegistryOptions<TSession extends SessionRegistrySession = SessionRegistrySession> {
|
|
64
|
+
readonly maxSessions?: number;
|
|
65
|
+
readonly initialSessions?: readonly InitialSessionRegistryEntry<TSession>[];
|
|
66
|
+
readonly createSession?: (options: SessionRegistryCreateContext) => MaybePromise<TSession>;
|
|
67
|
+
readonly killProcess?: (target: number, signal: TerminalSessionSignal) => void;
|
|
68
|
+
readonly now?: () => number;
|
|
69
|
+
readonly platform?: string;
|
|
70
|
+
/**
|
|
71
|
+
* How long `stop()` waits for a killed session to report exit before marking
|
|
72
|
+
* it `stopping` and returning. Guards against sessions whose exit never
|
|
73
|
+
* settles (e.g. a surviving descendant holding the PTY open). Default 5s.
|
|
74
|
+
*/
|
|
75
|
+
readonly stopExitGraceMs?: number;
|
|
76
|
+
}
|
|
77
|
+
export type StoredSessionRegistryEntry<TSession extends SessionRegistrySession> = {
|
|
78
|
+
id: string;
|
|
79
|
+
command: string;
|
|
80
|
+
session: TSession;
|
|
81
|
+
createdAt: number;
|
|
82
|
+
lastUsedAt: number;
|
|
83
|
+
state: TerminalSessionState;
|
|
84
|
+
exitedAt: number | null;
|
|
85
|
+
unsubscribeExit: (() => void) | null;
|
|
86
|
+
stopPromise: Promise<boolean> | null;
|
|
87
|
+
detachedCleanupPromise: Promise<void> | null;
|
|
88
|
+
};
|
|
89
|
+
export declare class SessionRegistryCapacityError extends Error {
|
|
90
|
+
readonly code = "session_registry_capacity";
|
|
91
|
+
readonly maxSessions: number;
|
|
92
|
+
constructor(maxSessions: number);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=registry-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-types.d.ts","sourceRoot":"","sources":["../src/registry-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export class SessionRegistryCapacityError extends Error {
|
|
2
|
+
constructor(maxSessions) {
|
|
3
|
+
super(`Cannot create terminal session: all ${maxSessions} registry slots are live.`);
|
|
4
|
+
this.code = "session_registry_capacity";
|
|
5
|
+
this.name = "SessionRegistryCapacityError";
|
|
6
|
+
this.maxSessions = maxSessions;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=registry-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-types.js","sourceRoot":"","sources":["../src/registry-types.ts"],"names":[],"mappings":"AAgGA,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IAItD,YAAY,WAAmB;QAC9B,KAAK,CAAC,uCAAuC,WAAW,2BAA2B,CAAC,CAAC;QAJ7E,SAAI,GAAG,2BAA2B,CAAC;QAK3C,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAC;QAC3C,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAChC,CAAC;CACD","sourcesContent":["export type TerminalSessionState = \"live\" | \"stopping\" | \"exited\";\nexport type TerminalSessionSignal = NodeJS.Signals;\nexport type MaybePromise<T> = T | Promise<T>;\n\nexport interface TrackedDetachedChild {\n\treadonly pid?: number;\n\treadonly processGroupId?: number;\n\treadonly exited?: boolean | (() => boolean);\n\treadonly kill?: (signal?: TerminalSessionSignal) => MaybePromise<unknown>;\n}\n\nexport interface SessionRegistrySession {\n\treadonly command?: string;\n\treadonly exited?: boolean;\n\treadonly isExited?: boolean | (() => boolean);\n\treadonly exitResult?: unknown;\n\treadonly exitState?: { readonly status?: string };\n\treadonly status?: string;\n\treadonly trackedDetachedChildren?: readonly TrackedDetachedChild[];\n\treadonly getTrackedDetachedChildren?: () => readonly TrackedDetachedChild[];\n\treadonly onExit?: (handler: () => void) => (() => void) | undefined;\n\treadonly stop?: () => MaybePromise<unknown>;\n\treadonly kill?: (signal?: TerminalSessionSignal) => MaybePromise<unknown>;\n\treadonly signal?: (signal: TerminalSessionSignal) => MaybePromise<unknown>;\n\treadonly waitExit?: () => Promise<unknown>;\n\treadonly wait?: () => Promise<unknown>;\n}\n\nexport interface SessionRegistryCreateOptions<TSession extends SessionRegistrySession = SessionRegistrySession> {\n\treadonly command?: string;\n\treadonly args?: readonly string[];\n\treadonly cwd?: string;\n\treadonly env?: Readonly<Record<string, string | undefined>>;\n\treadonly cols?: number;\n\treadonly rows?: number;\n\treadonly session?: TSession;\n}\n\nexport interface SessionRegistryCreateContext {\n\treadonly id: string;\n\treadonly command: string;\n\treadonly args?: readonly string[];\n\treadonly cwd?: string;\n\treadonly env?: Readonly<Record<string, string | undefined>>;\n\treadonly cols?: number;\n\treadonly rows?: number;\n}\n\nexport interface InitialSessionRegistryEntry<TSession extends SessionRegistrySession = SessionRegistrySession> {\n\treadonly id: string;\n\treadonly session: TSession;\n\treadonly command?: string;\n}\n\nexport interface SessionRegistryEntry<TSession extends SessionRegistrySession = SessionRegistrySession> {\n\treadonly id: string;\n\treadonly command: string;\n\treadonly session: TSession;\n\treadonly createdAt: number;\n\treadonly lastUsedAt: number;\n\treadonly state: TerminalSessionState;\n\treadonly exitedAt: number | null;\n}\n\nexport interface SessionRegistrySweepOptions {\n\treadonly remove?: boolean;\n}\n\nexport interface SessionRegistryOptions<TSession extends SessionRegistrySession = SessionRegistrySession> {\n\treadonly maxSessions?: number;\n\treadonly initialSessions?: readonly InitialSessionRegistryEntry<TSession>[];\n\treadonly createSession?: (options: SessionRegistryCreateContext) => MaybePromise<TSession>;\n\treadonly killProcess?: (target: number, signal: TerminalSessionSignal) => void;\n\treadonly now?: () => number;\n\treadonly platform?: string;\n\t/**\n\t * How long `stop()` waits for a killed session to report exit before marking\n\t * it `stopping` and returning. Guards against sessions whose exit never\n\t * settles (e.g. a surviving descendant holding the PTY open). Default 5s.\n\t */\n\treadonly stopExitGraceMs?: number;\n}\n\nexport type StoredSessionRegistryEntry<TSession extends SessionRegistrySession> = {\n\tid: string;\n\tcommand: string;\n\tsession: TSession;\n\tcreatedAt: number;\n\tlastUsedAt: number;\n\tstate: TerminalSessionState;\n\texitedAt: number | null;\n\tunsubscribeExit: (() => void) | null;\n\tstopPromise: Promise<boolean> | null;\n\tdetachedCleanupPromise: Promise<void> | null;\n};\n\nexport class SessionRegistryCapacityError extends Error {\n\treadonly code = \"session_registry_capacity\";\n\treadonly maxSessions: number;\n\n\tconstructor(maxSessions: number) {\n\t\tsuper(`Cannot create terminal session: all ${maxSessions} registry slots are live.`);\n\t\tthis.name = \"SessionRegistryCapacityError\";\n\t\tthis.maxSessions = maxSessions;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SessionRegistryCreateOptions, SessionRegistryEntry, SessionRegistryOptions, SessionRegistrySession, SessionRegistrySweepOptions } from "./registry-types.ts";
|
|
2
|
+
export { isTerminalSessionExited, sessionIdPrefix } from "./registry-session.ts";
|
|
3
|
+
export type { InitialSessionRegistryEntry, MaybePromise, SessionRegistryCreateContext, SessionRegistryCreateOptions, SessionRegistryEntry, SessionRegistryOptions, SessionRegistrySession, SessionRegistrySweepOptions, TerminalSessionSignal, TerminalSessionState, TrackedDetachedChild, } from "./registry-types.ts";
|
|
4
|
+
export { SessionRegistryCapacityError } from "./registry-types.ts";
|
|
5
|
+
export declare class SessionRegistry<TSession extends SessionRegistrySession = SessionRegistrySession> {
|
|
6
|
+
private readonly entries;
|
|
7
|
+
private readonly idCounters;
|
|
8
|
+
private readonly createSession;
|
|
9
|
+
private readonly killProcess;
|
|
10
|
+
private readonly now;
|
|
11
|
+
private readonly platform;
|
|
12
|
+
private sequence;
|
|
13
|
+
readonly maxSessions: number;
|
|
14
|
+
private readonly stopExitGraceMs;
|
|
15
|
+
constructor(options?: SessionRegistryOptions<TSession>);
|
|
16
|
+
get size(): number;
|
|
17
|
+
list(): readonly SessionRegistryEntry<TSession>[];
|
|
18
|
+
get(id: string): SessionRegistryEntry<TSession> | null;
|
|
19
|
+
create(options?: SessionRegistryCreateOptions<TSession>): Promise<SessionRegistryEntry<TSession>>;
|
|
20
|
+
sweepExited(options?: SessionRegistrySweepOptions): Promise<readonly string[]>;
|
|
21
|
+
stop(id: string): Promise<boolean>;
|
|
22
|
+
stopAll(): Promise<void>;
|
|
23
|
+
teardown(): Promise<void>;
|
|
24
|
+
private createSessionWithFactory;
|
|
25
|
+
private addEntry;
|
|
26
|
+
private handleSessionExit;
|
|
27
|
+
private enforceCapacityForCreate;
|
|
28
|
+
private pruneExitedUntilAtMost;
|
|
29
|
+
private findLeastRecentlyUsedExited;
|
|
30
|
+
private stopEntry;
|
|
31
|
+
private cleanupDetachedChildren;
|
|
32
|
+
private cleanupDetachedChildrenOnce;
|
|
33
|
+
private refreshExitedStates;
|
|
34
|
+
private refreshEntryState;
|
|
35
|
+
private markExited;
|
|
36
|
+
private removeEntry;
|
|
37
|
+
private allocateId;
|
|
38
|
+
private observeId;
|
|
39
|
+
private timestamp;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAGX,4BAA4B,EAC5B,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,2BAA2B,EAG3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACjF,YAAY,EACX,2BAA2B,EAC3B,YAAY,EACZ,4BAA4B,EAC5B,4BAA4B,EAC5B,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AASnE,qBAAa,eAAe,CAAC,QAAQ,SAAS,sBAAsB,GAAG,sBAAsB;IAC5F,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2D;IACnF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IACxD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6E;IAC3G,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0D;IACtF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAK;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAEzC,YAAY,OAAO,GAAE,sBAAsB,CAAC,QAAQ,CAAM,EAiBzD;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,IAAI,SAAS,oBAAoB,CAAC,QAAQ,CAAC,EAAE,CAGhD;IAED,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB,CAAC,QAAQ,CAAC,GAAG,IAAI,CAMrD;IAEK,MAAM,CAAC,OAAO,GAAE,4BAA4B,CAAC,QAAQ,CAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAgB1G;IAEK,WAAW,CAAC,OAAO,GAAE,2BAAgC,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAUvF;IAEK,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMvC;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAE7B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAG9B;YAEa,wBAAwB;IAOtC,OAAO,CAAC,QAAQ;YAwBF,iBAAiB;YAOjB,wBAAwB;YAMxB,sBAAsB;IASpC,OAAO,CAAC,2BAA2B;YAUrB,SAAS;YAeT,uBAAuB;YAMvB,2BAA2B;IAIzC,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,SAAS;CAIjB"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { cleanupDetachedChildren, defaultKillProcess, getRuntimePlatform } from "./registry-detached.js";
|
|
2
|
+
import { isTerminalSessionExited, sessionIdPrefix, stopTerminalSession, waitForTerminalSessionExit, } from "./registry-session.js";
|
|
3
|
+
export { isTerminalSessionExited, sessionIdPrefix } from "./registry-session.js";
|
|
4
|
+
export { SessionRegistryCapacityError } from "./registry-types.js";
|
|
5
|
+
import { SessionRegistryCapacityError } from "./registry-types.js";
|
|
6
|
+
const DEFAULT_MAX_SESSIONS = 32;
|
|
7
|
+
const DEFAULT_COMMAND = "bash";
|
|
8
|
+
const DEFAULT_STOP_EXIT_GRACE_MS = 5000;
|
|
9
|
+
const MAX_STOP_EXIT_GRACE_MS = 2_147_483_647;
|
|
10
|
+
export class SessionRegistry {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.entries = new Map();
|
|
13
|
+
this.idCounters = new Map();
|
|
14
|
+
this.sequence = 0;
|
|
15
|
+
this.maxSessions = normalizeMaxSessions(options.maxSessions);
|
|
16
|
+
this.stopExitGraceMs =
|
|
17
|
+
options.stopExitGraceMs !== undefined &&
|
|
18
|
+
Number.isFinite(options.stopExitGraceMs) &&
|
|
19
|
+
options.stopExitGraceMs > 0
|
|
20
|
+
? Math.min(options.stopExitGraceMs, MAX_STOP_EXIT_GRACE_MS)
|
|
21
|
+
: DEFAULT_STOP_EXIT_GRACE_MS;
|
|
22
|
+
this.createSession = options.createSession ?? null;
|
|
23
|
+
this.killProcess = options.killProcess ?? defaultKillProcess;
|
|
24
|
+
this.now = options.now ?? Date.now;
|
|
25
|
+
this.platform = options.platform ?? getRuntimePlatform();
|
|
26
|
+
for (const initial of options.initialSessions ?? []) {
|
|
27
|
+
this.addEntry(initial.id, initial.session, initial.command ?? initial.session.command ?? DEFAULT_COMMAND);
|
|
28
|
+
}
|
|
29
|
+
void this.sweepExited();
|
|
30
|
+
}
|
|
31
|
+
get size() {
|
|
32
|
+
return this.entries.size;
|
|
33
|
+
}
|
|
34
|
+
list() {
|
|
35
|
+
this.refreshExitedStates();
|
|
36
|
+
return [...this.entries.values()].sort((left, right) => left.createdAt - right.createdAt);
|
|
37
|
+
}
|
|
38
|
+
get(id) {
|
|
39
|
+
const entry = this.entries.get(id);
|
|
40
|
+
if (!entry)
|
|
41
|
+
return null;
|
|
42
|
+
entry.lastUsedAt = this.timestamp();
|
|
43
|
+
this.refreshEntryState(entry);
|
|
44
|
+
return entry;
|
|
45
|
+
}
|
|
46
|
+
async create(options = {}) {
|
|
47
|
+
await this.enforceCapacityForCreate();
|
|
48
|
+
const command = options.command ?? options.session?.command ?? DEFAULT_COMMAND;
|
|
49
|
+
const id = this.allocateId(command);
|
|
50
|
+
const session = options.session ??
|
|
51
|
+
(await this.createSessionWithFactory({
|
|
52
|
+
id,
|
|
53
|
+
command,
|
|
54
|
+
args: options.args,
|
|
55
|
+
cwd: options.cwd,
|
|
56
|
+
env: options.env,
|
|
57
|
+
cols: options.cols,
|
|
58
|
+
rows: options.rows,
|
|
59
|
+
}));
|
|
60
|
+
return this.addEntry(id, session, command);
|
|
61
|
+
}
|
|
62
|
+
async sweepExited(options = {}) {
|
|
63
|
+
this.refreshExitedStates();
|
|
64
|
+
const sweptIds = [];
|
|
65
|
+
for (const entry of this.entries.values()) {
|
|
66
|
+
if (entry.state !== "exited")
|
|
67
|
+
continue;
|
|
68
|
+
await this.cleanupDetachedChildren(entry);
|
|
69
|
+
sweptIds.push(entry.id);
|
|
70
|
+
if (options.remove)
|
|
71
|
+
this.removeEntry(entry.id);
|
|
72
|
+
}
|
|
73
|
+
return sweptIds;
|
|
74
|
+
}
|
|
75
|
+
async stop(id) {
|
|
76
|
+
const entry = this.entries.get(id);
|
|
77
|
+
if (!entry)
|
|
78
|
+
return false;
|
|
79
|
+
if (entry.stopPromise)
|
|
80
|
+
return entry.stopPromise;
|
|
81
|
+
entry.stopPromise = this.stopEntry(entry);
|
|
82
|
+
return entry.stopPromise;
|
|
83
|
+
}
|
|
84
|
+
async stopAll() {
|
|
85
|
+
await Promise.all([...this.entries.keys()].map((id) => this.stop(id)));
|
|
86
|
+
}
|
|
87
|
+
async teardown() {
|
|
88
|
+
await this.stopAll();
|
|
89
|
+
for (const id of [...this.entries.keys()])
|
|
90
|
+
this.removeEntry(id);
|
|
91
|
+
}
|
|
92
|
+
async createSessionWithFactory(options) {
|
|
93
|
+
if (!this.createSession) {
|
|
94
|
+
throw new Error("Cannot create terminal session: SessionRegistry requires a createSession factory.");
|
|
95
|
+
}
|
|
96
|
+
return this.createSession(options);
|
|
97
|
+
}
|
|
98
|
+
addEntry(id, session, command) {
|
|
99
|
+
const timestamp = this.timestamp();
|
|
100
|
+
const exited = isTerminalSessionExited(session);
|
|
101
|
+
const entry = {
|
|
102
|
+
id,
|
|
103
|
+
command,
|
|
104
|
+
session,
|
|
105
|
+
createdAt: timestamp,
|
|
106
|
+
lastUsedAt: timestamp,
|
|
107
|
+
state: exited ? "exited" : "live",
|
|
108
|
+
exitedAt: exited ? timestamp : null,
|
|
109
|
+
unsubscribeExit: null,
|
|
110
|
+
stopPromise: null,
|
|
111
|
+
detachedCleanupPromise: null,
|
|
112
|
+
};
|
|
113
|
+
entry.unsubscribeExit =
|
|
114
|
+
session.onExit?.(() => {
|
|
115
|
+
void this.handleSessionExit(id);
|
|
116
|
+
}) ?? null;
|
|
117
|
+
this.entries.set(id, entry);
|
|
118
|
+
this.observeId(id);
|
|
119
|
+
return entry;
|
|
120
|
+
}
|
|
121
|
+
async handleSessionExit(id) {
|
|
122
|
+
const entry = this.entries.get(id);
|
|
123
|
+
if (!entry)
|
|
124
|
+
return;
|
|
125
|
+
this.markExited(entry);
|
|
126
|
+
await this.cleanupDetachedChildren(entry);
|
|
127
|
+
}
|
|
128
|
+
async enforceCapacityForCreate() {
|
|
129
|
+
this.refreshExitedStates();
|
|
130
|
+
await this.pruneExitedUntilAtMost(this.maxSessions - 1);
|
|
131
|
+
if (this.entries.size >= this.maxSessions)
|
|
132
|
+
throw new SessionRegistryCapacityError(this.maxSessions);
|
|
133
|
+
}
|
|
134
|
+
async pruneExitedUntilAtMost(maxSize) {
|
|
135
|
+
while (this.entries.size > maxSize) {
|
|
136
|
+
const entry = this.findLeastRecentlyUsedExited();
|
|
137
|
+
if (!entry)
|
|
138
|
+
return;
|
|
139
|
+
await this.cleanupDetachedChildren(entry);
|
|
140
|
+
this.removeEntry(entry.id);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
findLeastRecentlyUsedExited() {
|
|
144
|
+
let selected = null;
|
|
145
|
+
for (const entry of this.entries.values()) {
|
|
146
|
+
if (entry.state !== "exited")
|
|
147
|
+
continue;
|
|
148
|
+
if (!selected || entry.lastUsedAt < selected.lastUsedAt)
|
|
149
|
+
selected = entry;
|
|
150
|
+
else if (selected.lastUsedAt === entry.lastUsedAt && entry.createdAt < selected.createdAt)
|
|
151
|
+
selected = entry;
|
|
152
|
+
}
|
|
153
|
+
return selected;
|
|
154
|
+
}
|
|
155
|
+
async stopEntry(entry) {
|
|
156
|
+
this.refreshEntryState(entry);
|
|
157
|
+
await this.cleanupDetachedChildren(entry);
|
|
158
|
+
if (entry.state !== "exited") {
|
|
159
|
+
await stopTerminalSession(entry.session);
|
|
160
|
+
if (await waitForTerminalSessionExit(entry.session, this.stopExitGraceMs)) {
|
|
161
|
+
this.markExited(entry);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
entry.state = "stopping";
|
|
165
|
+
entry.exitedAt = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
async cleanupDetachedChildren(entry) {
|
|
171
|
+
if (entry.detachedCleanupPromise)
|
|
172
|
+
return entry.detachedCleanupPromise;
|
|
173
|
+
entry.detachedCleanupPromise = this.cleanupDetachedChildrenOnce(entry.session);
|
|
174
|
+
return entry.detachedCleanupPromise;
|
|
175
|
+
}
|
|
176
|
+
async cleanupDetachedChildrenOnce(session) {
|
|
177
|
+
await cleanupDetachedChildren(session, this.platform, this.killProcess);
|
|
178
|
+
}
|
|
179
|
+
refreshExitedStates() {
|
|
180
|
+
for (const entry of this.entries.values())
|
|
181
|
+
this.refreshEntryState(entry);
|
|
182
|
+
}
|
|
183
|
+
refreshEntryState(entry) {
|
|
184
|
+
if (entry.state === "exited")
|
|
185
|
+
return;
|
|
186
|
+
if (isTerminalSessionExited(entry.session))
|
|
187
|
+
this.markExited(entry);
|
|
188
|
+
}
|
|
189
|
+
markExited(entry) {
|
|
190
|
+
if (entry.state === "exited")
|
|
191
|
+
return;
|
|
192
|
+
entry.state = "exited";
|
|
193
|
+
entry.exitedAt = this.timestamp();
|
|
194
|
+
}
|
|
195
|
+
removeEntry(id) {
|
|
196
|
+
const entry = this.entries.get(id);
|
|
197
|
+
if (!entry)
|
|
198
|
+
return;
|
|
199
|
+
entry.unsubscribeExit?.();
|
|
200
|
+
this.entries.delete(id);
|
|
201
|
+
}
|
|
202
|
+
allocateId(command) {
|
|
203
|
+
const prefix = sessionIdPrefix(command);
|
|
204
|
+
let next = (this.idCounters.get(prefix) ?? 0) + 1;
|
|
205
|
+
while (this.entries.has(`${prefix}_${next}`))
|
|
206
|
+
next += 1;
|
|
207
|
+
this.idCounters.set(prefix, next);
|
|
208
|
+
return `${prefix}_${next}`;
|
|
209
|
+
}
|
|
210
|
+
observeId(id) {
|
|
211
|
+
const match = /^(.*)_(\d+)$/.exec(id);
|
|
212
|
+
if (!match)
|
|
213
|
+
return;
|
|
214
|
+
const prefix = match[1];
|
|
215
|
+
const value = Number.parseInt(match[2] ?? "", 10);
|
|
216
|
+
if (!prefix || !Number.isFinite(value))
|
|
217
|
+
return;
|
|
218
|
+
this.idCounters.set(prefix, Math.max(this.idCounters.get(prefix) ?? 0, value));
|
|
219
|
+
}
|
|
220
|
+
timestamp() {
|
|
221
|
+
this.sequence += 1;
|
|
222
|
+
return this.now() * 1000 + this.sequence;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function normalizeMaxSessions(value) {
|
|
226
|
+
if (value === undefined)
|
|
227
|
+
return DEFAULT_MAX_SESSIONS;
|
|
228
|
+
if (!Number.isFinite(value) || value < 1)
|
|
229
|
+
throw new Error("maxSessions must be a positive finite number.");
|
|
230
|
+
return Math.trunc(value);
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { cleanupDetachedChildren, defaultKillProcess, getRuntimePlatform } from \"./registry-detached.ts\";\nimport {\n\tisTerminalSessionExited,\n\tsessionIdPrefix,\n\tstopTerminalSession,\n\twaitForTerminalSessionExit,\n} from \"./registry-session.ts\";\nimport type {\n\tMaybePromise,\n\tSessionRegistryCreateContext,\n\tSessionRegistryCreateOptions,\n\tSessionRegistryEntry,\n\tSessionRegistryOptions,\n\tSessionRegistrySession,\n\tSessionRegistrySweepOptions,\n\tStoredSessionRegistryEntry,\n\tTerminalSessionSignal,\n} from \"./registry-types.ts\";\n\nexport { isTerminalSessionExited, sessionIdPrefix } from \"./registry-session.ts\";\nexport type {\n\tInitialSessionRegistryEntry,\n\tMaybePromise,\n\tSessionRegistryCreateContext,\n\tSessionRegistryCreateOptions,\n\tSessionRegistryEntry,\n\tSessionRegistryOptions,\n\tSessionRegistrySession,\n\tSessionRegistrySweepOptions,\n\tTerminalSessionSignal,\n\tTerminalSessionState,\n\tTrackedDetachedChild,\n} from \"./registry-types.ts\";\nexport { SessionRegistryCapacityError } from \"./registry-types.ts\";\n\nimport { SessionRegistryCapacityError } from \"./registry-types.ts\";\n\nconst DEFAULT_MAX_SESSIONS = 32;\nconst DEFAULT_COMMAND = \"bash\";\nconst DEFAULT_STOP_EXIT_GRACE_MS = 5000;\nconst MAX_STOP_EXIT_GRACE_MS = 2_147_483_647;\n\nexport class SessionRegistry<TSession extends SessionRegistrySession = SessionRegistrySession> {\n\tprivate readonly entries = new Map<string, StoredSessionRegistryEntry<TSession>>();\n\tprivate readonly idCounters = new Map<string, number>();\n\tprivate readonly createSession: ((options: SessionRegistryCreateContext) => MaybePromise<TSession>) | null;\n\tprivate readonly killProcess: (target: number, signal: TerminalSessionSignal) => void;\n\tprivate readonly now: () => number;\n\tprivate readonly platform: string;\n\tprivate sequence = 0;\n\treadonly maxSessions: number;\n\tprivate readonly stopExitGraceMs: number;\n\n\tconstructor(options: SessionRegistryOptions<TSession> = {}) {\n\t\tthis.maxSessions = normalizeMaxSessions(options.maxSessions);\n\t\tthis.stopExitGraceMs =\n\t\t\toptions.stopExitGraceMs !== undefined &&\n\t\t\tNumber.isFinite(options.stopExitGraceMs) &&\n\t\t\toptions.stopExitGraceMs > 0\n\t\t\t\t? Math.min(options.stopExitGraceMs, MAX_STOP_EXIT_GRACE_MS)\n\t\t\t\t: DEFAULT_STOP_EXIT_GRACE_MS;\n\t\tthis.createSession = options.createSession ?? null;\n\t\tthis.killProcess = options.killProcess ?? defaultKillProcess;\n\t\tthis.now = options.now ?? Date.now;\n\t\tthis.platform = options.platform ?? getRuntimePlatform();\n\n\t\tfor (const initial of options.initialSessions ?? []) {\n\t\t\tthis.addEntry(initial.id, initial.session, initial.command ?? initial.session.command ?? DEFAULT_COMMAND);\n\t\t}\n\t\tvoid this.sweepExited();\n\t}\n\n\tget size(): number {\n\t\treturn this.entries.size;\n\t}\n\n\tlist(): readonly SessionRegistryEntry<TSession>[] {\n\t\tthis.refreshExitedStates();\n\t\treturn [...this.entries.values()].sort((left, right) => left.createdAt - right.createdAt);\n\t}\n\n\tget(id: string): SessionRegistryEntry<TSession> | null {\n\t\tconst entry = this.entries.get(id);\n\t\tif (!entry) return null;\n\t\tentry.lastUsedAt = this.timestamp();\n\t\tthis.refreshEntryState(entry);\n\t\treturn entry;\n\t}\n\n\tasync create(options: SessionRegistryCreateOptions<TSession> = {}): Promise<SessionRegistryEntry<TSession>> {\n\t\tawait this.enforceCapacityForCreate();\n\t\tconst command = options.command ?? options.session?.command ?? DEFAULT_COMMAND;\n\t\tconst id = this.allocateId(command);\n\t\tconst session =\n\t\t\toptions.session ??\n\t\t\t(await this.createSessionWithFactory({\n\t\t\t\tid,\n\t\t\t\tcommand,\n\t\t\t\targs: options.args,\n\t\t\t\tcwd: options.cwd,\n\t\t\t\tenv: options.env,\n\t\t\t\tcols: options.cols,\n\t\t\t\trows: options.rows,\n\t\t\t}));\n\t\treturn this.addEntry(id, session, command);\n\t}\n\n\tasync sweepExited(options: SessionRegistrySweepOptions = {}): Promise<readonly string[]> {\n\t\tthis.refreshExitedStates();\n\t\tconst sweptIds: string[] = [];\n\t\tfor (const entry of this.entries.values()) {\n\t\t\tif (entry.state !== \"exited\") continue;\n\t\t\tawait this.cleanupDetachedChildren(entry);\n\t\t\tsweptIds.push(entry.id);\n\t\t\tif (options.remove) this.removeEntry(entry.id);\n\t\t}\n\t\treturn sweptIds;\n\t}\n\n\tasync stop(id: string): Promise<boolean> {\n\t\tconst entry = this.entries.get(id);\n\t\tif (!entry) return false;\n\t\tif (entry.stopPromise) return entry.stopPromise;\n\t\tentry.stopPromise = this.stopEntry(entry);\n\t\treturn entry.stopPromise;\n\t}\n\n\tasync stopAll(): Promise<void> {\n\t\tawait Promise.all([...this.entries.keys()].map((id) => this.stop(id)));\n\t}\n\n\tasync teardown(): Promise<void> {\n\t\tawait this.stopAll();\n\t\tfor (const id of [...this.entries.keys()]) this.removeEntry(id);\n\t}\n\n\tprivate async createSessionWithFactory(options: SessionRegistryCreateContext): Promise<TSession> {\n\t\tif (!this.createSession) {\n\t\t\tthrow new Error(\"Cannot create terminal session: SessionRegistry requires a createSession factory.\");\n\t\t}\n\t\treturn this.createSession(options);\n\t}\n\n\tprivate addEntry(id: string, session: TSession, command: string): SessionRegistryEntry<TSession> {\n\t\tconst timestamp = this.timestamp();\n\t\tconst exited = isTerminalSessionExited(session);\n\t\tconst entry: StoredSessionRegistryEntry<TSession> = {\n\t\t\tid,\n\t\t\tcommand,\n\t\t\tsession,\n\t\t\tcreatedAt: timestamp,\n\t\t\tlastUsedAt: timestamp,\n\t\t\tstate: exited ? \"exited\" : \"live\",\n\t\t\texitedAt: exited ? timestamp : null,\n\t\t\tunsubscribeExit: null,\n\t\t\tstopPromise: null,\n\t\t\tdetachedCleanupPromise: null,\n\t\t};\n\t\tentry.unsubscribeExit =\n\t\t\tsession.onExit?.(() => {\n\t\t\t\tvoid this.handleSessionExit(id);\n\t\t\t}) ?? null;\n\t\tthis.entries.set(id, entry);\n\t\tthis.observeId(id);\n\t\treturn entry;\n\t}\n\n\tprivate async handleSessionExit(id: string): Promise<void> {\n\t\tconst entry = this.entries.get(id);\n\t\tif (!entry) return;\n\t\tthis.markExited(entry);\n\t\tawait this.cleanupDetachedChildren(entry);\n\t}\n\n\tprivate async enforceCapacityForCreate(): Promise<void> {\n\t\tthis.refreshExitedStates();\n\t\tawait this.pruneExitedUntilAtMost(this.maxSessions - 1);\n\t\tif (this.entries.size >= this.maxSessions) throw new SessionRegistryCapacityError(this.maxSessions);\n\t}\n\n\tprivate async pruneExitedUntilAtMost(maxSize: number): Promise<void> {\n\t\twhile (this.entries.size > maxSize) {\n\t\t\tconst entry = this.findLeastRecentlyUsedExited();\n\t\t\tif (!entry) return;\n\t\t\tawait this.cleanupDetachedChildren(entry);\n\t\t\tthis.removeEntry(entry.id);\n\t\t}\n\t}\n\n\tprivate findLeastRecentlyUsedExited(): StoredSessionRegistryEntry<TSession> | null {\n\t\tlet selected: StoredSessionRegistryEntry<TSession> | null = null;\n\t\tfor (const entry of this.entries.values()) {\n\t\t\tif (entry.state !== \"exited\") continue;\n\t\t\tif (!selected || entry.lastUsedAt < selected.lastUsedAt) selected = entry;\n\t\t\telse if (selected.lastUsedAt === entry.lastUsedAt && entry.createdAt < selected.createdAt) selected = entry;\n\t\t}\n\t\treturn selected;\n\t}\n\n\tprivate async stopEntry(entry: StoredSessionRegistryEntry<TSession>): Promise<boolean> {\n\t\tthis.refreshEntryState(entry);\n\t\tawait this.cleanupDetachedChildren(entry);\n\t\tif (entry.state !== \"exited\") {\n\t\t\tawait stopTerminalSession(entry.session);\n\t\t\tif (await waitForTerminalSessionExit(entry.session, this.stopExitGraceMs)) {\n\t\t\t\tthis.markExited(entry);\n\t\t\t} else {\n\t\t\t\tentry.state = \"stopping\";\n\t\t\t\tentry.exitedAt = null;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\tprivate async cleanupDetachedChildren(entry: StoredSessionRegistryEntry<TSession>): Promise<void> {\n\t\tif (entry.detachedCleanupPromise) return entry.detachedCleanupPromise;\n\t\tentry.detachedCleanupPromise = this.cleanupDetachedChildrenOnce(entry.session);\n\t\treturn entry.detachedCleanupPromise;\n\t}\n\n\tprivate async cleanupDetachedChildrenOnce(session: TSession): Promise<void> {\n\t\tawait cleanupDetachedChildren(session, this.platform, this.killProcess);\n\t}\n\n\tprivate refreshExitedStates(): void {\n\t\tfor (const entry of this.entries.values()) this.refreshEntryState(entry);\n\t}\n\n\tprivate refreshEntryState(entry: StoredSessionRegistryEntry<TSession>): void {\n\t\tif (entry.state === \"exited\") return;\n\t\tif (isTerminalSessionExited(entry.session)) this.markExited(entry);\n\t}\n\n\tprivate markExited(entry: StoredSessionRegistryEntry<TSession>): void {\n\t\tif (entry.state === \"exited\") return;\n\t\tentry.state = \"exited\";\n\t\tentry.exitedAt = this.timestamp();\n\t}\n\n\tprivate removeEntry(id: string): void {\n\t\tconst entry = this.entries.get(id);\n\t\tif (!entry) return;\n\t\tentry.unsubscribeExit?.();\n\t\tthis.entries.delete(id);\n\t}\n\n\tprivate allocateId(command: string): string {\n\t\tconst prefix = sessionIdPrefix(command);\n\t\tlet next = (this.idCounters.get(prefix) ?? 0) + 1;\n\t\twhile (this.entries.has(`${prefix}_${next}`)) next += 1;\n\t\tthis.idCounters.set(prefix, next);\n\t\treturn `${prefix}_${next}`;\n\t}\n\n\tprivate observeId(id: string): void {\n\t\tconst match = /^(.*)_(\\d+)$/.exec(id);\n\t\tif (!match) return;\n\t\tconst prefix = match[1];\n\t\tconst value = Number.parseInt(match[2] ?? \"\", 10);\n\t\tif (!prefix || !Number.isFinite(value)) return;\n\t\tthis.idCounters.set(prefix, Math.max(this.idCounters.get(prefix) ?? 0, value));\n\t}\n\n\tprivate timestamp(): number {\n\t\tthis.sequence += 1;\n\t\treturn this.now() * 1000 + this.sequence;\n\t}\n}\n\nfunction normalizeMaxSessions(value: number | undefined): number {\n\tif (value === undefined) return DEFAULT_MAX_SESSIONS;\n\tif (!Number.isFinite(value) || value < 1) throw new Error(\"maxSessions must be a positive finite number.\");\n\treturn Math.trunc(value);\n}\n"]}
|
package/dist/screen.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface TerminalScreenOptions {
|
|
2
|
+
readonly cols?: number;
|
|
3
|
+
readonly rows?: number;
|
|
4
|
+
readonly scrollback?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface TerminalScreenSnapshot {
|
|
7
|
+
readonly cols: number;
|
|
8
|
+
readonly rows: number;
|
|
9
|
+
readonly visibleGrid: readonly string[];
|
|
10
|
+
readonly scrollback: readonly string[];
|
|
11
|
+
readonly cursor: {
|
|
12
|
+
readonly x: number;
|
|
13
|
+
readonly y: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare class TerminalScreen {
|
|
17
|
+
private terminal;
|
|
18
|
+
private readonly history;
|
|
19
|
+
private historyLength;
|
|
20
|
+
private readonly maxReplayHistoryLength;
|
|
21
|
+
private readonly scrollback;
|
|
22
|
+
constructor(options?: TerminalScreenOptions);
|
|
23
|
+
feed(data: string | Uint8Array): Promise<void>;
|
|
24
|
+
resize(cols: number, rows: number): Promise<void>;
|
|
25
|
+
flush(): Promise<void>;
|
|
26
|
+
snapshot(): TerminalScreenSnapshot;
|
|
27
|
+
dispose(): void;
|
|
28
|
+
private createTerminal;
|
|
29
|
+
private write;
|
|
30
|
+
private appendHistory;
|
|
31
|
+
private trimHistory;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=screen.d.ts.map
|