@rivet-dev/agentos-node-pty 0.2.8-rc.3

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 ADDED
@@ -0,0 +1,5 @@
1
+ # AgentOS node-pty
2
+
3
+ `@rivet-dev/agentos-node-pty` implements the `@lydell/node-pty` JavaScript API
4
+ on AgentOS kernel PTYs. It is intended for packages running inside an AgentOS
5
+ VM and does not load a native Node addon.
@@ -0,0 +1,36 @@
1
+ export interface IDisposable {
2
+ dispose(): void;
3
+ }
4
+ export interface IPtyForkOptions {
5
+ name?: string;
6
+ cols?: number;
7
+ rows?: number;
8
+ cwd?: string;
9
+ env?: Record<string, string | undefined>;
10
+ encoding?: BufferEncoding | null;
11
+ handleFlowControl?: boolean;
12
+ flowControlPause?: string;
13
+ flowControlResume?: string;
14
+ uid?: number;
15
+ gid?: number;
16
+ }
17
+ export type IWindowsPtyForkOptions = IPtyForkOptions;
18
+ export interface IPty {
19
+ readonly pid: number;
20
+ readonly cols: number;
21
+ readonly rows: number;
22
+ readonly process: string;
23
+ handleFlowControl: boolean;
24
+ readonly onData: (listener: (data: string) => unknown) => IDisposable;
25
+ readonly onExit: (listener: (event: {
26
+ exitCode: number;
27
+ signal?: number;
28
+ }) => unknown) => IDisposable;
29
+ resize(columns: number, rows: number): void;
30
+ clear(): void;
31
+ write(data: string | Buffer): void;
32
+ kill(signal?: string): void;
33
+ pause(): void;
34
+ resume(): void;
35
+ }
36
+ export declare function spawn(file: string, args: string[] | string, options?: IPtyForkOptions | IWindowsPtyForkOptions): IPty;
package/dist/index.js ADDED
@@ -0,0 +1,159 @@
1
+ import { spawn as spawnChild } from "node:child_process";
2
+ import { constants as osConstants } from "node:os";
3
+ import { StringDecoder } from "node:string_decoder";
4
+ const DEFAULT_COLS = 80;
5
+ const DEFAULT_ROWS = 24;
6
+ const DEFAULT_MAX_LISTENERS = 100;
7
+ function positiveInteger(value, fallback, label) {
8
+ const result = value ?? fallback;
9
+ if (!Number.isInteger(result) || result <= 0) {
10
+ throw Object.assign(new RangeError(`${label} must be a positive integer`), {
11
+ code: "EINVAL",
12
+ });
13
+ }
14
+ return result;
15
+ }
16
+ function maxListeners() {
17
+ const configured = Number(process.env.AGENTOS_NODE_PTY_MAX_LISTENERS);
18
+ return Number.isInteger(configured) && configured > 0
19
+ ? configured
20
+ : DEFAULT_MAX_LISTENERS;
21
+ }
22
+ function addBoundedListener(listeners, listener) {
23
+ if (listeners.size >= maxListeners()) {
24
+ throw Object.assign(new Error(`node-pty listener limit exceeded (${maxListeners()}); raise AGENTOS_NODE_PTY_MAX_LISTENERS to allow more`), { code: "ERR_AGENTOS_NODE_PTY_LISTENER_LIMIT" });
25
+ }
26
+ listeners.add(listener);
27
+ return { dispose: () => listeners.delete(listener) };
28
+ }
29
+ function signalNumber(signal) {
30
+ return signal ? osConstants.signals[signal] : undefined;
31
+ }
32
+ export function spawn(file, args, options = {}) {
33
+ if (typeof args === "string") {
34
+ throw Object.assign(new Error("pre-escaped Windows command lines are not supported by AgentOS node-pty"), { code: "ENOSYS" });
35
+ }
36
+ if (options.uid !== undefined || options.gid !== undefined) {
37
+ throw Object.assign(new Error("node-pty uid/gid overrides are not supported by AgentOS"), {
38
+ code: "ENOSYS",
39
+ });
40
+ }
41
+ let cols = positiveInteger(options.cols, DEFAULT_COLS, "cols");
42
+ let rows = positiveInteger(options.rows, DEFAULT_ROWS, "rows");
43
+ const encoding = options.encoding === undefined ? "utf8" : options.encoding;
44
+ if (encoding === null) {
45
+ throw Object.assign(new Error("binary node-pty output is not supported"), {
46
+ code: "ENOSYS",
47
+ });
48
+ }
49
+ const decoder = new StringDecoder(encoding);
50
+ const dataListeners = new Set();
51
+ const exitListeners = new Set();
52
+ let exited = false;
53
+ let handleFlowControl = options.handleFlowControl ?? false;
54
+ const flowControlPause = options.flowControlPause ?? "\x13";
55
+ const flowControlResume = options.flowControlResume ?? "\x11";
56
+ const child = spawnChild(file, args, {
57
+ cwd: options.cwd,
58
+ env: {
59
+ ...process.env,
60
+ ...options.env,
61
+ TERM: options.name ?? "xterm-256color",
62
+ COLUMNS: String(cols),
63
+ LINES: String(rows),
64
+ },
65
+ stdio: ["pipe", "pipe", "pipe"],
66
+ agentosPty: { cols, rows },
67
+ });
68
+ const emitData = (chunk) => {
69
+ const text = typeof chunk === "string" ? chunk : decoder.write(chunk);
70
+ if (!text)
71
+ return;
72
+ for (const listener of [...dataListeners])
73
+ listener(text);
74
+ };
75
+ const emitExit = (exitCode, signal) => {
76
+ if (exited)
77
+ return;
78
+ exited = true;
79
+ const trailing = decoder.end();
80
+ if (trailing) {
81
+ for (const listener of [...dataListeners])
82
+ listener(trailing);
83
+ }
84
+ for (const listener of [...exitListeners])
85
+ listener({ exitCode, signal });
86
+ dataListeners.clear();
87
+ exitListeners.clear();
88
+ };
89
+ child.stdout?.on("data", emitData);
90
+ child.stderr?.on("data", emitData);
91
+ child.once("error", (error) => {
92
+ console.error("AgentOS node-pty child process failed", error);
93
+ emitExit(1);
94
+ });
95
+ child.once("exit", (code, signal) => emitExit(code ?? 0, signalNumber(signal)));
96
+ return {
97
+ get pid() {
98
+ return child.pid ?? -1;
99
+ },
100
+ get cols() {
101
+ return cols;
102
+ },
103
+ get rows() {
104
+ return rows;
105
+ },
106
+ get process() {
107
+ return file;
108
+ },
109
+ get handleFlowControl() {
110
+ return handleFlowControl;
111
+ },
112
+ set handleFlowControl(value) {
113
+ handleFlowControl = value;
114
+ },
115
+ onData(listener) {
116
+ return addBoundedListener(dataListeners, listener);
117
+ },
118
+ onExit(listener) {
119
+ return addBoundedListener(exitListeners, listener);
120
+ },
121
+ resize(columns, nextRows) {
122
+ const nextCols = positiveInteger(columns, DEFAULT_COLS, "columns");
123
+ const normalizedRows = positiveInteger(nextRows, DEFAULT_ROWS, "rows");
124
+ if (typeof child.resizePty !== "function") {
125
+ throw Object.assign(new Error("AgentOS child process PTY resize bridge is unavailable"), {
126
+ code: "ENOTTY",
127
+ });
128
+ }
129
+ child.resizePty(nextCols, normalizedRows);
130
+ cols = nextCols;
131
+ rows = normalizedRows;
132
+ },
133
+ clear() { },
134
+ write(data) {
135
+ if (handleFlowControl && data === flowControlPause) {
136
+ child.stdout?.pause();
137
+ child.stderr?.pause();
138
+ return;
139
+ }
140
+ if (handleFlowControl && data === flowControlResume) {
141
+ child.stdout?.resume();
142
+ child.stderr?.resume();
143
+ return;
144
+ }
145
+ child.stdin?.write(data);
146
+ },
147
+ kill(signal = "SIGHUP") {
148
+ child.kill(signal);
149
+ },
150
+ pause() {
151
+ child.stdout?.pause();
152
+ child.stderr?.pause();
153
+ },
154
+ resume() {
155
+ child.stdout?.resume();
156
+ child.stderr?.resume();
157
+ },
158
+ };
159
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@rivet-dev/agentos-node-pty",
3
+ "version": "0.2.8-rc.3",
4
+ "description": "AgentOS-backed node-pty compatibility layer",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "default": "./dist/index.js"
18
+ }
19
+ },
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "check-types": "tsc --noEmit",
23
+ "test": "pnpm build && node --test tests/*.test.mjs"
24
+ },
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^22.10.2",
30
+ "typescript": "^5.7.2"
31
+ }
32
+ }