@swarmify/agents-cli 1.10.3 → 1.10.4
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 +28 -0
- package/dist/commands/__tests__/sessions.test.d.ts +2 -0
- package/dist/commands/__tests__/sessions.test.d.ts.map +1 -0
- package/dist/commands/__tests__/sessions.test.js +170 -0
- package/dist/commands/__tests__/sessions.test.js.map +1 -0
- package/dist/commands/commands.d.ts.map +1 -1
- package/dist/commands/commands.js +74 -56
- package/dist/commands/commands.js.map +1 -1
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/hooks.d.ts.map +1 -1
- package/dist/commands/hooks.js +67 -49
- package/dist/commands/hooks.js.map +1 -1
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +29 -18
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/packages.d.ts.map +1 -1
- package/dist/commands/packages.js +3 -3
- package/dist/commands/packages.js.map +1 -1
- package/dist/commands/permissions.d.ts.map +1 -1
- package/dist/commands/permissions.js +71 -48
- package/dist/commands/permissions.js.map +1 -1
- package/dist/commands/plugins.js +8 -8
- package/dist/commands/plugins.js.map +1 -1
- package/dist/commands/pty.d.ts +20 -0
- package/dist/commands/pty.d.ts.map +1 -0
- package/dist/commands/pty.js +280 -0
- package/dist/commands/pty.js.map +1 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +15 -14
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +2 -2
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/routines.d.ts.map +1 -1
- package/dist/commands/routines.js +14 -10
- package/dist/commands/routines.js.map +1 -1
- package/dist/commands/rules.d.ts.map +1 -1
- package/dist/commands/rules.js +65 -50
- package/dist/commands/rules.js.map +1 -1
- package/dist/commands/sessions.d.ts.map +1 -1
- package/dist/commands/sessions.js +219 -21
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/sessions.test.d.ts +2 -0
- package/dist/commands/sessions.test.d.ts.map +1 -0
- package/dist/commands/sessions.test.js +53 -0
- package/dist/commands/sessions.test.js.map +1 -0
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +70 -46
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/subagents.d.ts.map +1 -1
- package/dist/commands/subagents.js +10 -4
- package/dist/commands/subagents.js.map +1 -1
- package/dist/commands/utils.d.ts +16 -0
- package/dist/commands/utils.d.ts.map +1 -1
- package/dist/commands/utils.js +48 -0
- package/dist/commands/utils.js.map +1 -1
- package/dist/commands/versions.d.ts.map +1 -1
- package/dist/commands/versions.js +144 -43
- package/dist/commands/versions.js.map +1 -1
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +97 -40
- package/dist/commands/view.js.map +1 -1
- package/dist/index.js +65 -42
- package/dist/index.js.map +1 -1
- package/dist/lib/agents.d.ts +4 -0
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +36 -1
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/daemon.d.ts.map +1 -1
- package/dist/lib/daemon.js +15 -7
- package/dist/lib/daemon.js.map +1 -1
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +11 -1
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/pty-client.d.ts +22 -0
- package/dist/lib/pty-client.d.ts.map +1 -0
- package/dist/lib/pty-client.js +181 -0
- package/dist/lib/pty-client.js.map +1 -0
- package/dist/lib/pty-server.d.ts +16 -0
- package/dist/lib/pty-server.d.ts.map +1 -0
- package/dist/lib/pty-server.js +422 -0
- package/dist/lib/pty-server.js.map +1 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +13 -9
- package/dist/lib/runner.js.map +1 -1
- package/dist/lib/sandbox.js +1 -1
- package/dist/lib/sandbox.js.map +1 -1
- package/dist/lib/session/discover.d.ts +18 -0
- package/dist/lib/session/discover.d.ts.map +1 -1
- package/dist/lib/session/discover.js +78 -67
- package/dist/lib/session/discover.js.map +1 -1
- package/dist/lib/session/parse.d.ts.map +1 -1
- package/dist/lib/session/parse.js +8 -3
- package/dist/lib/session/parse.js.map +1 -1
- package/dist/lib/shims.d.ts.map +1 -1
- package/dist/lib/shims.js +6 -1
- package/dist/lib/shims.js.map +1 -1
- package/dist/lib/state.d.ts +0 -1
- package/dist/lib/state.d.ts.map +1 -1
- package/dist/lib/state.js +3 -9
- package/dist/lib/state.js.map +1 -1
- package/dist/lib/types.d.ts +2 -5
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/usage.d.ts +29 -0
- package/dist/lib/usage.d.ts.map +1 -0
- package/dist/lib/usage.js +416 -0
- package/dist/lib/usage.js.map +1 -0
- package/dist/lib/versions.d.ts.map +1 -1
- package/dist/lib/versions.js +19 -8
- package/dist/lib/versions.js.map +1 -1
- package/package.json +3 -1
- package/dist/commands/cron.d.ts +0 -3
- package/dist/commands/cron.d.ts.map +0 -1
- package/dist/commands/cron.js +0 -457
- package/dist/commands/cron.js.map +0 -1
- package/dist/lib/cron.d.ts +0 -70
- package/dist/lib/cron.d.ts.map +0 -1
- package/dist/lib/cron.js +0 -325
- package/dist/lib/cron.js.map +0 -1
- package/dist/lib/drive-server.d.ts +0 -9
- package/dist/lib/drive-server.d.ts.map +0 -1
- package/dist/lib/drive-server.js +0 -217
- package/dist/lib/drive-server.js.map +0 -1
- package/dist/lib/drives.d.ts +0 -34
- package/dist/lib/drives.d.ts.map +0 -1
- package/dist/lib/drives.js +0 -267
- package/dist/lib/drives.js.map +0 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PTY Client
|
|
3
|
+
*
|
|
4
|
+
* Thin client that connects to the PTY sidecar server over unix socket.
|
|
5
|
+
* Each call opens a connection, sends a JSON request, reads the JSON response, and closes.
|
|
6
|
+
*/
|
|
7
|
+
export interface PtyResponse {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Send a request to the PTY server and return the response.
|
|
14
|
+
* Auto-starts the server if not running.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ptyRequest(action: string, id?: string, params?: Record<string, any>): Promise<PtyResponse>;
|
|
17
|
+
/**
|
|
18
|
+
* Parse escape sequences in user input strings.
|
|
19
|
+
* Handles: \n \r \t \e \xHH \\
|
|
20
|
+
*/
|
|
21
|
+
export declare function unescapeInput(input: string): string;
|
|
22
|
+
//# sourceMappingURL=pty-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty-client.d.ts","sourceRoot":"","sources":["../../src/lib/pty-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAQhH;AAiJD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAgBnD"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PTY Client
|
|
3
|
+
*
|
|
4
|
+
* Thin client that connects to the PTY sidecar server over unix socket.
|
|
5
|
+
* Each call opens a connection, sends a JSON request, reads the JSON response, and closes.
|
|
6
|
+
*/
|
|
7
|
+
import * as net from 'net';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import { spawn, execSync } from 'child_process';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { getSocketPath, isPtyServerRunning } from './pty-server.js';
|
|
13
|
+
const CONNECT_TIMEOUT_MS = 5000;
|
|
14
|
+
const RESPONSE_TIMEOUT_MS = 30000;
|
|
15
|
+
/**
|
|
16
|
+
* Send a request to the PTY server and return the response.
|
|
17
|
+
* Auto-starts the server if not running.
|
|
18
|
+
*/
|
|
19
|
+
export async function ptyRequest(action, id, params) {
|
|
20
|
+
await ensureServer();
|
|
21
|
+
const req = { action };
|
|
22
|
+
if (id)
|
|
23
|
+
req.id = id;
|
|
24
|
+
if (params)
|
|
25
|
+
req.params = params;
|
|
26
|
+
return sendRequest(req);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Ensure the PTY server is running. Start it if not.
|
|
30
|
+
*/
|
|
31
|
+
async function ensureServer() {
|
|
32
|
+
if (isPtyServerRunning())
|
|
33
|
+
return;
|
|
34
|
+
// Find the entry point to spawn the server
|
|
35
|
+
const { bin, args } = getServerSpawnArgs();
|
|
36
|
+
const logPath = path.join(path.dirname(getSocketPath()), 'pty.log');
|
|
37
|
+
const logFd = fs.openSync(logPath, 'a');
|
|
38
|
+
const child = spawn(bin, args, {
|
|
39
|
+
stdio: ['ignore', logFd, logFd],
|
|
40
|
+
detached: true,
|
|
41
|
+
});
|
|
42
|
+
child.unref();
|
|
43
|
+
fs.closeSync(logFd);
|
|
44
|
+
// Wait for socket to appear
|
|
45
|
+
const socketPath = getSocketPath();
|
|
46
|
+
const deadline = Date.now() + 5000;
|
|
47
|
+
while (Date.now() < deadline) {
|
|
48
|
+
if (fs.existsSync(socketPath)) {
|
|
49
|
+
// Verify we can connect
|
|
50
|
+
try {
|
|
51
|
+
await sendRequest({ action: 'ping' });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Not ready yet
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
await new Promise(r => setTimeout(r, 100));
|
|
59
|
+
}
|
|
60
|
+
throw new Error('PTY server failed to start within 5 seconds. Check ~/.agents/pty.log');
|
|
61
|
+
}
|
|
62
|
+
function getServerSpawnArgs() {
|
|
63
|
+
// Prefer the dist/index.js from the same installation as this code.
|
|
64
|
+
// This avoids version mismatch when a globally installed `agents` is older.
|
|
65
|
+
try {
|
|
66
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
67
|
+
const distIndex = path.join(__dirname, '..', 'index.js');
|
|
68
|
+
if (fs.existsSync(distIndex)) {
|
|
69
|
+
return { bin: process.execPath, args: [distIndex, 'pty', '_server'] };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch { }
|
|
73
|
+
// Fallback: use the globally installed agents command
|
|
74
|
+
try {
|
|
75
|
+
const agentsBin = execSync('which agents', { encoding: 'utf-8' }).trim();
|
|
76
|
+
if (agentsBin) {
|
|
77
|
+
return { bin: agentsBin, args: ['pty', '_server'] };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
return { bin: 'agents', args: ['pty', '_server'] };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Send a JSON request over the unix socket and return the parsed response.
|
|
85
|
+
*/
|
|
86
|
+
function sendRequest(req) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const socketPath = getSocketPath();
|
|
89
|
+
if (!fs.existsSync(socketPath)) {
|
|
90
|
+
reject(new Error('PTY server socket not found. Is the server running?'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const conn = net.createConnection({ path: socketPath });
|
|
94
|
+
let data = '';
|
|
95
|
+
let settled = false;
|
|
96
|
+
const connectTimeout = setTimeout(() => {
|
|
97
|
+
if (!settled) {
|
|
98
|
+
settled = true;
|
|
99
|
+
conn.destroy();
|
|
100
|
+
reject(new Error('Connection to PTY server timed out'));
|
|
101
|
+
}
|
|
102
|
+
}, CONNECT_TIMEOUT_MS);
|
|
103
|
+
const responseTimeout = setTimeout(() => {
|
|
104
|
+
if (!settled) {
|
|
105
|
+
settled = true;
|
|
106
|
+
conn.destroy();
|
|
107
|
+
reject(new Error('PTY server response timed out'));
|
|
108
|
+
}
|
|
109
|
+
}, RESPONSE_TIMEOUT_MS);
|
|
110
|
+
conn.on('connect', () => {
|
|
111
|
+
clearTimeout(connectTimeout);
|
|
112
|
+
conn.write(JSON.stringify(req) + '\n');
|
|
113
|
+
});
|
|
114
|
+
conn.on('data', (chunk) => {
|
|
115
|
+
data += chunk.toString();
|
|
116
|
+
const nlIndex = data.indexOf('\n');
|
|
117
|
+
if (nlIndex !== -1) {
|
|
118
|
+
if (!settled) {
|
|
119
|
+
settled = true;
|
|
120
|
+
clearTimeout(connectTimeout);
|
|
121
|
+
clearTimeout(responseTimeout);
|
|
122
|
+
try {
|
|
123
|
+
resolve(JSON.parse(data.slice(0, nlIndex)));
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
reject(new Error(`Invalid JSON from PTY server: ${data.slice(0, 200)}`));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
conn.end();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
conn.on('error', (err) => {
|
|
133
|
+
if (!settled) {
|
|
134
|
+
settled = true;
|
|
135
|
+
clearTimeout(connectTimeout);
|
|
136
|
+
clearTimeout(responseTimeout);
|
|
137
|
+
reject(new Error(`PTY server connection error: ${err.message}`));
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
conn.on('end', () => {
|
|
141
|
+
if (!settled) {
|
|
142
|
+
settled = true;
|
|
143
|
+
clearTimeout(connectTimeout);
|
|
144
|
+
clearTimeout(responseTimeout);
|
|
145
|
+
if (data.trim()) {
|
|
146
|
+
try {
|
|
147
|
+
resolve(JSON.parse(data.trim()));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
reject(new Error('PTY server closed connection with invalid response'));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
reject(new Error('PTY server closed connection without response'));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Parse escape sequences in user input strings.
|
|
162
|
+
* Handles: \n \r \t \e \xHH \\
|
|
163
|
+
*/
|
|
164
|
+
export function unescapeInput(input) {
|
|
165
|
+
return input.replace(/\\(n|r|t|e|\\|x[0-9a-fA-F]{2})/g, (match, seq) => {
|
|
166
|
+
switch (seq) {
|
|
167
|
+
case 'n': return '\n';
|
|
168
|
+
case 'r': return '\r';
|
|
169
|
+
case 't': return '\t';
|
|
170
|
+
case 'e': return '\x1b';
|
|
171
|
+
case '\\': return '\\';
|
|
172
|
+
default:
|
|
173
|
+
// \xHH
|
|
174
|
+
if (seq.startsWith('x')) {
|
|
175
|
+
return String.fromCharCode(parseInt(seq.slice(1), 16));
|
|
176
|
+
}
|
|
177
|
+
return match;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=pty-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty-client.js","sourceRoot":"","sources":["../../src/lib/pty-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PTY Sidecar Server
|
|
3
|
+
*
|
|
4
|
+
* Lightweight unix socket server that manages persistent PTY sessions.
|
|
5
|
+
* Started as a detached process by `agents pty` commands. Sessions survive
|
|
6
|
+
* across multiple CLI invocations. Each session holds a real PTY (via node-pty)
|
|
7
|
+
* and a headless terminal emulator (via @xterm/headless) for screen rendering.
|
|
8
|
+
*
|
|
9
|
+
* Protocol: newline-delimited JSON over ~/.agents/pty.sock
|
|
10
|
+
*/
|
|
11
|
+
export declare function getSocketPath(): string;
|
|
12
|
+
export declare function getPtyPidPath(): string;
|
|
13
|
+
export declare function getPtyLogPath(): string;
|
|
14
|
+
export declare function isPtyServerRunning(): boolean;
|
|
15
|
+
export declare function runPtyServer(): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=pty-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty-server.d.ts","sourceRoot":"","sources":["../../src/lib/pty-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwCH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAY5C;AAcD,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAqYlD"}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PTY Sidecar Server
|
|
3
|
+
*
|
|
4
|
+
* Lightweight unix socket server that manages persistent PTY sessions.
|
|
5
|
+
* Started as a detached process by `agents pty` commands. Sessions survive
|
|
6
|
+
* across multiple CLI invocations. Each session holds a real PTY (via node-pty)
|
|
7
|
+
* and a headless terminal emulator (via @xterm/headless) for screen rendering.
|
|
8
|
+
*
|
|
9
|
+
* Protocol: newline-delimited JSON over ~/.agents/pty.sock
|
|
10
|
+
*/
|
|
11
|
+
import * as net from 'net';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import * as crypto from 'crypto';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { getAgentsDir } from './state.js';
|
|
17
|
+
// --- Constants ---
|
|
18
|
+
const SENTINEL = '__AGENTS_PTY_DONE__';
|
|
19
|
+
const SOCKET_NAME = 'pty.sock';
|
|
20
|
+
const PID_FILE = 'pty.pid';
|
|
21
|
+
const LOG_FILE = 'pty.log';
|
|
22
|
+
const SESSION_IDLE_MS = 30 * 60 * 1000; // 30 min
|
|
23
|
+
const SERVER_IDLE_MS = 60 * 60 * 1000; // 1 hour
|
|
24
|
+
// --- Path helpers ---
|
|
25
|
+
export function getSocketPath() {
|
|
26
|
+
return path.join(getAgentsDir(), SOCKET_NAME);
|
|
27
|
+
}
|
|
28
|
+
export function getPtyPidPath() {
|
|
29
|
+
return path.join(getAgentsDir(), PID_FILE);
|
|
30
|
+
}
|
|
31
|
+
export function getPtyLogPath() {
|
|
32
|
+
return path.join(getAgentsDir(), LOG_FILE);
|
|
33
|
+
}
|
|
34
|
+
export function isPtyServerRunning() {
|
|
35
|
+
const pidPath = getPtyPidPath();
|
|
36
|
+
if (!fs.existsSync(pidPath))
|
|
37
|
+
return false;
|
|
38
|
+
try {
|
|
39
|
+
const pid = parseInt(fs.readFileSync(pidPath, 'utf-8').trim(), 10);
|
|
40
|
+
if (isNaN(pid))
|
|
41
|
+
return false;
|
|
42
|
+
process.kill(pid, 0);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
try {
|
|
47
|
+
fs.unlinkSync(pidPath);
|
|
48
|
+
}
|
|
49
|
+
catch { }
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// --- Logging ---
|
|
54
|
+
function log(level, message) {
|
|
55
|
+
const ts = new Date().toISOString();
|
|
56
|
+
const line = `[${ts}] [${level}] ${message}\n`;
|
|
57
|
+
try {
|
|
58
|
+
fs.appendFileSync(getPtyLogPath(), line, 'utf-8');
|
|
59
|
+
}
|
|
60
|
+
catch { }
|
|
61
|
+
}
|
|
62
|
+
// --- Server ---
|
|
63
|
+
export async function runPtyServer() {
|
|
64
|
+
// Dynamic imports for optional native deps
|
|
65
|
+
let nodePty;
|
|
66
|
+
let XtermTerminal;
|
|
67
|
+
try {
|
|
68
|
+
nodePty = await import('node-pty');
|
|
69
|
+
// Handle ESM default export
|
|
70
|
+
if (nodePty.default?.spawn)
|
|
71
|
+
nodePty = nodePty.default;
|
|
72
|
+
// Ensure spawn-helper is executable (bun install doesn't set +x on prebuilds)
|
|
73
|
+
try {
|
|
74
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
75
|
+
const ptyBase = path.resolve(__dirname, '..', '..', 'node_modules', 'node-pty');
|
|
76
|
+
const helpers = [
|
|
77
|
+
path.join(ptyBase, 'prebuilds', `${process.platform}-${process.arch}`, 'spawn-helper'),
|
|
78
|
+
path.join(ptyBase, 'build', 'Release', 'spawn-helper'),
|
|
79
|
+
];
|
|
80
|
+
for (const h of helpers) {
|
|
81
|
+
if (fs.existsSync(h)) {
|
|
82
|
+
fs.chmodSync(h, 0o755);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch { }
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
console.error('node-pty is required for PTY support.');
|
|
90
|
+
console.error('Install: cd ' + getAgentsDir() + '/../agents-cli && bun add node-pty');
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const xterm = await import('@xterm/headless');
|
|
95
|
+
// Handle ESM default export wrapping
|
|
96
|
+
XtermTerminal = xterm.Terminal || xterm.default?.Terminal;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
console.error('@xterm/headless is required for PTY support.');
|
|
100
|
+
console.error('Install: cd ' + getAgentsDir() + '/../agents-cli && bun add @xterm/headless');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
const sessions = new Map();
|
|
104
|
+
const socketPath = getSocketPath();
|
|
105
|
+
// Remove stale socket
|
|
106
|
+
if (fs.existsSync(socketPath)) {
|
|
107
|
+
try {
|
|
108
|
+
fs.unlinkSync(socketPath);
|
|
109
|
+
}
|
|
110
|
+
catch { }
|
|
111
|
+
}
|
|
112
|
+
let lastActivityTime = Date.now();
|
|
113
|
+
function generateId() {
|
|
114
|
+
return crypto.randomBytes(4).toString('hex');
|
|
115
|
+
}
|
|
116
|
+
function killSession(session) {
|
|
117
|
+
if (!session.exited) {
|
|
118
|
+
try {
|
|
119
|
+
session.pty.kill();
|
|
120
|
+
}
|
|
121
|
+
catch { }
|
|
122
|
+
session.exited = true;
|
|
123
|
+
}
|
|
124
|
+
if (session.terminal) {
|
|
125
|
+
try {
|
|
126
|
+
session.terminal.dispose();
|
|
127
|
+
}
|
|
128
|
+
catch { }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function getScreenLines(session) {
|
|
132
|
+
const lines = [];
|
|
133
|
+
const buf = session.terminal.buffer.active;
|
|
134
|
+
for (let y = 0; y < session.rows; y++) {
|
|
135
|
+
const line = buf.getLine(y);
|
|
136
|
+
const text = line ? line.translateToString(true) : '';
|
|
137
|
+
// Strip lines containing the sentinel pattern
|
|
138
|
+
if (text.includes(SENTINEL))
|
|
139
|
+
continue;
|
|
140
|
+
lines.push(text);
|
|
141
|
+
}
|
|
142
|
+
// Trim trailing empty lines
|
|
143
|
+
while (lines.length > 0 && lines[lines.length - 1] === '') {
|
|
144
|
+
lines.pop();
|
|
145
|
+
}
|
|
146
|
+
return lines;
|
|
147
|
+
}
|
|
148
|
+
// Session idle cleanup + server auto-exit
|
|
149
|
+
const cleanupInterval = setInterval(() => {
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
for (const [id, session] of sessions) {
|
|
152
|
+
if (now - session.lastActivity > SESSION_IDLE_MS) {
|
|
153
|
+
log('INFO', `Cleaning up idle session ${id}`);
|
|
154
|
+
killSession(session);
|
|
155
|
+
sessions.delete(id);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (sessions.size === 0 && now - lastActivityTime > SERVER_IDLE_MS) {
|
|
159
|
+
log('INFO', 'No sessions, server idle timeout reached. Shutting down.');
|
|
160
|
+
shutdown();
|
|
161
|
+
}
|
|
162
|
+
}, 60_000);
|
|
163
|
+
// --- Request handlers ---
|
|
164
|
+
async function handleRequest(req) {
|
|
165
|
+
lastActivityTime = Date.now();
|
|
166
|
+
switch (req.action) {
|
|
167
|
+
case 'start': {
|
|
168
|
+
const rows = req.params?.rows || 24;
|
|
169
|
+
const cols = req.params?.cols || 120;
|
|
170
|
+
const shell = req.params?.shell || process.env.SHELL || 'zsh';
|
|
171
|
+
const cwd = req.params?.cwd || process.env.HOME || '/';
|
|
172
|
+
const id = generateId();
|
|
173
|
+
let ptyProcess;
|
|
174
|
+
try {
|
|
175
|
+
ptyProcess = nodePty.spawn(shell, [], {
|
|
176
|
+
name: 'xterm-256color',
|
|
177
|
+
cols,
|
|
178
|
+
rows,
|
|
179
|
+
cwd,
|
|
180
|
+
env: { ...process.env },
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
return { ok: false, error: `Failed to spawn PTY: ${err.message}` };
|
|
185
|
+
}
|
|
186
|
+
const terminal = new XtermTerminal({ rows, cols, allowProposedApi: true });
|
|
187
|
+
const session = {
|
|
188
|
+
id,
|
|
189
|
+
pty: ptyProcess,
|
|
190
|
+
terminal,
|
|
191
|
+
rows,
|
|
192
|
+
cols,
|
|
193
|
+
shell,
|
|
194
|
+
cwd,
|
|
195
|
+
pid: ptyProcess.pid,
|
|
196
|
+
startedAt: Date.now(),
|
|
197
|
+
lastActivity: Date.now(),
|
|
198
|
+
pendingOutput: '',
|
|
199
|
+
appActive: false,
|
|
200
|
+
activeCommand: '',
|
|
201
|
+
exited: false,
|
|
202
|
+
exitCode: null,
|
|
203
|
+
};
|
|
204
|
+
ptyProcess.onData((data) => {
|
|
205
|
+
session.pendingOutput += data;
|
|
206
|
+
terminal.write(data);
|
|
207
|
+
session.lastActivity = Date.now();
|
|
208
|
+
// Check for sentinel to detect command completion
|
|
209
|
+
if (session.appActive && session.pendingOutput.includes(SENTINEL + ':')) {
|
|
210
|
+
session.appActive = false;
|
|
211
|
+
session.activeCommand = '';
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
ptyProcess.onExit(({ exitCode }) => {
|
|
215
|
+
session.exited = true;
|
|
216
|
+
session.exitCode = exitCode;
|
|
217
|
+
session.appActive = false;
|
|
218
|
+
});
|
|
219
|
+
// Wait for shell to initialize, then clear init output
|
|
220
|
+
await new Promise(r => setTimeout(r, 300));
|
|
221
|
+
session.pendingOutput = '';
|
|
222
|
+
sessions.set(id, session);
|
|
223
|
+
log('INFO', `Session started: ${id} (pid=${ptyProcess.pid}, shell=${shell}, ${cols}x${rows})`);
|
|
224
|
+
return { ok: true, id, pid: ptyProcess.pid, rows, cols, shell };
|
|
225
|
+
}
|
|
226
|
+
case 'exec': {
|
|
227
|
+
const session = sessions.get(req.id);
|
|
228
|
+
if (!session)
|
|
229
|
+
return { ok: false, error: `Session not found: ${req.id}` };
|
|
230
|
+
if (session.exited)
|
|
231
|
+
return { ok: false, error: 'Session has exited' };
|
|
232
|
+
if (session.appActive) {
|
|
233
|
+
return { ok: false, error: `Command already active: ${session.activeCommand}. Use write to interact or signal to interrupt.` };
|
|
234
|
+
}
|
|
235
|
+
const command = req.params?.command;
|
|
236
|
+
if (!command)
|
|
237
|
+
return { ok: false, error: 'command is required' };
|
|
238
|
+
session.appActive = true;
|
|
239
|
+
session.activeCommand = command;
|
|
240
|
+
session.pendingOutput = '';
|
|
241
|
+
session.pty.write(`${command}; echo "${SENTINEL}:$?"\n`);
|
|
242
|
+
session.lastActivity = Date.now();
|
|
243
|
+
return { ok: true, submitted: true };
|
|
244
|
+
}
|
|
245
|
+
case 'read': {
|
|
246
|
+
const session = sessions.get(req.id);
|
|
247
|
+
if (!session)
|
|
248
|
+
return { ok: false, error: `Session not found: ${req.id}` };
|
|
249
|
+
const waitMs = Math.min(Math.max(req.params?.ms || 100, 50), 5000);
|
|
250
|
+
// Wait for output to accumulate
|
|
251
|
+
if (session.pendingOutput.length === 0) {
|
|
252
|
+
await new Promise(r => setTimeout(r, waitMs));
|
|
253
|
+
}
|
|
254
|
+
const output = session.pendingOutput;
|
|
255
|
+
session.pendingOutput = '';
|
|
256
|
+
// Strip sentinel lines from output
|
|
257
|
+
const cleaned = output
|
|
258
|
+
.split('\n')
|
|
259
|
+
.filter(line => !line.includes(SENTINEL))
|
|
260
|
+
.join('\n');
|
|
261
|
+
return {
|
|
262
|
+
ok: true,
|
|
263
|
+
output: cleaned,
|
|
264
|
+
bytes: output.length,
|
|
265
|
+
app_active: session.appActive,
|
|
266
|
+
active_command: session.activeCommand || undefined,
|
|
267
|
+
exited: session.exited,
|
|
268
|
+
exit_code: session.exitCode,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
case 'write': {
|
|
272
|
+
const session = sessions.get(req.id);
|
|
273
|
+
if (!session)
|
|
274
|
+
return { ok: false, error: `Session not found: ${req.id}` };
|
|
275
|
+
if (session.exited)
|
|
276
|
+
return { ok: false, error: 'Session has exited' };
|
|
277
|
+
let input = req.params?.input ?? '';
|
|
278
|
+
if (input === '')
|
|
279
|
+
input = '\n';
|
|
280
|
+
session.pty.write(input);
|
|
281
|
+
session.lastActivity = Date.now();
|
|
282
|
+
return { ok: true };
|
|
283
|
+
}
|
|
284
|
+
case 'screen': {
|
|
285
|
+
const session = sessions.get(req.id);
|
|
286
|
+
if (!session)
|
|
287
|
+
return { ok: false, error: `Session not found: ${req.id}` };
|
|
288
|
+
const lines = getScreenLines(session);
|
|
289
|
+
const buf = session.terminal.buffer.active;
|
|
290
|
+
return {
|
|
291
|
+
ok: true,
|
|
292
|
+
screen: lines.join('\n'),
|
|
293
|
+
rows: session.rows,
|
|
294
|
+
cols: session.cols,
|
|
295
|
+
cursor: { x: buf.cursorX, y: buf.cursorY },
|
|
296
|
+
app_active: session.appActive,
|
|
297
|
+
active_command: session.activeCommand || undefined,
|
|
298
|
+
exited: session.exited,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
case 'signal': {
|
|
302
|
+
const session = sessions.get(req.id);
|
|
303
|
+
if (!session)
|
|
304
|
+
return { ok: false, error: `Session not found: ${req.id}` };
|
|
305
|
+
if (session.exited)
|
|
306
|
+
return { ok: false, error: 'Session has exited' };
|
|
307
|
+
const sig = (req.params?.signal || 'INT').toUpperCase();
|
|
308
|
+
if (!['INT', 'TERM', 'KILL', 'HUP'].includes(sig)) {
|
|
309
|
+
return { ok: false, error: `Unsupported signal: ${sig}` };
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
// node-pty kill accepts signal number; use process.kill for named signals
|
|
313
|
+
process.kill(session.pid, `SIG${sig}`);
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
return { ok: false, error: `Failed to send signal: ${err.message}` };
|
|
317
|
+
}
|
|
318
|
+
return { ok: true };
|
|
319
|
+
}
|
|
320
|
+
case 'resize': {
|
|
321
|
+
const session = sessions.get(req.id);
|
|
322
|
+
if (!session)
|
|
323
|
+
return { ok: false, error: `Session not found: ${req.id}` };
|
|
324
|
+
if (session.exited)
|
|
325
|
+
return { ok: false, error: 'Session has exited' };
|
|
326
|
+
const rows = req.params?.rows || session.rows;
|
|
327
|
+
const cols = req.params?.cols || session.cols;
|
|
328
|
+
session.pty.resize(cols, rows);
|
|
329
|
+
session.terminal.resize(cols, rows);
|
|
330
|
+
session.rows = rows;
|
|
331
|
+
session.cols = cols;
|
|
332
|
+
return { ok: true, rows, cols };
|
|
333
|
+
}
|
|
334
|
+
case 'list': {
|
|
335
|
+
const list = [];
|
|
336
|
+
for (const [, session] of sessions) {
|
|
337
|
+
list.push({
|
|
338
|
+
id: session.id,
|
|
339
|
+
pid: session.pid,
|
|
340
|
+
shell: session.shell,
|
|
341
|
+
cwd: session.cwd,
|
|
342
|
+
rows: session.rows,
|
|
343
|
+
cols: session.cols,
|
|
344
|
+
started_at: session.startedAt,
|
|
345
|
+
last_activity: session.lastActivity,
|
|
346
|
+
app_active: session.appActive,
|
|
347
|
+
active_command: session.activeCommand || undefined,
|
|
348
|
+
exited: session.exited,
|
|
349
|
+
exit_code: session.exitCode,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return { ok: true, sessions: list };
|
|
353
|
+
}
|
|
354
|
+
case 'stop': {
|
|
355
|
+
const session = sessions.get(req.id);
|
|
356
|
+
if (!session)
|
|
357
|
+
return { ok: false, error: `Session not found: ${req.id}` };
|
|
358
|
+
killSession(session);
|
|
359
|
+
sessions.delete(req.id);
|
|
360
|
+
log('INFO', `Session stopped: ${req.id}`);
|
|
361
|
+
return { ok: true };
|
|
362
|
+
}
|
|
363
|
+
case 'ping': {
|
|
364
|
+
return { ok: true, sessions: sessions.size, pid: process.pid };
|
|
365
|
+
}
|
|
366
|
+
default:
|
|
367
|
+
return { ok: false, error: `Unknown action: ${req.action}` };
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// --- Socket server ---
|
|
371
|
+
const server = net.createServer((conn) => {
|
|
372
|
+
let buf = '';
|
|
373
|
+
conn.on('data', async (chunk) => {
|
|
374
|
+
buf += chunk.toString();
|
|
375
|
+
const nlIndex = buf.indexOf('\n');
|
|
376
|
+
if (nlIndex === -1)
|
|
377
|
+
return;
|
|
378
|
+
const line = buf.slice(0, nlIndex);
|
|
379
|
+
buf = '';
|
|
380
|
+
try {
|
|
381
|
+
const req = JSON.parse(line);
|
|
382
|
+
const res = await handleRequest(req);
|
|
383
|
+
conn.write(JSON.stringify(res) + '\n');
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
conn.write(JSON.stringify({ ok: false, error: err.message || String(err) }) + '\n');
|
|
387
|
+
}
|
|
388
|
+
conn.end();
|
|
389
|
+
});
|
|
390
|
+
conn.on('error', () => { });
|
|
391
|
+
});
|
|
392
|
+
await new Promise((resolve) => {
|
|
393
|
+
server.listen(socketPath, () => resolve());
|
|
394
|
+
});
|
|
395
|
+
// Write PID
|
|
396
|
+
fs.writeFileSync(getPtyPidPath(), String(process.pid), 'utf-8');
|
|
397
|
+
log('INFO', `PTY server started (PID: ${process.pid}, socket: ${socketPath})`);
|
|
398
|
+
// Shutdown handler
|
|
399
|
+
function shutdown() {
|
|
400
|
+
log('INFO', 'PTY server shutting down');
|
|
401
|
+
for (const session of sessions.values()) {
|
|
402
|
+
killSession(session);
|
|
403
|
+
}
|
|
404
|
+
sessions.clear();
|
|
405
|
+
clearInterval(cleanupInterval);
|
|
406
|
+
server.close();
|
|
407
|
+
try {
|
|
408
|
+
fs.unlinkSync(socketPath);
|
|
409
|
+
}
|
|
410
|
+
catch { }
|
|
411
|
+
try {
|
|
412
|
+
fs.unlinkSync(getPtyPidPath());
|
|
413
|
+
}
|
|
414
|
+
catch { }
|
|
415
|
+
process.exit(0);
|
|
416
|
+
}
|
|
417
|
+
process.on('SIGTERM', shutdown);
|
|
418
|
+
process.on('SIGINT', shutdown);
|
|
419
|
+
// Keep alive
|
|
420
|
+
await new Promise(() => { });
|
|
421
|
+
}
|
|
422
|
+
//# sourceMappingURL=pty-server.js.map
|