indieclaw-agent 2.4.0 → 2.4.2

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.
Files changed (2) hide show
  1. package/index.js +298 -149
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { WebSocketServer } = require('ws');
4
+ const WebSocket = require('ws');
4
5
  const { execSync, exec } = require('child_process');
5
6
  const fs = require('fs');
6
7
  const path = require('path');
@@ -8,6 +9,7 @@ const os = require('os');
8
9
  const crypto = require('crypto');
9
10
  const http = require('http');
10
11
  const https = require('https');
12
+ const net = require('net');
11
13
 
12
14
  // --- Version from package.json ---
13
15
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
@@ -89,7 +91,7 @@ if (TLS_ENABLED) {
89
91
  }
90
92
 
91
93
  const terminals = new Map(); // id -> pty process
92
- const activeChats = new Map(); // id -> http.ClientRequest
94
+ const activeChats = new Map(); // chatId -> { runId, _ws }
93
95
  const activeSearches = new Map(); // ws -> child_process (one search per connection)
94
96
 
95
97
  // --- Deep Link & QR Code ---
@@ -143,77 +145,275 @@ try {
143
145
  // qrcode-terminal not installed, skip QR display
144
146
  }
145
147
 
146
- // --- OpenClaw Detection & Config ---
147
- const OPENCLAW_CONFIG_PATHS = [
148
- path.join(os.homedir(), '.openclaw', 'openclaw.json'),
149
- path.join(os.homedir(), '.openclaw', 'openclaw.json5'),
150
- ];
148
+ // --- OpenClaw Detection, Config & Gateway Client ---
149
+
150
+ // Search multiple possible config locations (systemd may resolve homedir differently)
151
+ function getConfigPaths() {
152
+ const paths = [];
153
+ const home = os.homedir();
154
+ paths.push(path.join(home, '.openclaw', 'openclaw.json'));
155
+ paths.push(path.join(home, '.openclaw', 'openclaw.json5'));
156
+ // Also check /root explicitly (systemd services may not resolve ~ to /root)
157
+ if (home !== '/root') {
158
+ paths.push('/root/.openclaw/openclaw.json');
159
+ paths.push('/root/.openclaw/openclaw.json5');
160
+ }
161
+ return paths;
162
+ }
163
+
164
+ const OPENCLAW_CONFIG_PATHS = getConfigPaths();
165
+
166
+ function stripJsonComments(raw) {
167
+ // Remove comments WITHOUT corrupting URLs inside strings
168
+ // Walk char-by-char, track if we're inside a string
169
+ let result = '';
170
+ let inString = false;
171
+ let escaped = false;
172
+ for (let i = 0; i < raw.length; i++) {
173
+ const ch = raw[i];
174
+ if (inString) {
175
+ result += ch;
176
+ if (escaped) { escaped = false; continue; }
177
+ if (ch === '\\') { escaped = true; continue; }
178
+ if (ch === '"') { inString = false; }
179
+ continue;
180
+ }
181
+ // Not in string
182
+ if (ch === '"') { inString = true; result += ch; continue; }
183
+ // Line comment
184
+ if (ch === '/' && raw[i + 1] === '/') {
185
+ // Skip to end of line
186
+ while (i < raw.length && raw[i] !== '\n') i++;
187
+ result += '\n';
188
+ continue;
189
+ }
190
+ // Block comment
191
+ if (ch === '/' && raw[i + 1] === '*') {
192
+ i += 2;
193
+ while (i < raw.length && !(raw[i] === '*' && raw[i + 1] === '/')) i++;
194
+ i++; // skip closing /
195
+ continue;
196
+ }
197
+ result += ch;
198
+ }
199
+ return result;
200
+ }
151
201
 
