@itz4blitz/agentful 1.0.0 → 1.0.1
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/bin/cli.js +274 -4
- package/lib/server/executor.js +52 -1
- package/lib/server/index.js +37 -12
- package/package.json +1 -1
- package/version.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -996,12 +996,272 @@ async function remote(args) {
|
|
|
996
996
|
}
|
|
997
997
|
}
|
|
998
998
|
|
|
999
|
+
/**
|
|
1000
|
+
* Get PID file path
|
|
1001
|
+
* @returns {string} Path to PID file
|
|
1002
|
+
*/
|
|
1003
|
+
function getPidFilePath() {
|
|
1004
|
+
return path.join(process.cwd(), '.agentful', 'server.pid');
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Start server in daemon mode
|
|
1009
|
+
* @param {string[]} args - Original args
|
|
1010
|
+
* @param {Object} config - Server configuration
|
|
1011
|
+
*/
|
|
1012
|
+
async function startDaemon(args, config) {
|
|
1013
|
+
const { spawn } = await import('child_process');
|
|
1014
|
+
|
|
1015
|
+
// Check if daemon is already running
|
|
1016
|
+
const pidFile = getPidFilePath();
|
|
1017
|
+
if (fs.existsSync(pidFile)) {
|
|
1018
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
1019
|
+
|
|
1020
|
+
// Check if process is still running
|
|
1021
|
+
try {
|
|
1022
|
+
process.kill(pid, 0); // Signal 0 checks if process exists
|
|
1023
|
+
log(colors.yellow, 'Server is already running');
|
|
1024
|
+
log(colors.dim, `PID: ${pid}`);
|
|
1025
|
+
console.log('');
|
|
1026
|
+
log(colors.dim, 'To stop: agentful serve --stop');
|
|
1027
|
+
log(colors.dim, 'To check status: agentful serve --status');
|
|
1028
|
+
process.exit(1);
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
// Process doesn't exist, clean up stale PID file
|
|
1031
|
+
fs.unlinkSync(pidFile);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Ensure .agentful directory exists
|
|
1036
|
+
const agentfulDir = path.join(process.cwd(), '.agentful');
|
|
1037
|
+
if (!fs.existsSync(agentfulDir)) {
|
|
1038
|
+
fs.mkdirSync(agentfulDir, { recursive: true });
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Prepare args for child process (remove --daemon flag)
|
|
1042
|
+
const childArgs = args.filter(arg => !arg.startsWith('--daemon') && arg !== '-d');
|
|
1043
|
+
|
|
1044
|
+
// Spawn detached child process
|
|
1045
|
+
const child = spawn(
|
|
1046
|
+
process.argv[0], // node executable
|
|
1047
|
+
[process.argv[1], 'serve', ...childArgs], // script path and args
|
|
1048
|
+
{
|
|
1049
|
+
detached: true,
|
|
1050
|
+
stdio: 'ignore',
|
|
1051
|
+
cwd: process.cwd(),
|
|
1052
|
+
env: {
|
|
1053
|
+
...process.env,
|
|
1054
|
+
AGENTFUL_DAEMON: '1' // Flag to indicate we're running as daemon
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
// Write PID file
|
|
1060
|
+
fs.writeFileSync(pidFile, child.pid.toString(), 'utf-8');
|
|
1061
|
+
|
|
1062
|
+
// Unref to allow parent to exit
|
|
1063
|
+
child.unref();
|
|
1064
|
+
|
|
1065
|
+
// Show success message
|
|
1066
|
+
log(colors.green, `Server started in background (PID: ${child.pid})`);
|
|
1067
|
+
console.log('');
|
|
1068
|
+
log(colors.dim, `PID file: ${pidFile}`);
|
|
1069
|
+
log(colors.dim, `Port: ${config.port}`);
|
|
1070
|
+
log(colors.dim, `Auth: ${config.auth}`);
|
|
1071
|
+
console.log('');
|
|
1072
|
+
log(colors.dim, 'Commands:');
|
|
1073
|
+
log(colors.dim, ' agentful serve --stop Stop the daemon');
|
|
1074
|
+
log(colors.dim, ' agentful serve --status Check daemon status');
|
|
1075
|
+
console.log('');
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Stop daemon server
|
|
1080
|
+
*/
|
|
1081
|
+
async function stopDaemon() {
|
|
1082
|
+
const pidFile = getPidFilePath();
|
|
1083
|
+
|
|
1084
|
+
if (!fs.existsSync(pidFile)) {
|
|
1085
|
+
log(colors.yellow, 'No daemon server running');
|
|
1086
|
+
log(colors.dim, 'PID file not found');
|
|
1087
|
+
process.exit(1);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
1091
|
+
|
|
1092
|
+
// Try to kill the process
|
|
1093
|
+
try {
|
|
1094
|
+
process.kill(pid, 'SIGTERM');
|
|
1095
|
+
|
|
1096
|
+
// Wait a moment for graceful shutdown
|
|
1097
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1098
|
+
|
|
1099
|
+
// Check if process is still running
|
|
1100
|
+
try {
|
|
1101
|
+
process.kill(pid, 0);
|
|
1102
|
+
// Still running, force kill
|
|
1103
|
+
log(colors.yellow, 'Graceful shutdown failed, forcing...');
|
|
1104
|
+
process.kill(pid, 'SIGKILL');
|
|
1105
|
+
} catch {
|
|
1106
|
+
// Process stopped successfully
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Remove PID file
|
|
1110
|
+
fs.unlinkSync(pidFile);
|
|
1111
|
+
|
|
1112
|
+
log(colors.green, `Server stopped (PID: ${pid})`);
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
if (error.code === 'ESRCH') {
|
|
1115
|
+
// Process doesn't exist
|
|
1116
|
+
log(colors.yellow, 'Server process not found (stale PID file)');
|
|
1117
|
+
fs.unlinkSync(pidFile);
|
|
1118
|
+
} else if (error.code === 'EPERM') {
|
|
1119
|
+
log(colors.red, `Permission denied to kill process ${pid}`);
|
|
1120
|
+
log(colors.dim, 'Try: sudo agentful serve --stop');
|
|
1121
|
+
process.exit(1);
|
|
1122
|
+
} else {
|
|
1123
|
+
log(colors.red, `Failed to stop server: ${error.message}`);
|
|
1124
|
+
process.exit(1);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Check daemon server status
|
|
1131
|
+
*/
|
|
1132
|
+
async function checkDaemonStatus() {
|
|
1133
|
+
const pidFile = getPidFilePath();
|
|
1134
|
+
|
|
1135
|
+
if (!fs.existsSync(pidFile)) {
|
|
1136
|
+
log(colors.yellow, 'No daemon server running');
|
|
1137
|
+
log(colors.dim, 'PID file not found');
|
|
1138
|
+
console.log('');
|
|
1139
|
+
log(colors.dim, 'Start daemon: agentful serve --daemon');
|
|
1140
|
+
process.exit(1);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
1144
|
+
|
|
1145
|
+
// Check if process is running
|
|
1146
|
+
try {
|
|
1147
|
+
process.kill(pid, 0); // Signal 0 just checks if process exists
|
|
1148
|
+
|
|
1149
|
+
log(colors.green, 'Server is running');
|
|
1150
|
+
console.log('');
|
|
1151
|
+
log(colors.dim, `PID: ${pid}`);
|
|
1152
|
+
log(colors.dim, `PID file: ${pidFile}`);
|
|
1153
|
+
|
|
1154
|
+
// Try to get more info from /proc (Linux/macOS)
|
|
1155
|
+
try {
|
|
1156
|
+
const { execSync } = await import('child_process');
|
|
1157
|
+
const psOutput = execSync(`ps -p ${pid} -o comm,etime,rss`, { encoding: 'utf-8' });
|
|
1158
|
+
const lines = psOutput.trim().split('\n');
|
|
1159
|
+
if (lines.length > 1) {
|
|
1160
|
+
const [cmd, etime, rss] = lines[1].trim().split(/\s+/);
|
|
1161
|
+
console.log('');
|
|
1162
|
+
log(colors.dim, `Uptime: ${etime}`);
|
|
1163
|
+
log(colors.dim, `Memory: ${Math.round(parseInt(rss) / 1024)} MB`);
|
|
1164
|
+
}
|
|
1165
|
+
} catch {
|
|
1166
|
+
// ps command failed, skip detailed info
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
console.log('');
|
|
1170
|
+
log(colors.dim, 'Commands:');
|
|
1171
|
+
log(colors.dim, ' agentful serve --stop Stop the daemon');
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
if (error.code === 'ESRCH') {
|
|
1174
|
+
log(colors.yellow, 'Server not running (stale PID file)');
|
|
1175
|
+
log(colors.dim, `PID file exists but process ${pid} not found`);
|
|
1176
|
+
console.log('');
|
|
1177
|
+
log(colors.dim, 'Clean up: rm .agentful/server.pid');
|
|
1178
|
+
process.exit(1);
|
|
1179
|
+
} else {
|
|
1180
|
+
log(colors.red, `Failed to check status: ${error.message}`);
|
|
1181
|
+
process.exit(1);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
999
1186
|
/**
|
|
1000
1187
|
* Serve command - Start remote execution server
|
|
1001
1188
|
*/
|
|
1002
1189
|
async function serve(args) {
|
|
1003
1190
|
const flags = parseFlags(args);
|
|
1004
1191
|
|
|
1192
|
+
// Handle --stop subcommand
|
|
1193
|
+
if (flags.stop) {
|
|
1194
|
+
return await stopDaemon();
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Handle --status subcommand
|
|
1198
|
+
if (flags.status) {
|
|
1199
|
+
return await checkDaemonStatus();
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// Handle --help flag first
|
|
1203
|
+
if (flags.help || flags.h) {
|
|
1204
|
+
showBanner();
|
|
1205
|
+
log(colors.bright, 'Agentful Remote Execution Server');
|
|
1206
|
+
console.log('');
|
|
1207
|
+
log(colors.dim, 'Start a secure HTTP server for remote agent execution.');
|
|
1208
|
+
console.log('');
|
|
1209
|
+
log(colors.bright, 'USAGE:');
|
|
1210
|
+
console.log(` ${colors.green}agentful serve${colors.reset} ${colors.dim}[options]${colors.reset}`);
|
|
1211
|
+
console.log('');
|
|
1212
|
+
log(colors.bright, 'AUTHENTICATION MODES:');
|
|
1213
|
+
console.log(` ${colors.cyan}--auth=tailscale${colors.reset} ${colors.dim}(default) Tailscale network only${colors.reset}`);
|
|
1214
|
+
console.log(` ${colors.cyan}--auth=hmac${colors.reset} ${colors.dim}HMAC signature authentication (requires --secret)${colors.reset}`);
|
|
1215
|
+
console.log(` ${colors.cyan}--auth=none${colors.reset} ${colors.dim}No authentication (binds to all interfaces, use with SSH tunnel)${colors.reset}`);
|
|
1216
|
+
console.log('');
|
|
1217
|
+
log(colors.bright, 'OPTIONS:');
|
|
1218
|
+
console.log(` ${colors.yellow}--port=<number>${colors.reset} ${colors.dim}Server port (default: 3000)${colors.reset}`);
|
|
1219
|
+
console.log(` ${colors.yellow}--secret=<key>${colors.reset} ${colors.dim}HMAC secret key (required for --auth=hmac)${colors.reset}`);
|
|
1220
|
+
console.log(` ${colors.yellow}--https${colors.reset} ${colors.dim}Enable HTTPS (requires --cert and --key)${colors.reset}`);
|
|
1221
|
+
console.log(` ${colors.yellow}--cert=<path>${colors.reset} ${colors.dim}SSL certificate file path${colors.reset}`);
|
|
1222
|
+
console.log(` ${colors.yellow}--key=<path>${colors.reset} ${colors.dim}SSL private key file path${colors.reset}`);
|
|
1223
|
+
console.log(` ${colors.yellow}--daemon, -d${colors.reset} ${colors.dim}Run server in background (daemon mode)${colors.reset}`);
|
|
1224
|
+
console.log(` ${colors.yellow}--stop${colors.reset} ${colors.dim}Stop background server${colors.reset}`);
|
|
1225
|
+
console.log(` ${colors.yellow}--status${colors.reset} ${colors.dim}Check background server status${colors.reset}`);
|
|
1226
|
+
console.log(` ${colors.yellow}--help, -h${colors.reset} ${colors.dim}Show this help message${colors.reset}`);
|
|
1227
|
+
console.log('');
|
|
1228
|
+
log(colors.bright, 'EXAMPLES:');
|
|
1229
|
+
console.log('');
|
|
1230
|
+
log(colors.dim, ' # Start server with Tailscale auth (default)');
|
|
1231
|
+
console.log(` ${colors.green}agentful serve${colors.reset}`);
|
|
1232
|
+
console.log('');
|
|
1233
|
+
log(colors.dim, ' # Start server with HMAC authentication');
|
|
1234
|
+
console.log(` ${colors.green}agentful serve --auth=hmac --secret=your-secret-key${colors.reset}`);
|
|
1235
|
+
console.log('');
|
|
1236
|
+
log(colors.dim, ' # Start HTTPS server with HMAC auth');
|
|
1237
|
+
console.log(` ${colors.green}agentful serve --auth=hmac --secret=key --https --cert=cert.pem --key=key.pem${colors.reset}`);
|
|
1238
|
+
console.log('');
|
|
1239
|
+
log(colors.dim, ' # Start server without auth (public access, use SSH tunnel)');
|
|
1240
|
+
console.log(` ${colors.green}agentful serve --auth=none --port=3737${colors.reset}`);
|
|
1241
|
+
console.log('');
|
|
1242
|
+
log(colors.dim, ' # Start server in background (daemon mode)');
|
|
1243
|
+
console.log(` ${colors.green}agentful serve --daemon${colors.reset}`);
|
|
1244
|
+
console.log('');
|
|
1245
|
+
log(colors.dim, ' # Check daemon status');
|
|
1246
|
+
console.log(` ${colors.green}agentful serve --status${colors.reset}`);
|
|
1247
|
+
console.log('');
|
|
1248
|
+
log(colors.dim, ' # Stop daemon');
|
|
1249
|
+
console.log(` ${colors.green}agentful serve --stop${colors.reset}`);
|
|
1250
|
+
console.log('');
|
|
1251
|
+
log(colors.dim, ' # Generate HMAC secret');
|
|
1252
|
+
console.log(` ${colors.green}openssl rand -hex 32${colors.reset}`);
|
|
1253
|
+
console.log('');
|
|
1254
|
+
log(colors.dim, ' # Generate self-signed certificate');
|
|
1255
|
+
console.log(` ${colors.green}openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes${colors.reset}`);
|
|
1256
|
+
console.log('');
|
|
1257
|
+
log(colors.bright, 'SECURITY NOTES:');
|
|
1258
|
+
console.log(` ${colors.yellow}Tailscale mode:${colors.reset} Binds to 0.0.0.0, relies on Tailscale network isolation`);
|
|
1259
|
+
console.log(` ${colors.yellow}HMAC mode:${colors.reset} Binds to 0.0.0.0, uses cryptographic signatures (recommended for public networks)`);
|
|
1260
|
+
console.log(` ${colors.yellow}None mode:${colors.reset} Binds to 0.0.0.0, no authentication (use SSH tunnel: ssh -L 3000:localhost:3000 user@host)`);
|
|
1261
|
+
console.log('');
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1005
1265
|
// Parse configuration
|
|
1006
1266
|
const config = {
|
|
1007
1267
|
auth: flags.auth || 'tailscale',
|
|
@@ -1013,6 +1273,9 @@ async function serve(args) {
|
|
|
1013
1273
|
projectRoot: process.cwd(),
|
|
1014
1274
|
};
|
|
1015
1275
|
|
|
1276
|
+
// Check if --daemon flag is set
|
|
1277
|
+
const isDaemon = flags.daemon || flags.d;
|
|
1278
|
+
|
|
1016
1279
|
// Validate auth mode
|
|
1017
1280
|
const validAuthModes = ['tailscale', 'hmac', 'none'];
|
|
1018
1281
|
if (!validAuthModes.includes(config.auth)) {
|
|
@@ -1027,8 +1290,15 @@ async function serve(args) {
|
|
|
1027
1290
|
process.exit(1);
|
|
1028
1291
|
}
|
|
1029
1292
|
|
|
1030
|
-
//
|
|
1031
|
-
|
|
1293
|
+
// If daemon mode, fork the process
|
|
1294
|
+
if (isDaemon) {
|
|
1295
|
+
return await startDaemon(args, config);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Show configuration (skip banner if running as daemon child)
|
|
1299
|
+
if (!process.env.AGENTFUL_DAEMON) {
|
|
1300
|
+
showBanner();
|
|
1301
|
+
}
|
|
1032
1302
|
log(colors.bright, 'Starting Agentful Server');
|
|
1033
1303
|
console.log('');
|
|
1034
1304
|
log(colors.dim, `Authentication: ${config.auth}`);
|
|
@@ -1036,8 +1306,8 @@ async function serve(args) {
|
|
|
1036
1306
|
log(colors.dim, `HTTPS: ${config.https ? 'enabled' : 'disabled'}`);
|
|
1037
1307
|
|
|
1038
1308
|
if (config.auth === 'none') {
|
|
1039
|
-
log(colors.yellow, 'Warning: Server
|
|
1040
|
-
log(colors.dim, 'Use SSH tunnel for remote access: ssh -L 3000:localhost:3000 user@host');
|
|
1309
|
+
log(colors.yellow, 'Warning: Server running with no authentication (binds to all interfaces)');
|
|
1310
|
+
log(colors.dim, 'Recommended: Use SSH tunnel for remote access: ssh -L 3000:localhost:3000 user@host');
|
|
1041
1311
|
}
|
|
1042
1312
|
|
|
1043
1313
|
if (config.auth === 'hmac' && !config.secret) {
|
package/lib/server/executor.js
CHANGED
|
@@ -133,13 +133,15 @@ ${agent.instructions}
|
|
|
133
133
|
* @param {string} [options.projectRoot] - Project root directory
|
|
134
134
|
* @param {number} [options.timeout] - Execution timeout in ms
|
|
135
135
|
* @param {Object} [options.env] - Additional environment variables
|
|
136
|
-
* @
|
|
136
|
+
* @param {boolean} [options.async=false] - If true, return immediately with executionId
|
|
137
|
+
* @returns {Promise<Object>} Execution result (or just executionId if async=true)
|
|
137
138
|
*/
|
|
138
139
|
export async function executeAgent(agentName, task, options = {}) {
|
|
139
140
|
const {
|
|
140
141
|
projectRoot = process.cwd(),
|
|
141
142
|
timeout = 10 * 60 * 1000, // 10 minutes default
|
|
142
143
|
env = {},
|
|
144
|
+
async = false, // New option for non-blocking execution
|
|
143
145
|
} = options;
|
|
144
146
|
|
|
145
147
|
// Validate agent name to prevent path traversal
|
|
@@ -176,6 +178,55 @@ export async function executeAgent(agentName, task, options = {}) {
|
|
|
176
178
|
|
|
177
179
|
executions.set(executionId, execution);
|
|
178
180
|
|
|
181
|
+
// If async mode, start execution in background and return immediately
|
|
182
|
+
if (async) {
|
|
183
|
+
// Start execution in background (don't await)
|
|
184
|
+
runAgentExecution(executionId, agentName, task, {
|
|
185
|
+
projectRoot,
|
|
186
|
+
timeout,
|
|
187
|
+
filteredEnv,
|
|
188
|
+
}).catch((error) => {
|
|
189
|
+
// Update execution with error if background execution fails
|
|
190
|
+
const exec = executions.get(executionId);
|
|
191
|
+
if (exec) {
|
|
192
|
+
exec.state = ExecutionState.FAILED;
|
|
193
|
+
exec.endTime = Date.now();
|
|
194
|
+
exec.error = error.message;
|
|
195
|
+
exec.exitCode = -1;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
executionId,
|
|
201
|
+
state: ExecutionState.PENDING,
|
|
202
|
+
message: 'Execution started in background',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Synchronous mode - wait for completion and return full result
|
|
207
|
+
return runAgentExecution(executionId, agentName, task, {
|
|
208
|
+
projectRoot,
|
|
209
|
+
timeout,
|
|
210
|
+
filteredEnv,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Internal function to run agent execution
|
|
216
|
+
* @param {string} executionId - Execution ID
|
|
217
|
+
* @param {string} agentName - Agent name
|
|
218
|
+
* @param {string} task - Task description
|
|
219
|
+
* @param {Object} options - Execution options
|
|
220
|
+
* @returns {Promise<Object>} Execution result
|
|
221
|
+
*/
|
|
222
|
+
async function runAgentExecution(executionId, agentName, task, options) {
|
|
223
|
+
const { projectRoot, timeout, filteredEnv } = options;
|
|
224
|
+
const execution = executions.get(executionId);
|
|
225
|
+
|
|
226
|
+
if (!execution) {
|
|
227
|
+
throw new Error(`Execution ${executionId} not found`);
|
|
228
|
+
}
|
|
229
|
+
|
|
179
230
|
try {
|
|
180
231
|
// Load agent definition
|
|
181
232
|
const agent = await loadAgentDefinition(agentName, projectRoot);
|
package/lib/server/index.js
CHANGED
|
@@ -243,24 +243,28 @@ export function createServer(config = {}) {
|
|
|
243
243
|
);
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
//
|
|
247
|
-
|
|
246
|
+
// Log agent execution request
|
|
247
|
+
console.log(`[${new Date().toISOString()}] Executing agent: ${body.agent}`);
|
|
248
|
+
console.log(`[${new Date().toISOString()}] Task: ${body.task}`);
|
|
249
|
+
|
|
250
|
+
// Start agent execution in background (async mode)
|
|
251
|
+
const executionTimeout = body.timeout || 10 * 60 * 1000; // Default 10 min for execution
|
|
252
|
+
|
|
253
|
+
const result = await executeAgent(body.agent, body.task, {
|
|
248
254
|
projectRoot,
|
|
249
|
-
timeout:
|
|
255
|
+
timeout: executionTimeout,
|
|
250
256
|
env: body.env,
|
|
257
|
+
async: true, // Return immediately with executionId
|
|
251
258
|
});
|
|
252
259
|
|
|
253
|
-
|
|
254
|
-
result.then(() => {}).catch(() => {}); // Prevent unhandled rejection
|
|
255
|
-
|
|
256
|
-
const executionId = (await result).executionId;
|
|
260
|
+
console.log(`[${new Date().toISOString()}] Execution started: ${result.executionId}`);
|
|
257
261
|
|
|
258
262
|
res.writeHead(202, { 'Content-Type': 'application/json' });
|
|
259
263
|
res.end(
|
|
260
264
|
JSON.stringify({
|
|
261
|
-
executionId,
|
|
265
|
+
executionId: result.executionId,
|
|
262
266
|
message: 'Agent execution started',
|
|
263
|
-
statusUrl: `/status/${executionId}`,
|
|
267
|
+
statusUrl: `/status/${result.executionId}`,
|
|
264
268
|
})
|
|
265
269
|
);
|
|
266
270
|
} catch (error) {
|
|
@@ -315,6 +319,13 @@ export function createServer(config = {}) {
|
|
|
315
319
|
|
|
316
320
|
// Request handler
|
|
317
321
|
const requestHandler = (req, res) => {
|
|
322
|
+
const startTime = Date.now();
|
|
323
|
+
const clientIP = req.socket.remoteAddress;
|
|
324
|
+
|
|
325
|
+
// Log incoming request
|
|
326
|
+
const timestamp = new Date().toISOString();
|
|
327
|
+
console.log(`[${timestamp}] ${req.method} ${req.url} from ${clientIP}`);
|
|
328
|
+
|
|
318
329
|
// Add CORS headers (restricted by default)
|
|
319
330
|
if (corsOrigin) {
|
|
320
331
|
res.setHeader('Access-Control-Allow-Origin', corsOrigin);
|
|
@@ -328,14 +339,27 @@ export function createServer(config = {}) {
|
|
|
328
339
|
// Handle preflight
|
|
329
340
|
if (req.method === 'OPTIONS') {
|
|
330
341
|
res.writeHead(204);
|
|
342
|
+
const duration = Date.now() - startTime;
|
|
343
|
+
console.log(`[${new Date().toISOString()}] Response sent: 204 (${duration}ms)`);
|
|
331
344
|
return res.end();
|
|
332
345
|
}
|
|
333
346
|
|
|
334
347
|
// Apply rate limiting
|
|
335
348
|
if (!checkRateLimit(req, res)) {
|
|
349
|
+
const duration = Date.now() - startTime;
|
|
350
|
+
console.log(`[${new Date().toISOString()}] Response sent: 429 Rate Limited (${duration}ms)`);
|
|
336
351
|
return; // Rate limit exceeded, response already sent
|
|
337
352
|
}
|
|
338
353
|
|
|
354
|
+
// Intercept res.end to log responses
|
|
355
|
+
const originalEnd = res.end;
|
|
356
|
+
res.end = function(...args) {
|
|
357
|
+
const duration = Date.now() - startTime;
|
|
358
|
+
const statusCode = res.statusCode;
|
|
359
|
+
console.log(`[${new Date().toISOString()}] Response sent: ${statusCode} (${duration}ms)`);
|
|
360
|
+
originalEnd.apply(res, args);
|
|
361
|
+
};
|
|
362
|
+
|
|
339
363
|
// Capture raw body (needed for HMAC verification)
|
|
340
364
|
captureRawBody(req, res, () => {
|
|
341
365
|
// Apply authentication (except for /health)
|
|
@@ -384,8 +408,9 @@ export function createServer(config = {}) {
|
|
|
384
408
|
server = http.createServer(requestHandler);
|
|
385
409
|
}
|
|
386
410
|
|
|
387
|
-
//
|
|
388
|
-
|
|
411
|
+
// Always bind to all interfaces (0.0.0.0)
|
|
412
|
+
// Security is enforced through authentication middleware, not binding address
|
|
413
|
+
const host = '0.0.0.0';
|
|
389
414
|
|
|
390
415
|
return {
|
|
391
416
|
start: () => {
|
|
@@ -400,7 +425,7 @@ export function createServer(config = {}) {
|
|
|
400
425
|
console.log(`Authentication mode: ${auth}`);
|
|
401
426
|
|
|
402
427
|
if (auth === 'none') {
|
|
403
|
-
console.log('
|
|
428
|
+
console.log('Warning: No authentication enabled - use SSH tunnel for secure remote access');
|
|
404
429
|
}
|
|
405
430
|
|
|
406
431
|
// Start periodic cleanup
|
package/package.json
CHANGED
package/version.json
CHANGED