@moxxy/plugin-terminal 0.27.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moxxy (moxxy.ai)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ export { buildTerminalSurface, buildTerminalTool, closeAllTerminals, } from './terminal.js';
2
+ export { createTerminalProcess, type TerminalProcess } from './pty.js';
3
+ export declare function buildTerminalPlugin(): import("@moxxy/sdk").Plugin;
4
+ export declare const terminalPlugin: import("@moxxy/sdk").Plugin;
5
+ export default terminalPlugin;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAEvE,wBAAgB,mBAAmB,gCAalC;AAED,eAAO,MAAM,cAAc,6BAAwB,CAAC;AAEpD,eAAe,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import { definePlugin } from '@moxxy/sdk';
2
+ import { buildTerminalSurface, buildTerminalTool, closeAllTerminals } from './terminal.js';
3
+ export { buildTerminalSurface, buildTerminalTool, closeAllTerminals, } from './terminal.js';
4
+ export { createTerminalProcess } from './pty.js';
5
+ export function buildTerminalPlugin() {
6
+ return definePlugin({
7
+ name: '@moxxy/plugin-terminal',
8
+ version: '0.0.0',
9
+ surfaces: [buildTerminalSurface()],
10
+ tools: [buildTerminalTool()],
11
+ hooks: {
12
+ onShutdown: () => {
13
+ // Kill the shared shell(s) with the session so no orphan PTY lingers.
14
+ closeAllTerminals();
15
+ },
16
+ },
17
+ });
18
+ }
19
+ export const terminalPlugin = buildTerminalPlugin();
20
+ export default terminalPlugin;
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE3F,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAwB,MAAM,UAAU,CAAC;AAEvE,MAAM,UAAU,mBAAmB;IACjC,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,wBAAwB;QAC9B,OAAO,EAAE,OAAO;QAChB,QAAQ,EAAE,CAAC,oBAAoB,EAAE,CAAC;QAClC,KAAK,EAAE,CAAC,iBAAiB,EAAE,CAAC;QAC5B,KAAK,EAAE;YACL,UAAU,EAAE,GAAG,EAAE;gBACf,sEAAsE;gBACtE,iBAAiB,EAAE,CAAC;YACtB,CAAC;SACF;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,EAAE,CAAC;AAEpD,eAAe,cAAc,CAAC"}
package/dist/pty.d.ts ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The shared terminal process behind the `terminal` surface + tool.
3
+ *
4
+ * Two backends, picked at open time:
5
+ * 1. **node-pty** (preferred) — a real PTY, so interactive programs (vim, top,
6
+ * a REPL) and prompts render correctly. Lazy-loaded; absent in a default
7
+ * install (it is an OPTIONAL peer dep — native, so CI never has to build
8
+ * it).
9
+ * 2. **piped child shell** (fallback, dependency-free) — spawns the user's
10
+ * shell with piped stdio. Commands run and output streams live, which is
11
+ * enough for "run a command for the user"; full TTY apps are degraded.
12
+ *
13
+ * One process is shared per cwd (a module singleton map), so the agent's
14
+ * `terminal` tool and the desktop pane drive the SAME session — the user sees
15
+ * the agent's commands appear live and can take over typing.
16
+ */
17
+ import { type ChildProcessWithoutNullStreams } from 'node:child_process';
18
+ /** Minimal slice of node-pty we use — declared locally so typecheck never needs
19
+ * `@types/node-pty` (the dep is optional). */
20
+ interface NodePtyModule {
21
+ spawn(file: string, args: string[] | string, opts: {
22
+ name?: string;
23
+ cols?: number;
24
+ rows?: number;
25
+ cwd?: string;
26
+ env?: NodeJS.ProcessEnv;
27
+ }): NodePtyProcess;
28
+ }
29
+ interface NodePtyProcess {
30
+ onData(cb: (data: string) => void): void;
31
+ onExit(cb: (e: {
32
+ exitCode: number;
33
+ }) => void): void;
34
+ write(data: string): void;
35
+ resize(cols: number, rows: number): void;
36
+ kill(signal?: string): void;
37
+ }
38
+ /**
39
+ * Resolve a usable NodePtyModule from whatever `import('node-pty')` yields, or
40
+ * null. Handles ESM/CJS interop (`.spawn` on the namespace OR on `.default`) and
41
+ * — critically — REQUIRES a callable `spawn` on whichever we pick. A malformed /
42
+ * partially-shimmed module (a `default` that lacks `spawn`, a non-function
43
+ * `spawn`) must degrade to the piped fallback HERE rather than be returned and
44
+ * blow up later as `pty.spawn is not a function` inside `createTerminalProcess`.
45
+ * Exported for tests — the optional dep is never present in CI, so the
46
+ * shape-resolution logic is unit-tested against hand-built module objects.
47
+ */
48
+ export declare function resolveNodePtyModule(m: unknown): NodePtyModule | null;
49
+ /** Add the executable bit (u+x,g+x,o+x) to a file if it lacks it. Returns true
50
+ * when the file now has it (was already +x, or we just set it). Exported for
51
+ * tests — the chmod logic is the load-bearing fix, so it's unit-tested directly
52
+ * without needing node-pty present. */
53
+ export declare function makeExecutable(filePath: string): boolean;
54
+ export type TerminalBackend = 'pty' | 'pipe';
55
+ export interface TerminalProcess {
56
+ readonly backend: TerminalBackend;
57
+ /** When `backend === 'pipe'`, the reason the real PTY couldn't start (so the
58
+ * surface can show an honest "degraded" status instead of a silently-dead
59
+ * box). Null when a real PTY is in use. */
60
+ readonly ptyError: string | null;
61
+ /** Subscribe to output (utf8). Returns an unsubscribe fn. */
62
+ onData(cb: (data: string) => void): () => void;
63
+ /** Subscribe to process exit. */
64
+ onExit(cb: (code: number) => void): () => void;
65
+ /** Recent output for a late-joining viewer. */
66
+ scrollback(): string;
67
+ write(data: string): void;
68
+ resize(cols: number, rows: number): void;
69
+ kill(): void;
70
+ readonly alive: boolean;
71
+ }
72
+ /** Exported for tests: lets a suite drive `emitData`/`scrollback` directly
73
+ * without spawning a real shell. Not part of the plugin's public surface. */
74
+ export declare class TerminalProcessImpl implements TerminalProcess {
75
+ readonly backend: TerminalBackend;
76
+ private readonly pty;
77
+ private readonly child;
78
+ readonly ptyError: string | null;
79
+ private readonly dataListeners;
80
+ private readonly exitListeners;
81
+ private buffer;
82
+ private warnedListenerLeak;
83
+ alive: boolean;
84
+ constructor(backend: TerminalBackend, pty: NodePtyProcess | null, child: ChildProcessWithoutNullStreams | null, ptyError?: string | null);
85
+ /** Warn once when a listener Set grows past the leak threshold (a viewer whose
86
+ * close() never ran keeps its subscription for the shell's whole life). */
87
+ private checkListenerLeak;
88
+ private emitData;
89
+ private emitExit;
90
+ onData(cb: (d: string) => void): () => void;
91
+ onExit(cb: (c: number) => void): () => void;
92
+ scrollback(): string;
93
+ write(data: string): void;
94
+ resize(cols: number, rows: number): void;
95
+ kill(): void;
96
+ /**
97
+ * Terminate the piped shell AND its descendants. The child is spawned
98
+ * `detached` (its own process group), so a negative-pid signal reaches the
99
+ * whole tree — otherwise a running grandchild (a `sleep`, a dev server, a
100
+ * `tail -f`, a build) is reparented to init and leaks past session teardown,
101
+ * holding ports/files. Escalate SIGTERM → SIGKILL after a grace period.
102
+ */
103
+ private killChildTree;
104
+ }
105
+ /** Spawn a fresh shared terminal in `cwd`, preferring a real PTY. */
106
+ export declare function createTerminalProcess(cwd: string): Promise<TerminalProcess>;
107
+ export {};
108
+ //# sourceMappingURL=pty.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pty.d.ts","sourceRoot":"","sources":["../src/pty.ts"],"names":[],"mappings":"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"}
package/dist/pty.js ADDED
@@ -0,0 +1,415 @@
1
+ /**
2
+ * The shared terminal process behind the `terminal` surface + tool.
3
+ *
4
+ * Two backends, picked at open time:
5
+ * 1. **node-pty** (preferred) — a real PTY, so interactive programs (vim, top,
6
+ * a REPL) and prompts render correctly. Lazy-loaded; absent in a default
7
+ * install (it is an OPTIONAL peer dep — native, so CI never has to build
8
+ * it).
9
+ * 2. **piped child shell** (fallback, dependency-free) — spawns the user's
10
+ * shell with piped stdio. Commands run and output streams live, which is
11
+ * enough for "run a command for the user"; full TTY apps are degraded.
12
+ *
13
+ * One process is shared per cwd (a module singleton map), so the agent's
14
+ * `terminal` tool and the desktop pane drive the SAME session — the user sees
15
+ * the agent's commands appear live and can take over typing.
16
+ */
17
+ import { spawn } from 'node:child_process';
18
+ import { chmodSync, existsSync, statSync } from 'node:fs';
19
+ import { createRequire } from 'node:module';
20
+ import * as nodePath from 'node:path';
21
+ /**
22
+ * Upper bound on output/exit listeners on a single shared process. A surface
23
+ * whose `close()` never runs (the viewer disconnects abnormally, the desktop
24
+ * crashes mid-stream) leaks its subscription for the life of the shared shell.
25
+ * Past this many live listeners we warn once — a runaway count is a leak, not a
26
+ * legitimate fan-out (only a handful of viewers + the per-command reader are
27
+ * ever expected). The set is not hard-capped (dropping a real viewer's stream
28
+ * is worse than the warning), but the diagnostic makes the leak visible.
29
+ */
30
+ const LISTENER_WARN_THRESHOLD = 64;
31
+ /**
32
+ * Grace period before escalating a kill() from SIGTERM to SIGKILL. An
33
+ * interactive shell that ignores/handles SIGTERM (or is wedged) would otherwise
34
+ * never die; after this we force it.
35
+ */
36
+ const KILL_ESCALATION_MS = 2_000;
37
+ /**
38
+ * Resolve a usable NodePtyModule from whatever `import('node-pty')` yields, or
39
+ * null. Handles ESM/CJS interop (`.spawn` on the namespace OR on `.default`) and
40
+ * — critically — REQUIRES a callable `spawn` on whichever we pick. A malformed /
41
+ * partially-shimmed module (a `default` that lacks `spawn`, a non-function
42
+ * `spawn`) must degrade to the piped fallback HERE rather than be returned and
43
+ * blow up later as `pty.spawn is not a function` inside `createTerminalProcess`.
44
+ * Exported for tests — the optional dep is never present in CI, so the
45
+ * shape-resolution logic is unit-tested against hand-built module objects.
46
+ */
47
+ export function resolveNodePtyModule(m) {
48
+ const hasSpawn = (v) => typeof v === 'object' && v !== null && typeof v.spawn === 'function';
49
+ if (hasSpawn(m))
50
+ return m;
51
+ const def = m?.default;
52
+ if (hasSpawn(def))
53
+ return def;
54
+ return null;
55
+ }
56
+ let nodePtyPromise;
57
+ /** Lazy-load node-pty; resolves to null when it isn't installed/usable. */
58
+ function loadNodePty() {
59
+ if (!nodePtyPromise) {
60
+ nodePtyPromise = import('node-pty')
61
+ .then((m) => resolveNodePtyModule(m))
62
+ .catch(() => null);
63
+ }
64
+ return nodePtyPromise;
65
+ }
66
+ /** Add the executable bit (u+x,g+x,o+x) to a file if it lacks it. Returns true
67
+ * when the file now has it (was already +x, or we just set it). Exported for
68
+ * tests — the chmod logic is the load-bearing fix, so it's unit-tested directly
69
+ * without needing node-pty present. */
70
+ export function makeExecutable(filePath) {
71
+ try {
72
+ const st = statSync(filePath);
73
+ if (st.mode & 0o111)
74
+ return true; // already executable
75
+ chmodSync(filePath, st.mode | 0o111);
76
+ return true;
77
+ }
78
+ catch {
79
+ return false; // not present / not permitted — caller treats as best-effort
80
+ }
81
+ }
82
+ /**
83
+ * node-pty ships a prebuilt `spawn-helper` binary on macOS that it exec()s to
84
+ * launch the shell inside the PTY. Several install/repack paths (notably
85
+ * `npm install` into the desktop's writable CLI prefix, and pnpm's content store)
86
+ * drop the executable bit on it — node-pty then loads fine but `pty.spawn`
87
+ * throws `posix_spawnp failed`, which used to be swallowed into the (effectively
88
+ * dead) piped fallback. So before we spawn, ensure the helper is executable.
89
+ * Best-effort: never throws; the spawn itself is the real test.
90
+ */
91
+ function ensureSpawnHelperExecutable() {
92
+ if (process.platform === 'win32')
93
+ return; // Windows uses conpty/winpty, no spawn-helper
94
+ try {
95
+ const root = nodePtyPackageRoot();
96
+ if (!root)
97
+ return;
98
+ const candidates = [
99
+ // prebuildify layout (the published npm package)
100
+ nodePath.join(root, 'prebuilds', `${process.platform}-${process.arch}`, 'spawn-helper'),
101
+ // build-from-source layout (no prebuild for this platform)
102
+ nodePath.join(root, 'build', 'Release', 'spawn-helper'),
103
+ ];
104
+ for (const c of candidates)
105
+ makeExecutable(c);
106
+ }
107
+ catch {
108
+ /* best-effort */
109
+ }
110
+ }
111
+ /** Resolve node-pty's package directory (the one containing `prebuilds/`),
112
+ * resolving from THIS module so it works whether node-pty sits in a hoisted
113
+ * root, a nested, or a pnpm store layout. */
114
+ function nodePtyPackageRoot() {
115
+ try {
116
+ const require = createRequire(import.meta.url);
117
+ try {
118
+ return nodePath.dirname(require.resolve('node-pty/package.json'));
119
+ }
120
+ catch {
121
+ // `exports` may hide package.json; resolve the entry and walk up to the
122
+ // first ancestor that has a `prebuilds/` (or `build/`) dir.
123
+ let dir = nodePath.dirname(require.resolve('node-pty'));
124
+ for (let i = 0; i < 6; i += 1) {
125
+ if (existsSync(nodePath.join(dir, 'prebuilds')) || existsSync(nodePath.join(dir, 'build'))) {
126
+ return dir;
127
+ }
128
+ const parent = nodePath.dirname(dir);
129
+ if (parent === dir)
130
+ break;
131
+ dir = parent;
132
+ }
133
+ return null;
134
+ }
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ }
140
+ /** Pick the user's interactive shell per platform. */
141
+ function defaultShell() {
142
+ if (process.platform === 'win32')
143
+ return process.env['COMSPEC'] ?? 'powershell.exe';
144
+ return process.env['SHELL'] ?? '/bin/bash';
145
+ }
146
+ /** Cap retained scrollback so a chatty process can't grow the buffer forever. */
147
+ const MAX_SCROLLBACK = 200_000;
148
+ /**
149
+ * Hysteresis margin: let the live buffer grow this far past the cap before
150
+ * trimming back down to it, so we amortize the (expensive) slice over many
151
+ * chunks instead of copying ~MAX_SCROLLBACK bytes on every chunk once
152
+ * saturated. `scrollback()` masks the slack by always returning the last
153
+ * MAX_SCROLLBACK chars.
154
+ */
155
+ const SCROLLBACK_SLACK = 100_000;
156
+ /** Upper bound on a PTY dimension. A viewer/relay could request an absurd
157
+ * cols/rows (off the wire, unvalidated upstream); node-pty/conpty allocates
158
+ * per-cell and can throw or wedge on extreme values, so cap to a generous but
159
+ * finite ceiling. Far larger than any real terminal. */
160
+ const MAX_DIMENSION = 10_000;
161
+ /** Floor to an integer and clamp into [1, MAX_DIMENSION]; non-finite → 1. */
162
+ function clampDimension(n) {
163
+ if (!Number.isFinite(n))
164
+ return 1;
165
+ return Math.min(MAX_DIMENSION, Math.max(1, Math.floor(n)));
166
+ }
167
+ /** Exported for tests: lets a suite drive `emitData`/`scrollback` directly
168
+ * without spawning a real shell. Not part of the plugin's public surface. */
169
+ export class TerminalProcessImpl {
170
+ backend;
171
+ pty;
172
+ child;
173
+ ptyError;
174
+ dataListeners = new Set();
175
+ exitListeners = new Set();
176
+ buffer = '';
177
+ warnedListenerLeak = false;
178
+ alive = true;
179
+ constructor(backend, pty, child, ptyError = null) {
180
+ this.backend = backend;
181
+ this.pty = pty;
182
+ this.child = child;
183
+ this.ptyError = ptyError;
184
+ if (pty) {
185
+ pty.onData((d) => this.emitData(d));
186
+ pty.onExit((e) => this.emitExit(e.exitCode));
187
+ }
188
+ else if (child) {
189
+ // The shared child is long-lived and may be (re)subscribed by several
190
+ // viewers over its lifetime; make the intent explicit so Node never emits
191
+ // a false-positive "possible EventEmitter memory leak detected" warning on
192
+ // its streams. Our own fan-out Sets are the real bound (see addListener).
193
+ child.stdout.setMaxListeners(0);
194
+ child.stderr.setMaxListeners(0);
195
+ child.stdin.setMaxListeners(0);
196
+ child.setMaxListeners(0);
197
+ child.stdout.on('data', (b) => this.emitData(b.toString('utf8')));
198
+ child.stderr.on('data', (b) => this.emitData(b.toString('utf8')));
199
+ child.on('exit', (code) => this.emitExit(code ?? 0));
200
+ child.on('error', () => this.emitExit(1));
201
+ // The child can exit (closing stdin) before the async 'exit' event flips
202
+ // `alive` — a write in that window can emit a broken-pipe 'error' on
203
+ // stdin. Swallow it here so it never goes unhandled and crashes the host.
204
+ child.stdin.on('error', () => {
205
+ /* broken pipe after shell exit — ignored */
206
+ });
207
+ }
208
+ }
209
+ /** Warn once when a listener Set grows past the leak threshold (a viewer whose
210
+ * close() never ran keeps its subscription for the shell's whole life). */
211
+ checkListenerLeak() {
212
+ if (this.warnedListenerLeak)
213
+ return;
214
+ if (this.dataListeners.size + this.exitListeners.size > LISTENER_WARN_THRESHOLD) {
215
+ this.warnedListenerLeak = true;
216
+ console.warn(`[plugin-terminal] shared terminal has ${this.dataListeners.size} data + ` +
217
+ `${this.exitListeners.size} exit listeners — likely a viewer that never closed.`);
218
+ }
219
+ }
220
+ emitData(d) {
221
+ // Append, and only trim when we exceed the cap by a hysteresis margin —
222
+ // trimming all the way back to the cap. The previous code sliced the full
223
+ // 200KB buffer on EVERY chunk once saturated (O(total_output * cap) churn);
224
+ // amortizing the trim makes appends ~O(1). `scrollback()` always returns the
225
+ // last MAX_SCROLLBACK chars, so the observable tail is unchanged.
226
+ this.buffer += d;
227
+ if (this.buffer.length > MAX_SCROLLBACK + SCROLLBACK_SLACK) {
228
+ this.buffer = this.buffer.slice(-MAX_SCROLLBACK);
229
+ }
230
+ for (const cb of this.dataListeners) {
231
+ try {
232
+ cb(d);
233
+ }
234
+ catch {
235
+ /* a bad viewer must not break the stream */
236
+ }
237
+ }
238
+ }
239
+ emitExit(code) {
240
+ if (!this.alive)
241
+ return;
242
+ this.alive = false;
243
+ for (const cb of this.exitListeners) {
244
+ try {
245
+ cb(code);
246
+ }
247
+ catch {
248
+ /* ignore */
249
+ }
250
+ }
251
+ }
252
+ onData(cb) {
253
+ this.dataListeners.add(cb);
254
+ this.checkListenerLeak();
255
+ return () => this.dataListeners.delete(cb);
256
+ }
257
+ onExit(cb) {
258
+ this.exitListeners.add(cb);
259
+ this.checkListenerLeak();
260
+ return () => this.exitListeners.delete(cb);
261
+ }
262
+ scrollback() {
263
+ // The buffer may hold up to MAX_SCROLLBACK + SCROLLBACK_SLACK chars between
264
+ // trims (see emitData); always hand back exactly the last MAX_SCROLLBACK so
265
+ // a late-joining viewer sees the same tail as before the hysteresis change.
266
+ return this.buffer.length > MAX_SCROLLBACK
267
+ ? this.buffer.slice(-MAX_SCROLLBACK)
268
+ : this.buffer;
269
+ }
270
+ write(data) {
271
+ if (!this.alive)
272
+ return;
273
+ try {
274
+ if (this.pty)
275
+ this.pty.write(data);
276
+ else
277
+ this.child?.stdin.write(data);
278
+ }
279
+ catch {
280
+ // The child may have exited between `alive` flipping and now, leaving a
281
+ // closed stdin pipe — a synchronous EPIPE here must not crash the host.
282
+ }
283
+ }
284
+ resize(cols, rows) {
285
+ if (this.pty && this.alive) {
286
+ // Coerce to a sane integer in [1, MAX_DIMENSION]. Callers SHOULD pre-validate
287
+ // (terminal.ts isValidDimension), but a float/huge value reaching node-pty
288
+ // can throw or wedge conpty, so clamp defensively here too.
289
+ try {
290
+ this.pty.resize(clampDimension(cols), clampDimension(rows));
291
+ }
292
+ catch {
293
+ /* resize on a dead pty — ignore */
294
+ }
295
+ }
296
+ // The piped fallback has no TTY to resize.
297
+ }
298
+ kill() {
299
+ if (!this.alive)
300
+ return;
301
+ try {
302
+ if (this.pty) {
303
+ // node-pty kills the conpty/pty session; on POSIX it signals the shell.
304
+ // Send SIGTERM, then escalate to SIGKILL after a grace period if the
305
+ // shell ignores/handles it (a wedged shell would otherwise never die).
306
+ this.pty.kill();
307
+ const pty = this.pty;
308
+ setTimeout(() => {
309
+ try {
310
+ pty.kill('SIGKILL');
311
+ }
312
+ catch {
313
+ /* already gone */
314
+ }
315
+ }, KILL_ESCALATION_MS).unref?.();
316
+ }
317
+ if (this.child)
318
+ this.killChildTree(this.child);
319
+ }
320
+ catch {
321
+ /* already gone */
322
+ }
323
+ this.emitExit(0);
324
+ }
325
+ /**
326
+ * Terminate the piped shell AND its descendants. The child is spawned
327
+ * `detached` (its own process group), so a negative-pid signal reaches the
328
+ * whole tree — otherwise a running grandchild (a `sleep`, a dev server, a
329
+ * `tail -f`, a build) is reparented to init and leaks past session teardown,
330
+ * holding ports/files. Escalate SIGTERM → SIGKILL after a grace period.
331
+ */
332
+ killChildTree(child) {
333
+ const pid = child.pid;
334
+ const signalGroup = (signal) => {
335
+ try {
336
+ if (pid !== undefined && process.platform !== 'win32') {
337
+ // Negative pid = the whole process group (requires detached spawn).
338
+ process.kill(-pid, signal);
339
+ }
340
+ else {
341
+ // Windows / unknown pid: node handles its own tree (taskkill /T-like).
342
+ child.kill(signal);
343
+ }
344
+ }
345
+ catch {
346
+ // No such process group (already dead) or not permitted — fall back to
347
+ // signaling just the child so we never leave it running.
348
+ try {
349
+ child.kill(signal);
350
+ }
351
+ catch {
352
+ /* already gone */
353
+ }
354
+ }
355
+ };
356
+ signalGroup('SIGTERM');
357
+ setTimeout(() => signalGroup('SIGKILL'), KILL_ESCALATION_MS).unref?.();
358
+ }
359
+ }
360
+ /** Spawn a fresh shared terminal in `cwd`, preferring a real PTY. */
361
+ export async function createTerminalProcess(cwd) {
362
+ const shell = defaultShell();
363
+ const cols = 80;
364
+ const rows = 24;
365
+ // SECURITY: the shared shell deliberately inherits the runner's full
366
+ // environment (API keys, tokens, MOXXY_* signing keys). This mirrors a real
367
+ // user shell so the agent's commands behave as the user expects (a script
368
+ // that needs $GITHUB_TOKEN works). The trade-off is that `env`/`printenv` can
369
+ // surface secrets into captured output and scrollback — acceptable because the
370
+ // terminal is a deliberately user-facing, user-controllable surface. Do NOT
371
+ // strip vars here (it would silently break legitimate commands); gate any
372
+ // scrubbing behind an explicit opt-in if ever needed.
373
+ const env = { ...process.env, TERM: 'xterm-256color' };
374
+ const pty = await loadNodePty();
375
+ let ptyError = pty ? null : 'node-pty is not installed';
376
+ if (pty) {
377
+ const trySpawn = () => pty.spawn(shell, [], { name: 'xterm-256color', cols, rows, cwd, env });
378
+ // Make sure the prebuilt spawn-helper is executable, then spawn. If the first
379
+ // spawn still fails (e.g. the bit was only just lost), repair + retry ONCE
380
+ // before giving up — most "posix_spawnp failed" cases clear on the retry.
381
+ ensureSpawnHelperExecutable();
382
+ try {
383
+ return new TerminalProcessImpl('pty', trySpawn(), null);
384
+ }
385
+ catch {
386
+ ensureSpawnHelperExecutable();
387
+ try {
388
+ return new TerminalProcessImpl('pty', trySpawn(), null);
389
+ }
390
+ catch (err2) {
391
+ // Don't swallow it: record WHY so the surface can show an honest status
392
+ // instead of a silently-dead piped terminal.
393
+ ptyError = err2 instanceof Error ? err2.message : String(err2);
394
+ }
395
+ }
396
+ }
397
+ // Dependency-free fallback: an interactive shell with piped stdio. `-i` keeps
398
+ // it from exiting immediately; non-Windows only — cmd/powershell are already
399
+ // interactive when stdin is piped. NOTE: this has no TTY line discipline (a
400
+ // viewer's `\r` is never turned into `\n`, nothing echoes), so it is NOT a
401
+ // usable interactive terminal — `ptyError` is surfaced to the user so the pane
402
+ // reports the degraded state rather than appearing to ignore every keystroke.
403
+ const args = process.platform === 'win32' ? [] : ['-i'];
404
+ const child = spawn(shell, args, {
405
+ cwd,
406
+ env: { ...env, PS1: '$ ' },
407
+ stdio: ['pipe', 'pipe', 'pipe'],
408
+ // Own process group (POSIX) so kill() can signal the WHOLE tree by negative
409
+ // pid — otherwise grandchildren (a dev server, a `tail -f`, a build) outlive
410
+ // the session. No-op semantics on Windows; node-pty handles its own tree.
411
+ detached: process.platform !== 'win32',
412
+ });
413
+ return new TerminalProcessImpl('pipe', null, child, ptyError);
414
+ }
415
+ //# sourceMappingURL=pty.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pty.js","sourceRoot":"","sources":["../src/pty.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type TerminalProcess } from './pty.js';
2
+ export declare function getSharedTerminal(cwd: string, create?: (cwd: string) => Promise<TerminalProcess>): Promise<TerminalProcess>;
3
+ /** Dispose every shared terminal (plugin shutdown / session close). */
4
+ export declare function closeAllTerminals(): void;
5
+ /** The `terminal` surface: streams the shared PTY's output and feeds a viewer's
6
+ * keystrokes / resizes back into it. */
7
+ export declare function buildTerminalSurface(): import("@moxxy/sdk").SurfaceDef;
8
+ /**
9
+ * The `terminal` tool runs a command in the SHARED terminal the user sees. It
10
+ * appends a unique sentinel that echoes the exit code, then reads output until
11
+ * the sentinel returns — reliable completion detection in an interactive,
12
+ * input-echoing shell. The command and its output stay visible to the user, who
13
+ * can take over at any time.
14
+ */
15
+ export declare function buildTerminalTool(): import("@moxxy/sdk").ToolDef;
16
+ /**
17
+ * Write `command` then a sentinel `printf` to a shared shell and collect output
18
+ * until the sentinel line appears. Returns the captured output (best-effort
19
+ * stripped of the echoed sentinel command) + the exit code parsed from it.
20
+ *
21
+ * Serialized per shared process: a command writes only once the previous one on
22
+ * the same shell finishes, so concurrent callers never interleave their writes
23
+ * on the single stdin and each `$?` reflects its OWN command.
24
+ */
25
+ export declare function runCommand(proc: TerminalProcess, command: string, marker: string, timeoutMs: number,
26
+ /** Optional turn-abort signal; firing it finishes the command immediately
27
+ * (timedOut=false) without waiting for the sentinel or the timeout. */
28
+ signal?: AbortSignal): Promise<{
29
+ output: string;
30
+ exitCode: number | null;
31
+ timedOut: boolean;
32
+ }>;
33
+ //# sourceMappingURL=terminal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAmCvE,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,CAAyB,GACxE,OAAO,CAAC,eAAe,CAAC,CA8B1B;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,IAAI,IAAI,CASxC;AAWD;yCACyC;AACzC,wBAAgB,oBAAoB,oCAyEnC;AAmBD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,iCAkChC;AAyCD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM;AACjB;wEACwE;AACxE,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAkBzE"}