152
- // Read OpenClaw gateway config (port + auth token) from local config file
153
202
  function readOpenClawConfig() {
154
- for (const configPath of OPENCLAW_CONFIG_PATHS) {
203
+ for (const cfgPath of OPENCLAW_CONFIG_PATHS) {
155
204
  try {
156
- if (!fs.existsSync(configPath)) continue;
157
- let raw = fs.readFileSync(configPath, 'utf-8');
158
- // Strip JSON5 comments (// and /* */) for safe JSON.parse
159
- raw = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
160
- // Strip trailing commas before } or ]
205
+ const exists = fs.existsSync(cfgPath);
206
+ console.log(` [OpenClaw] Config ${cfgPath}: ${exists ? 'EXISTS' : 'not found'}`);
207
+ if (!exists) continue;
208
+ let raw = fs.readFileSync(cfgPath, 'utf-8');
209
+ // Strip comments safely (preserves URLs in strings)
210
+ raw = stripJsonComments(raw);
211
+ // Remove trailing commas
161
212
  raw = raw.replace(/,\s*([\]}])/g, '$1');
162
213
  const config = JSON.parse(raw);
163
214
  const gw = config.gateway || {};
164
- return {
215
+ const result = {
165
216
  port: gw.port || 18789,
166
217
  token: gw.auth?.token || gw.auth?.password || null,
167
218
  host: gw.bind || '127.0.0.1',
168
219
  };
169
- } catch {}
220
+ console.log(` [OpenClaw] Config parsed: port=${result.port}, host=${result.host}, token=${result.token ? 'SET' : 'NONE'}`);
221
+ return result;
222
+ } catch (err) {
223
+ console.log(` [OpenClaw] Config parse error for ${cfgPath}: ${err.message}`);
224
+ }
170
225
  }
171
- return { port: 18789, token: null, host: '127.0.0.1' };
226
+ // Fallback: check env var
227
+ console.log(' [OpenClaw] No config file found, using defaults (port 18789)');
228
+ return {
229
+ port: parseInt(process.env.OPENCLAW_GATEWAY_PORT || '18789', 10),
230
+ token: process.env.OPENCLAW_GATEWAY_TOKEN || null,
231
+ host: '127.0.0.1',
232
+ };
172
233
  }
173
234
 
174
- // Cache the config on startup
175
235
  let openClawConfig = readOpenClawConfig();
176
236
 
