mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
chore: update package.json and refactor terminal WebSocket connection handling
- Added a postinstall script in package.json to set permissions for spawn-helper on macOS. - Refactored the terminal WebSocket connection handling in index.ts for improved readability and consistency. - Enhanced error logging and connection management in the terminal service. - Cleaned up formatting and indentation across multiple files for better code clarity.
This commit is contained in:
@@ -30,7 +30,12 @@ import { createSpecRegenerationRoutes } from "./routes/spec-regeneration.js";
|
||||
import { createRunningAgentsRoutes } from "./routes/running-agents.js";
|
||||
import { createWorkspaceRoutes } from "./routes/workspace.js";
|
||||
import { createTemplatesRoutes } from "./routes/templates.js";
|
||||
import { createTerminalRoutes, validateTerminalToken, isTerminalEnabled, isTerminalPasswordRequired } from "./routes/terminal.js";
|
||||
import {
|
||||
createTerminalRoutes,
|
||||
validateTerminalToken,
|
||||
isTerminalEnabled,
|
||||
isTerminalPasswordRequired,
|
||||
} from "./routes/terminal.js";
|
||||
import { AgentService } from "./services/agent-service.js";
|
||||
import { FeatureLoader } from "./services/feature-loader.js";
|
||||
import { AutoModeService } from "./services/auto-mode-service.js";
|
||||
@@ -64,7 +69,9 @@ if (!hasAnthropicKey && !hasOAuthToken) {
|
||||
╚═══════════════════════════════════════════════════════════════════════╝
|
||||
`);
|
||||
} else if (hasOAuthToken) {
|
||||
console.log("[Server] ✓ CLAUDE_CODE_OAUTH_TOKEN detected (subscription auth)");
|
||||
console.log(
|
||||
"[Server] ✓ CLAUDE_CODE_OAUTH_TOKEN detected (subscription auth)"
|
||||
);
|
||||
} else {
|
||||
console.log("[Server] ✓ ANTHROPIC_API_KEY detected (API key auth)");
|
||||
}
|
||||
@@ -130,7 +137,10 @@ const terminalService = getTerminalService();
|
||||
|
||||
// Handle HTTP upgrade requests manually to route to correct WebSocket server
|
||||
server.on("upgrade", (request, socket, head) => {
|
||||
const { pathname } = new URL(request.url || "", `http://${request.headers.host}`);
|
||||
const { pathname } = new URL(
|
||||
request.url || "",
|
||||
`http://${request.headers.host}`
|
||||
);
|
||||
|
||||
if (pathname === "/api/events") {
|
||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
@@ -171,139 +181,153 @@ wss.on("connection", (ws: WebSocket) => {
|
||||
const terminalConnections: Map<string, Set<WebSocket>> = new Map();
|
||||
|
||||
// Terminal WebSocket connection handler
|
||||
terminalWss.on("connection", (ws: WebSocket, req: import("http").IncomingMessage) => {
|
||||
// Parse URL to get session ID and token
|
||||
const url = new URL(req.url || "", `http://${req.headers.host}`);
|
||||
const sessionId = url.searchParams.get("sessionId");
|
||||
const token = url.searchParams.get("token");
|
||||
terminalWss.on(
|
||||
"connection",
|
||||
(ws: WebSocket, req: import("http").IncomingMessage) => {
|
||||
// Parse URL to get session ID and token
|
||||
const url = new URL(req.url || "", `http://${req.headers.host}`);
|
||||
const sessionId = url.searchParams.get("sessionId");
|
||||
const token = url.searchParams.get("token");
|
||||
|
||||
console.log(`[Terminal WS] Connection attempt for session: ${sessionId}`);
|
||||
console.log(`[Terminal WS] Connection attempt for session: ${sessionId}`);
|
||||
|
||||
// Check if terminal is enabled
|
||||
if (!isTerminalEnabled()) {
|
||||
console.log("[Terminal WS] Terminal is disabled");
|
||||
ws.close(4003, "Terminal access is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate token if password is required
|
||||
if (isTerminalPasswordRequired() && !validateTerminalToken(token || undefined)) {
|
||||
console.log("[Terminal WS] Invalid or missing token");
|
||||
ws.close(4001, "Authentication required");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sessionId) {
|
||||
console.log("[Terminal WS] No session ID provided");
|
||||
ws.close(4002, "Session ID required");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if session exists
|
||||
const session = terminalService.getSession(sessionId);
|
||||
if (!session) {
|
||||
console.log(`[Terminal WS] Session ${sessionId} not found`);
|
||||
ws.close(4004, "Session not found");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[Terminal WS] Client connected to session ${sessionId}`);
|
||||
|
||||
// Track this connection
|
||||
if (!terminalConnections.has(sessionId)) {
|
||||
terminalConnections.set(sessionId, new Set());
|
||||
}
|
||||
terminalConnections.get(sessionId)!.add(ws);
|
||||
|
||||
// Subscribe to terminal data
|
||||
const unsubscribeData = terminalService.onData((sid, data) => {
|
||||
if (sid === sessionId && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: "data", data }));
|
||||
// Check if terminal is enabled
|
||||
if (!isTerminalEnabled()) {
|
||||
console.log("[Terminal WS] Terminal is disabled");
|
||||
ws.close(4003, "Terminal access is disabled");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to terminal exit
|
||||
const unsubscribeExit = terminalService.onExit((sid, exitCode) => {
|
||||
if (sid === sessionId && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: "exit", exitCode }));
|
||||
ws.close(1000, "Session ended");
|
||||
// Validate token if password is required
|
||||
if (
|
||||
isTerminalPasswordRequired() &&
|
||||
!validateTerminalToken(token || undefined)
|
||||
) {
|
||||
console.log("[Terminal WS] Invalid or missing token");
|
||||
ws.close(4001, "Authentication required");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle incoming messages
|
||||
ws.on("message", (message) => {
|
||||
try {
|
||||
const msg = JSON.parse(message.toString());
|
||||
if (!sessionId) {
|
||||
console.log("[Terminal WS] No session ID provided");
|
||||
ws.close(4002, "Session ID required");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.type) {
|
||||
case "input":
|
||||
// Write user input to terminal
|
||||
terminalService.write(sessionId, msg.data);
|
||||
break;
|
||||
// Check if session exists
|
||||
const session = terminalService.getSession(sessionId);
|
||||
if (!session) {
|
||||
console.log(`[Terminal WS] Session ${sessionId} not found`);
|
||||
ws.close(4004, "Session not found");
|
||||
return;
|
||||
}
|
||||
|
||||
case "resize":
|
||||
// Resize terminal
|
||||
if (msg.cols && msg.rows) {
|
||||
terminalService.resize(sessionId, msg.cols, msg.rows);
|
||||
}
|
||||
break;
|
||||
console.log(`[Terminal WS] Client connected to session ${sessionId}`);
|
||||
|
||||
case "ping":
|
||||
// Respond to ping
|
||||
ws.send(JSON.stringify({ type: "pong" }));
|
||||
break;
|
||||
// Track this connection
|
||||
if (!terminalConnections.has(sessionId)) {
|
||||
terminalConnections.set(sessionId, new Set());
|
||||
}
|
||||
terminalConnections.get(sessionId)!.add(ws);
|
||||
|
||||
default:
|
||||
console.warn(`[Terminal WS] Unknown message type: ${msg.type}`);
|
||||
// Subscribe to terminal data
|
||||
const unsubscribeData = terminalService.onData((sid, data) => {
|
||||
if (sid === sessionId && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: "data", data }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Terminal WS] Error processing message:", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
console.log(`[Terminal WS] Client disconnected from session ${sessionId}`);
|
||||
unsubscribeData();
|
||||
unsubscribeExit();
|
||||
|
||||
// Remove from connections tracking
|
||||
const connections = terminalConnections.get(sessionId);
|
||||
if (connections) {
|
||||
connections.delete(ws);
|
||||
if (connections.size === 0) {
|
||||
terminalConnections.delete(sessionId);
|
||||
// Subscribe to terminal exit
|
||||
const unsubscribeExit = terminalService.onExit((sid, exitCode) => {
|
||||
if (sid === sessionId && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: "exit", exitCode }));
|
||||
ws.close(1000, "Session ended");
|
||||
}
|
||||
});
|
||||
|
||||
// Handle incoming messages
|
||||
ws.on("message", (message) => {
|
||||
try {
|
||||
const msg = JSON.parse(message.toString());
|
||||
|
||||
switch (msg.type) {
|
||||
case "input":
|
||||
// Write user input to terminal
|
||||
terminalService.write(sessionId, msg.data);
|
||||
break;
|
||||
|
||||
case "resize":
|
||||
// Resize terminal
|
||||
if (msg.cols && msg.rows) {
|
||||
terminalService.resize(sessionId, msg.cols, msg.rows);
|
||||
}
|
||||
break;
|
||||
|
||||
case "ping":
|
||||
// Respond to ping
|
||||
ws.send(JSON.stringify({ type: "pong" }));
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`[Terminal WS] Unknown message type: ${msg.type}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Terminal WS] Error processing message:", error);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
console.log(
|
||||
`[Terminal WS] Client disconnected from session ${sessionId}`
|
||||
);
|
||||
unsubscribeData();
|
||||
unsubscribeExit();
|
||||
|
||||
// Remove from connections tracking
|
||||
const connections = terminalConnections.get(sessionId);
|
||||
if (connections) {
|
||||
connections.delete(ws);
|
||||
if (connections.size === 0) {
|
||||
terminalConnections.delete(sessionId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("error", (error) => {
|
||||
console.error(`[Terminal WS] Error on session ${sessionId}:`, error);
|
||||
unsubscribeData();
|
||||
unsubscribeExit();
|
||||
});
|
||||
|
||||
// Send initial connection success
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "connected",
|
||||
sessionId,
|
||||
shell: session.shell,
|
||||
cwd: session.cwd,
|
||||
})
|
||||
);
|
||||
|
||||
// Send scrollback buffer to replay previous output
|
||||
const scrollback = terminalService.getScrollback(sessionId);
|
||||
if (scrollback && scrollback.length > 0) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "scrollback",
|
||||
data: scrollback,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("error", (error) => {
|
||||
console.error(`[Terminal WS] Error on session ${sessionId}:`, error);
|
||||
unsubscribeData();
|
||||
unsubscribeExit();
|
||||
});
|
||||
|
||||
// Send initial connection success
|
||||
ws.send(JSON.stringify({
|
||||
type: "connected",
|
||||
sessionId,
|
||||
shell: session.shell,
|
||||
cwd: session.cwd,
|
||||
}));
|
||||
|
||||
// Send scrollback buffer to replay previous output
|
||||
const scrollback = terminalService.getScrollback(sessionId);
|
||||
if (scrollback && scrollback.length > 0) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "scrollback",
|
||||
data: scrollback,
|
||||
}));
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// Start server
|
||||
server.listen(PORT, () => {
|
||||
const terminalStatus = isTerminalEnabled()
|
||||
? (isTerminalPasswordRequired() ? "enabled (password protected)" : "enabled")
|
||||
? isTerminalPasswordRequired()
|
||||
? "enabled (password protected)"
|
||||
: "enabled"
|
||||
: "disabled";
|
||||
console.log(`
|
||||
╔═══════════════════════════════════════════════════════╗
|
||||
|
||||
@@ -9,9 +9,7 @@ import fs from "fs/promises";
|
||||
import type { EventEmitter } from "../lib/events.js";
|
||||
import { ProviderFactory } from "../providers/provider-factory.js";
|
||||
import type { ExecuteOptions } from "../providers/types.js";
|
||||
import {
|
||||
readImageAsBase64,
|
||||
} from "../lib/image-handler.js";
|
||||
import { readImageAsBase64 } from "../lib/image-handler.js";
|
||||
import { buildPromptWithImages } from "../lib/prompt-builder.js";
|
||||
import { getEffectiveModel } from "../lib/model-resolver.js";
|
||||
import { isAbortError } from "../lib/error-handler.js";
|
||||
@@ -136,7 +134,10 @@ export class AgentService {
|
||||
filename: imageData.filename,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[AgentService] Failed to load image ${imagePath}:`, error);
|
||||
console.error(
|
||||
`[AgentService] Failed to load image ${imagePath}:`,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +198,8 @@ export class AgentService {
|
||||
"WebFetch",
|
||||
],
|
||||
abortController: session.abortController!,
|
||||
conversationHistory: conversationHistory.length > 0 ? conversationHistory : undefined,
|
||||
conversationHistory:
|
||||
conversationHistory.length > 0 ? conversationHistory : undefined,
|
||||
};
|
||||
|
||||
// Build prompt content with images
|
||||
@@ -381,7 +383,11 @@ export class AgentService {
|
||||
const sessionFile = path.join(this.stateDir, `${sessionId}.json`);
|
||||
|
||||
try {
|
||||
await fs.writeFile(sessionFile, JSON.stringify(messages, null, 2), "utf-8");
|
||||
await fs.writeFile(
|
||||
sessionFile,
|
||||
JSON.stringify(messages, null, 2),
|
||||
"utf-8"
|
||||
);
|
||||
await this.updateSessionTimestamp(sessionId);
|
||||
} catch (error) {
|
||||
console.error("[AgentService] Failed to save session:", error);
|
||||
@@ -398,7 +404,11 @@ export class AgentService {
|
||||
}
|
||||
|
||||
async saveMetadata(metadata: Record<string, SessionMetadata>): Promise<void> {
|
||||
await fs.writeFile(this.metadataFile, JSON.stringify(metadata, null, 2), "utf-8");
|
||||
await fs.writeFile(
|
||||
this.metadataFile,
|
||||
JSON.stringify(metadata, null, 2),
|
||||
"utf-8"
|
||||
);
|
||||
}
|
||||
|
||||
async updateSessionTimestamp(sessionId: string): Promise<void> {
|
||||
@@ -418,7 +428,8 @@ export class AgentService {
|
||||
}
|
||||
|
||||
return sessions.sort(
|
||||
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||
(a, b) =>
|
||||
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -505,7 +516,10 @@ export class AgentService {
|
||||
return true;
|
||||
}
|
||||
|
||||
private emitAgentEvent(sessionId: string, data: Record<string, unknown>): void {
|
||||
private emitAgentEvent(
|
||||
sessionId: string,
|
||||
data: Record<string, unknown>
|
||||
): void {
|
||||
this.events.emit("agent:stream", { sessionId, ...data });
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"libs/*"
|
||||
],
|
||||
"scripts": {
|
||||
"postinstall": "node -e \"const fs=require('fs');if(process.platform==='darwin'){['darwin-arm64','darwin-x64'].forEach(a=>{const p='node_modules/node-pty/prebuilds/'+a+'/spawn-helper';if(fs.existsSync(p))fs.chmodSync(p,0o755)})}\"",
|
||||
"dev": "npm run dev --workspace=apps/app",
|
||||
"dev:web": "npm run dev:web --workspace=apps/app",
|
||||
"dev:electron": "npm run dev:electron --workspace=apps/app",
|
||||
|
||||
Reference in New Issue
Block a user