-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathserver.ts
More file actions
123 lines (108 loc) · 3.52 KB
/
server.ts
File metadata and controls
123 lines (108 loc) · 3.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { createServer } from "http";
import { parse } from "url";
import next from "next";
import { WebSocketServer, WebSocket } from "ws";
import * as pty from "node-pty";
const dev = process.env.NODE_ENV !== "production";
const hostname = "0.0.0.0";
// Support: npm run dev -- -p 3012
const pFlagIndex = process.argv.indexOf("-p");
const portArg = pFlagIndex !== -1 ? process.argv[pFlagIndex + 1] : undefined;
const port = parseInt(portArg || process.env.PORT || "3011", 10);
const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = createServer(async (req, res) => {
try {
const parsedUrl = parse(req.url!, true);
await handle(req, res, parsedUrl);
} catch (err) {
console.error("Error occurred handling", req.url, err);
res.statusCode = 500;
res.end("internal server error");
}
});
// Terminal WebSocket server
const terminalWss = new WebSocketServer({ noServer: true });
// Handle WebSocket upgrades
server.on("upgrade", (request, socket, head) => {
const { pathname } = parse(request.url || "");
if (pathname === "/ws/terminal") {
terminalWss.handleUpgrade(request, socket, head, (ws) => {
terminalWss.emit("connection", ws, request);
});
}
// Let HMR and other WebSocket connections pass through to Next.js
});
// Terminal connections
terminalWss.on("connection", (ws: WebSocket) => {
let ptyProcess: pty.IPty;
try {
const shell = process.env.SHELL || "/bin/zsh";
// Use minimal env - only essentials for shell to work
// This lets Next.js/Vite/etc load .env.local without interference from parent process env
const minimalEnv: { [key: string]: string } = {
PATH: process.env.PATH || "/usr/local/bin:/usr/bin:/bin",
HOME: process.env.HOME || "/",
USER: process.env.USER || "",
SHELL: shell,
TERM: "xterm-256color",
COLORTERM: "truecolor",
LANG: process.env.LANG || "en_US.UTF-8",
};
ptyProcess = pty.spawn(shell, [], {
name: "xterm-256color",
cols: 80,
rows: 24,
cwd: process.env.HOME || "/",
env: minimalEnv,
});
} catch (err) {
console.error("Failed to spawn pty:", err);
ws.send(
JSON.stringify({ type: "error", message: "Failed to start terminal" })
);
ws.close();
return;
}
ptyProcess.onData((data: string) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "output", data }));
}
});
ptyProcess.onExit(({ exitCode }) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "exit", code: exitCode }));
ws.close();
}
});
ws.on("message", (message: Buffer) => {
try {
const msg = JSON.parse(message.toString());
switch (msg.type) {
case "input":
ptyProcess.write(msg.data);
break;
case "resize":
ptyProcess.resize(msg.cols, msg.rows);
break;
case "command":
ptyProcess.write(msg.data + "\r");
break;
}
} catch (err) {
console.error("Error parsing message:", err);
}
});
ws.on("close", () => {
ptyProcess.kill();
});
ws.on("error", (err) => {
console.error("WebSocket error:", err);
ptyProcess.kill();
});
});
server.listen(port, () => {
console.log(`> Agent-OS ready on http://${hostname}:${port}`);
});
});