177
- function detectOpenClaw() {
237
+ // TCP port check — fast and reliable, no auth needed
238
+ function isPortListening(port, host = '127.0.0.1') {
178
239
  return new Promise((resolve) => {
179
- // Refresh config each detection
240
+ console.log(` [OpenClaw] TCP probe ${host}:${port}...`);
241
+ const sock = net.createConnection({ port, host }, () => {
242
+ console.log(` [OpenClaw] TCP probe ${host}:${port} → OPEN`);
243
+ sock.destroy();
244
+ resolve(true);
245
+ });
246
+ sock.on('error', (err) => {
247
+ console.log(` [OpenClaw] TCP probe ${host}:${port} → ERROR: ${err.message}`);
248
+ resolve(false);
249
+ });
250
+ sock.setTimeout(2000, () => {
251
+ console.log(` [OpenClaw] TCP probe ${host}:${port} → TIMEOUT`);
252
+ sock.destroy();
253
+ resolve(false);
254
+ });
255
+ });
256
+ }
257
+
258
+ async function detectOpenClaw() {
259
+ console.log(' [OpenClaw] Running detection...');
260
+ openClawConfig = readOpenClawConfig();
261
+
262
+ // Always probe 127.0.0.1 — the gateway listens on loopback
263
+ const port = openClawConfig.port || 18789;
264
+ const listening = await isPortListening(port, '127.0.0.1');
265
+ if (listening) {
266
+ console.log(` [OpenClaw] Detection result: AVAILABLE (port ${port})`);
267
+ return { available: true, models: ['openclaw'], port };
268
+ }
269
+
270
+ // Fallback: check if config file exists (installed but not running)
271
+ const installed = OPENCLAW_CONFIG_PATHS.some((p) => fs.existsSync(p));
272
+ if (installed) {
273
+ console.log(` [OpenClaw] Detection result: INSTALLED but gateway not running`);
274
+ return { available: false, models: [], port, installed: true };
275
+ }
276
+ console.log(' [OpenClaw] Detection result: NOT FOUND');
277
+ return { available: false, models: [], port: null };
278
+ }
279
+
280
+ // --- OpenClaw Gateway WebSocket Client ---
281
+ let ocGateway = null;
282
+ let ocReady = false;
283
+ const ocPending = new Map();
284
+ const ocChatCallbacks = new Map(); // runId -> { ws, chatId }
285
+
286
+ function connectOcGateway() {
287
+ return new Promise((resolve, reject) => {
180
288
  openClawConfig = readOpenClawConfig();
289
+ const url = `ws://127.0.0.1:${openClawConfig.port}`;
290
+ console.log(` [OpenClaw] Connecting to gateway: ${url}`);
291
+
292
+ if (ocGateway) { try { ocGateway.close(); } catch {} }
293
+ ocGateway = null;
294
+ ocReady = false;
295
+
296
+ const ws = new WebSocket(url);
297
+ let connectReqId = null;
298
+ const timeout = setTimeout(() => { console.log(' [OpenClaw] Gateway connection TIMEOUT'); ws.close(); reject(new Error('Gateway timeout')); }, 10000);
299
+
300
+ ws.on('message', (data) => {
301
+ let msg;
302
+ try { msg = JSON.parse(data.toString()); } catch { return; }
303
+
304
+ console.log(` [OpenClaw] Gateway msg: type=${msg.type}, event=${msg.event || ''}, id=${msg.id || ''}, ok=${msg.ok}`);
305
+
306
+ // Step 1: Gateway sends connect.challenge
307
+ if (msg.type === 'event' && msg.event === 'connect.challenge') {
308
+ connectReqId = crypto.randomUUID();
309
+ const params = {
310
+ minProtocol: 3, maxProtocol: 3,
311
+ client: { id: 'indieclaw-agent', version: VERSION, platform: os.platform(), mode: 'operator' },
312
+ role: 'operator',
313
+ scopes: ['operator.read', 'operator.write'],
314
+ };
315
+ if (openClawConfig.token) params.auth = { token: openClawConfig.token };
316
+ ws.send(JSON.stringify({ type: 'req', id: connectReqId, method: 'connect', params }));
317
+ return;
318
+ }
181
319
 
182
- // Method 1: Use `openclaw gateway status` CLI (most reliable)
183
- exec('openclaw gateway status 2>&1', { timeout: 5000 }, (err, stdout) => {
184
- const output = (stdout || '').toLowerCase();
185
- if (!err && (output.includes('running') || output.includes('active'))) {
186
- return resolve({ available: true, models: ['openclaw'], port: openClawConfig.port });
320
+ // Step 2: Handle hello-ok response
321
+ if (msg.type === 'res' && msg.id === connectReqId) {
322
+ clearTimeout(timeout);
323
+ if (msg.ok) {
324
+ ocGateway = ws;
325
+ ocReady = true;
326
+ resolve(ws);
327
+ } else {
328
+ ws.close();
329
+ reject(new Error(msg.error?.message || 'Gateway auth failed'));
330
+ }
331
+ return;
187
332
  }
188
333
 
189
- // Method 2: Check if openclaw-gateway process is running
190
- exec('pgrep -f "openclaw.gateway\\|openclaw-gateway" 2>/dev/null', { timeout: 2000 }, (err3, pid) => {
191
- if (!err3 && pid?.trim()) {
192
- return resolve({ available: true, models: ['openclaw'], port: openClawConfig.port });
334
+ // Handle other req/res
335
+ if (msg.type === 'res' && msg.id) {
336
+ const pending = ocPending.get(msg.id);
337
+ if (pending) {
338
+ ocPending.delete(msg.id);
339
+ clearTimeout(pending.timeout);
340
+ if (msg.ok) pending.resolve(msg.payload);
341
+ else pending.reject(new Error(msg.error?.message || 'Request failed'));
193
342
  }
343
+ }
194
344
 
195
- // Method 3: Check if openclaw binary exists + health check
196
- exec('command -v openclaw 2>/dev/null', { timeout: 2000 }, (err2, binPath) => {
197
- if (err2 || !binPath?.trim()) {
198
- return resolve({ available: false, models: [], port: null });
199
- }
200
- exec(`openclaw gateway health --url ws://127.0.0.1:${openClawConfig.port} 2>&1`, { timeout: 5000 }, (err4, healthOut) => {
201
- const hOutput = (healthOut || '').toLowerCase();
202
- if (!err4 && (hOutput.includes('ok') || hOutput.includes('healthy') || hOutput.includes('reachable'))) {
203
- return resolve({ available: true, models: ['openclaw'], port: openClawConfig.port });
204
- }
205
- return resolve({ available: false, models: [], port: null });
206
- });
207
- });
208
- });
345
+ // Handle chat streaming events
346
+ if (msg.type === 'event' && msg.event === 'chat') {
347
+ const p = msg.payload;
348
+ const cb = ocChatCallbacks.get(p.runId);
349
+ if (!cb) return;
350
+
351
+ if (p.state === 'delta') {
352
+ // Extract text from delta try common field paths
353
+ const text = typeof p.message === 'string' ? p.message
354
+ : p.message?.content || p.message?.text || '';
355
+ if (text) send(cb.ws, { type: 'chat.stream', id: cb.chatId, content: text });
356
+ } else if (p.state === 'final') {
357
+ const text = typeof p.message === 'string' ? p.message
358
+ : p.message?.content || p.message?.text || '';
359
+ if (text) send(cb.ws, { type: 'chat.stream', id: cb.chatId, content: text });
360
+ send(cb.ws, { type: 'chat.done', id: cb.chatId });
361
+ ocChatCallbacks.delete(p.runId);
362
+ } else if (p.state === 'error') {
363
+ send(cb.ws, { type: 'chat.done', id: cb.chatId, error: p.errorMessage || 'OpenClaw error' });
364
+ ocChatCallbacks.delete(p.runId);
365
+ } else if (p.state === 'aborted') {
366
+ send(cb.ws, { type: 'chat.done', id: cb.chatId });
367
+ ocChatCallbacks.delete(p.runId);
368
+ }
369
+ }
370
+ });
371
+
372
+ ws.on('error', (err) => { console.log(` [OpenClaw] Gateway error: ${err.message}`); clearTimeout(timeout); ocReady = false; reject(err); });
373
+ ws.on('close', () => {
374
+ console.log(' [OpenClaw] Gateway connection closed');
375
+ ocReady = false;
376
+ ocGateway = null;
377
+ // Notify all pending chat streams that gateway disconnected
378
+ for (const [runId, cb] of ocChatCallbacks) {
379
+ send(cb.ws, { type: 'chat.done', id: cb.chatId, error: 'OpenClaw gateway disconnected' });
380
+ ocChatCallbacks.delete(runId);
381
+ }
382
+ // Reject all pending requests
383
+ for (const [id, p] of ocPending) {
384
+ clearTimeout(p.timeout);
385
+ p.reject(new Error('Gateway disconnected'));
386
+ ocPending.delete(id);
387
+ }
209
388
  });
210
389
  });
211
390
  }
212
391
 
392
+ async function getOcGateway() {
393
+ if (ocGateway && ocGateway.readyState === WebSocket.OPEN && ocReady) return ocGateway;
394
+ return connectOcGateway();
395
+ }
396
+
397
+ function ocRequest(method, params) {
398
+ return new Promise(async (resolve, reject) => {
399
+ try {
400
+ const ws = await getOcGateway();
401
+ const id = crypto.randomUUID();
402
+ const t = setTimeout(() => { ocPending.delete(id); reject(new Error('Timeout')); }, 30000);
403
+ ocPending.set(id, { resolve, reject, timeout: t });
404
+ ws.send(JSON.stringify({ type: 'req', id, method, params }));
405
+ } catch (err) { reject(err); }
406
+ });
407
+ }
408
+
213
409
  // Run detection on startup
214
410
  detectOpenClaw().then((oc) => {
215
411
  if (oc.available) {
216
412
  console.log(` [OpenClaw] Detected (gateway on port ${oc.port})`);
413
+ // Pre-connect to gateway
414
+ connectOcGateway().catch(() => {});
415
+ } else if (oc.installed) {
416
+ console.log(` [OpenClaw] Installed but gateway not running (port ${oc.port})`);
217
417
  } else {
218
418
  console.log(' [OpenClaw] Not detected');
219
419
  }
@@ -261,6 +461,7 @@ wss.on('connection', (ws) => {
261
461
  if (msg.type === 'auth' && msg.token === AUTH_TOKEN) {
262
462
  authenticated = true;
263
463
  const openclaw = await detectOpenClaw();
464
+ console.log(` [Auth] Sending auth success, openclaw:`, JSON.stringify(openclaw));
264
465
  return ws.send(JSON.stringify({ type: 'auth', success: true, openclaw }));
265
466
  }
266
467
  if (msg.type === 'ping') {
@@ -289,10 +490,13 @@ wss.on('connection', (ws) => {
289
490
  }
290
491
  }
291
492
  // Clean up any active chat streams owned by this connection
292
- for (const [id, req] of activeChats) {
293
- if (req._ws === ws) {
294
- req.destroy();
295
- activeChats.delete(id);
493
+ for (const [chatId, chat] of activeChats) {
494
+ if (chat._ws === ws) {
495
+ if (chat.runId) {
496
+ ocRequest('chat.abort', { runId: chat.runId }).catch(() => {});
497
+ ocChatCallbacks.delete(chat.runId);
498
+ }
499
+ activeChats.delete(chatId);
296
500
  }
297
501
  }
298
502
  // Kill any active search process for this connection
@@ -364,7 +568,7 @@ async function handleMessage(ws, msg) {
364
568
  case 'terminal.stop':
365
569
  return handleTerminalStop(ws, msg);
366
570
  case 'chat.send':
367
- return handleChatSend(ws, msg);
571
+ return await handleChatSend(ws, msg);
368
572
  case 'chat.stop':
369
573
  return handleChatStop(ws, msg);
370
574
  case 'push.register':
@@ -977,110 +1181,53 @@ function handleTerminalStop(ws, { id }) {
977
1181
  reply(ws, id, { stopped: true });
978
1182
  }
979
1183
 
980
- // --- Chat (OpenClaw Proxy) ---
981
- function handleChatSend(ws, { id, messages }) {
982
- // Use locally-detected OpenClaw config — no need for app to send credentials
983
- const port = openClawConfig.port || 18789;
984
- const host = openClawConfig.host || '127.0.0.1';
985
- const token = openClawConfig.token;
986
-
987
- const body = JSON.stringify({
988
- model: 'openclaw:main',
989
- messages,
990
- stream: true,
991
- });
1184
+ // --- Chat (OpenClaw Gateway Proxy) ---
1185
+ async function handleChatSend(ws, { id, messages }) {
1186
+ try {
1187
+ console.log(` [Chat] handleChatSend id=${id}, messages=${messages.length}`);
992
1188
 
993
- const headers = {
994
- 'Content-Type': 'application/json',
995
- 'x-openclaw-agent-id': 'main',
996
- };
997
- if (token) {
998
- headers['Authorization'] = `Bearer ${token}`;
999
- }
1189
+ // Connect to OpenClaw gateway (auto-reconnects if needed)
1190
+ await getOcGateway();
1191
+ console.log(' [Chat] Gateway connected');
1000
1192
 
1001
- const req = http.request(
1002
- {
1003
- hostname: host,
1004
- port,
1005
- path: '/v1/chat/completions',
1006
- method: 'POST',
1007
- headers,
1008
- },
1009
- (res) => {
1010
- if (res.statusCode !== 200) {
1011
- let errorBody = '';
1012
- res.on('data', (chunk) => (errorBody += chunk));
1013
- res.on('end', () => {
1014
- send(ws, { type: 'chat.done', id, error: `OpenClaw error ${res.statusCode}: ${errorBody}` });
1015
- activeChats.delete(id);
1016
- });
1017
- return;
1018
- }
1193
+ // Extract the last user message from the conversation
1194
+ const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
1195
+ if (!lastUserMsg) {
1196
+ return send(ws, { type: 'chat.done', id, error: 'No user message found' });
1197
+ }
1198
+ console.log(` [Chat] Sending to OpenClaw: "${lastUserMsg.content.substring(0, 80)}..."`);
1019
1199
 
1020
- let buffer = '';
1021
- let sentDone = false;
1022
-
1023
- res.on('data', (chunk) => {
1024
- buffer += chunk.toString();
1025
- const lines = buffer.split('\n');
1026
- buffer = lines.pop() || '';
1027
-
1028
- for (const line of lines) {
1029
- const trimmed = line.trim();
1030
- if (!trimmed || trimmed.startsWith(':')) continue;
1031
- if (trimmed === 'data: [DONE]') {
1032
- if (!sentDone) {
1033
- sentDone = true;
1034
- send(ws, { type: 'chat.done', id });
1035
- activeChats.delete(id);
1036
- }
1037
- return;
1038
- }
1039
- if (trimmed.startsWith('data: ')) {
1040
- try {
1041
- const json = JSON.parse(trimmed.slice(6));
1042
- const content = json.choices?.[0]?.delta?.content;
1043
- if (content) {
1044
- send(ws, { type: 'chat.stream', id, content });
1045
- }
1046
- } catch {}
1047
- }
1048
- }
1049
- });
1200
+ // Build a session key — unique per chat conversation
1201
+ const sessionKey = `agent:indieclaw:mobile:${id}`;
1202
+ const idempotencyKey = crypto.randomUUID();
1050
1203
 
1051
- res.on('end', () => {
1052
- if (!sentDone) {
1053
- sentDone = true;
1054
- send(ws, { type: 'chat.done', id });
1055
- activeChats.delete(id);
1056
- }
1057
- });
1204
+ // Send chat request to gateway via WebSocket protocol
1205
+ const result = await ocRequest('chat.send', {
1206
+ sessionKey,
1207
+ message: { role: 'user', content: lastUserMsg.content },
1208
+ idempotencyKey,
1209
+ });
1058
1210
 
1059
- res.on('error', (err) => {
1060
- if (!sentDone) {
1061
- sentDone = true;
1062
- send(ws, { type: 'chat.done', id, error: err.message });
1063
- activeChats.delete(id);
1064
- }
1065
- });
1066
- }
1067
- );
1211
+ console.log(` [Chat] ocRequest result:`, JSON.stringify(result).substring(0, 200));
1068
1212
 
1069
- req.on('error', (err) => {
1070
- send(ws, { type: 'chat.done', id, error: `Connection failed: ${err.message}` });
1071
- activeChats.delete(id);
1072
- });
1213
+ // Register for streaming events using the runId from the response
1214
+ const runId = result?.runId || result?.id || id;
1215
+ console.log(` [Chat] Registered callback for runId=${runId}`);
1216
+ ocChatCallbacks.set(runId, { ws, chatId: id });
1217
+ activeChats.set(id, { runId, _ws: ws });
1073
1218
 
1074
- req._ws = ws;
1075
- activeChats.set(id, req);
1076
- req.write(body);
1077
- req.end();
1219
+ } catch (err) {
1220
+ console.log(` [Chat] ERROR: ${err.message}`);
1221
+ send(ws, { type: 'chat.done', id, error: `OpenClaw: ${err.message}` });
1222
+ }
1078
1223
  }
1079
1224
 
1080
1225
  function handleChatStop(ws, { id, chatId }) {
1081
- const req = activeChats.get(chatId);
1082
- if (req) {
1083
- req.destroy();
1226
+ const chat = activeChats.get(chatId);
1227
+ if (chat && chat.runId) {
1228
+ // Abort the chat via OpenClaw gateway
1229
+ ocRequest('chat.abort', { runId: chat.runId }).catch(() => {});
1230
+ ocChatCallbacks.delete(chat.runId);
1084
1231
  activeChats.delete(chatId);
1085
1232
  }
1086
1233
  reply(ws, id, { stopped: true });
@@ -1304,19 +1451,21 @@ function handleAgentLogs(ws, { id, lines = 200 }) {
1304
1451
  }
1305
1452
 
1306
1453
  // --- Graceful Shutdown ---
1307
- process.on('SIGINT', () => {
1308
- console.log('\n[agent] Shutting down...');
1454
+ function gracefulShutdown() {
1309
1455
  for (const [, term] of terminals) term.kill();
1310
- for (const [, req] of activeChats) req.destroy();
1456
+ activeChats.clear();
1457
+ ocChatCallbacks.clear();
1458
+ if (ocGateway) { try { ocGateway.close(); } catch {} }
1311
1459
  wss.close();
1312
1460
  if (server) server.close();
1313
1461
  process.exit(0);
1462
+ }
1463
+
1464
+ process.on('SIGINT', () => {
1465
+ console.log('\n[agent] Shutting down...');
1466
+ gracefulShutdown();
1314
1467
  });
1315
1468
 
1316
1469
  process.on('SIGTERM', () => {
1317
- for (const [, term] of terminals) term.kill();
1318
- for (const [, req] of activeChats) req.destroy();
1319
- wss.close();
1320
- if (server) server.close();
1321
- process.exit(0);
1470
+ gracefulShutdown();
1322
1471
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indieclaw-agent",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "Manage your server from your phone. Agent for the IndieClaw mobile app.",
5
5
  "main": "index.js",
6
6
  "bin": {