neoagent 2.3.1-beta.44 → 2.3.1-beta.46

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.
@@ -23,8 +23,6 @@ const {
23
23
  } = require('../services/ai/settings');
24
24
  const {
25
25
  readMeshtasticEnabled,
26
- setMeshtasticEnabled,
27
- resetMeshtasticEnabled,
28
26
  } = require('../services/messaging/meshtastic_env');
29
27
  const {
30
28
  ensureDefaultRuntimeSettings,
@@ -84,6 +82,10 @@ const ENV_BACKED_SETTING_KEYS = new Set([
84
82
  'meshtastic_enabled',
85
83
  ]);
86
84
 
85
+ const READ_ONLY_ENV_SETTING_KEYS = new Set([
86
+ 'meshtastic_enabled',
87
+ ]);
88
+
87
89
  function toOptionalTrimmedString(value) {
88
90
  if (typeof value !== 'string') return undefined;
89
91
  const trimmed = value.trim();
@@ -175,14 +177,8 @@ function readEnvBackedSettingValue(key) {
175
177
 
176
178
  async function writeEnvBackedSettingValue(req, key, value) {
177
179
  switch (key) {
178
- case 'meshtastic_enabled': {
179
- const enabled = setMeshtasticEnabled(value);
180
- const manager = req.app?.locals?.messagingManager;
181
- if (manager && typeof manager.updateMeshtasticEnabled === 'function') {
182
- await manager.updateMeshtasticEnabled(enabled);
183
- }
184
- return enabled;
185
- }
180
+ case 'meshtastic_enabled':
181
+ return readMeshtasticEnabled();
186
182
  default:
187
183
  return value;
188
184
  }
@@ -191,11 +187,6 @@ async function writeEnvBackedSettingValue(req, key, value) {
191
187
  async function resetEnvBackedSettingValue(req, key) {
192
188
  switch (key) {
193
189
  case 'meshtastic_enabled': {
194
- resetMeshtasticEnabled();
195
- const manager = req.app?.locals?.messagingManager;
196
- if (manager && typeof manager.updateMeshtasticEnabled === 'function') {
197
- await manager.updateMeshtasticEnabled(readMeshtasticEnabled());
198
- }
199
190
  return;
200
191
  }
201
192
  default:
@@ -292,6 +283,10 @@ router.put('/', async (req, res) => {
292
283
  }
293
284
 
294
285
  for (const key of Object.keys(normalizedBody)) {
286
+ if (READ_ONLY_ENV_SETTING_KEYS.has(key)) {
287
+ delete normalizedBody[key];
288
+ continue;
289
+ }
295
290
  if (isEnvBackedSettingKey(key)) {
296
291
  normalizedBody[key] = await writeEnvBackedSettingValue(req, key, normalizedBody[key]);
297
292
  }
@@ -470,6 +465,13 @@ router.put('/:key', async (req, res) => {
470
465
  const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
471
466
  ensureDefaultRuntimeSettings(userId);
472
467
  let value = req.body.value;
468
+ if (READ_ONLY_ENV_SETTING_KEYS.has(req.params.key)) {
469
+ return res.status(403).json({
470
+ success: false,
471
+ error: `${req.params.key} is managed via environment only`,
472
+ value: readEnvBackedSettingValue(req.params.key),
473
+ });
474
+ }
473
475
  if (isEnvBackedSettingKey(req.params.key)) {
474
476
  const saved = await writeEnvBackedSettingValue(req, req.params.key, value);
475
477
  return res.json({ success: true, value: saved });
@@ -535,6 +537,13 @@ router.put('/:key', async (req, res) => {
535
537
 
536
538
  // Delete setting
537
539
  router.delete('/:key', (req, res) => {
540
+ if (READ_ONLY_ENV_SETTING_KEYS.has(req.params.key)) {
541
+ return res.status(403).json({
542
+ success: false,
543
+ error: `${req.params.key} is managed via environment only`,
544
+ value: readEnvBackedSettingValue(req.params.key),
545
+ });
546
+ }
538
547
  if (isEnvBackedSettingKey(req.params.key)) {
539
548
  return resetEnvBackedSettingValue(req, req.params.key)
540
549
  .then(() => res.json({ success: true }))
@@ -1551,6 +1551,7 @@ class AgentEngine {
1551
1551
  agentId,
1552
1552
  triggerType,
1553
1553
  triggerSource,
1554
+ widgetId: options.widgetId || null,
1554
1555
  });
1555
1556
  const mcpManager = app?.locals?.mcpManager || app?.locals?.mcpClient || this.mcpManager;
1556
1557
  const integrationManager = app?.locals?.integrationManager || null;
@@ -103,6 +103,12 @@ class MeshtasticPlatform extends BasePlatform {
103
103
  this._disconnecting = false;
104
104
  this.status = 'connecting';
105
105
 
106
+ const staleTransport = this._transport;
107
+ this._transport = null;
108
+ if (staleTransport) {
109
+ await staleTransport.disconnect().catch(() => {});
110
+ }
111
+
106
112
  const transport = await MeshtasticTcpTransport.create(this.host, this.port, 60000);
107
113
  this._transport = transport;
108
114
 
@@ -174,6 +180,9 @@ class MeshtasticPlatform extends BasePlatform {
174
180
 
175
181
  conn.on('disconnected', (info) => {
176
182
  if (this._disconnecting) return;
183
+ if (this._transport === transport) {
184
+ this._transport = null;
185
+ }
177
186
  this.status = 'disconnected';
178
187
  this.emit('disconnected', { reason: info?.reason || 'device_disconnected' });
179
188
  this._scheduleReconnect();
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
- const path = require('path');
5
4
  const { ENV_FILE } = require('../../../runtime/paths');
6
5
  const { parseEnv } = require('../../../runtime/env');
7
6
 
@@ -20,41 +19,6 @@ function readEnvFileRaw(envFile = ENV_FILE) {
20
19
  return fs.readFileSync(envFile, 'utf8');
21
20
  }
22
21
 
23
- function writeEnvFileValue(key, value, envFile = ENV_FILE) {
24
- const raw = readEnvFileRaw(envFile);
25
- const lines = raw ? raw.split('\n') : [];
26
- let replaced = false;
27
-
28
- for (let i = 0; i < lines.length; i += 1) {
29
- if (lines[i].startsWith(`${key}=`)) {
30
- lines[i] = `${key}=${value}`;
31
- replaced = true;
32
- break;
33
- }
34
- }
35
-
36
- if (!replaced) {
37
- lines.push(`${key}=${value}`);
38
- }
39
-
40
- const output = `${lines
41
- .filter((_, idx, items) => idx !== items.length - 1 || items[idx] !== '')
42
- .join('\n')}\n`;
43
- fs.mkdirSync(path.dirname(envFile), { recursive: true });
44
- fs.writeFileSync(envFile, output, { mode: 0o600 });
45
- }
46
-
47
- function removeEnvFileValue(key, envFile = ENV_FILE) {
48
- const raw = readEnvFileRaw(envFile);
49
- if (!raw) return false;
50
- const lines = raw.split('\n').filter((line) => !line.startsWith(`${key}=`));
51
- const output = `${lines
52
- .filter((_, idx, items) => idx !== items.length - 1 || items[idx] !== '')
53
- .join('\n')}\n`;
54
- fs.mkdirSync(path.dirname(envFile), { recursive: true });
55
- fs.writeFileSync(envFile, output, { mode: 0o600 });
56
- return true;
57
- }
58
22
 
59
23
  function readMeshtasticEnabled({
60
24
  env = process.env,
@@ -69,32 +33,8 @@ function readMeshtasticEnabled({
69
33
  return normalizeMeshtasticEnabled(parsed.get(MESHTASTIC_ENABLED_ENV_KEY), fallback);
70
34
  }
71
35
 
72
- function setMeshtasticEnabled(enabled, {
73
- env = process.env,
74
- envFile = ENV_FILE,
75
- } = {}) {
76
- const normalized = normalizeMeshtasticEnabled(enabled, true);
77
- writeEnvFileValue(MESHTASTIC_ENABLED_ENV_KEY, normalized ? 'true' : 'false', envFile);
78
- if (env && typeof env === 'object') {
79
- env[MESHTASTIC_ENABLED_ENV_KEY] = normalized ? 'true' : 'false';
80
- }
81
- return normalized;
82
- }
83
-
84
- function resetMeshtasticEnabled({
85
- env = process.env,
86
- envFile = ENV_FILE,
87
- } = {}) {
88
- removeEnvFileValue(MESHTASTIC_ENABLED_ENV_KEY, envFile);
89
- if (env && typeof env === 'object') {
90
- delete env[MESHTASTIC_ENABLED_ENV_KEY];
91
- }
92
- }
93
-
94
36
  module.exports = {
95
37
  MESHTASTIC_ENABLED_ENV_KEY,
96
38
  normalizeMeshtasticEnabled,
97
39
  readMeshtasticEnabled,
98
- setMeshtasticEnabled,
99
- resetMeshtasticEnabled,
100
40
  };
@@ -314,36 +314,63 @@ class MeshtasticConnection extends EventEmitter {
314
314
  return new Promise((resolve, reject) => {
315
315
  const socket = new Socket();
316
316
  let settled = false;
317
+ let timer = null;
318
+
319
+ const cleanupHandshake = () => {
320
+ if (timer) {
321
+ clearTimeout(timer);
322
+ timer = null;
323
+ }
324
+ socket.removeListener('error', fail);
325
+ socket.removeListener('close', onPreReadyClose);
326
+ this.removeListener('configured', onConfigured);
327
+ this.removeListener('disconnected', onDisconnected);
328
+ };
317
329
 
318
330
  const fail = (err) => {
319
331
  if (settled) return;
320
332
  settled = true;
333
+ cleanupHandshake();
334
+ if (this._socket === socket) {
335
+ this._socket = null;
336
+ }
321
337
  socket.destroy();
322
338
  reject(err);
323
339
  };
324
340
 
325
- const timer = setTimeout(() => fail(new Error('Connection timeout')), timeout);
341
+ const onConfigured = () => {
342
+ if (settled) return;
343
+ settled = true;
344
+ cleanupHandshake();
345
+ resolve();
346
+ };
347
+
348
+ const onDisconnected = (info) => {
349
+ fail(new Error(`Disconnected before configuration: ${info?.reason || 'unknown'}`));
350
+ };
351
+
352
+ const onPreReadyClose = () => {
353
+ fail(new Error('Disconnected before configuration: socket-closed'));
354
+ };
355
+
356
+ timer = setTimeout(() => fail(new Error('Connection timeout')), timeout);
326
357
 
327
358
  socket.once('error', fail);
359
+ socket.once('close', onPreReadyClose);
328
360
  socket.once('ready', () => {
329
361
  socket.removeListener('error', fail);
362
+ socket.removeListener('close', onPreReadyClose);
330
363
  this._socket = socket;
331
364
  this._wireSocket(socket);
332
365
  this.emit('status', 'connected');
333
366
 
334
- const onConfigured = () => {
335
- if (settled) return;
336
- settled = true;
337
- clearTimeout(timer);
338
- resolve();
339
- };
340
367
  this.once('configured', onConfigured);
368
+ this.once('disconnected', onDisconnected);
341
369
 
342
370
  const toRadio = encodeToRadioWantConfig(this._configId);
343
371
  socket.write(framePacket(toRadio));
344
372
  });
345
373
 
346
- socket.setTimeout(timeout);
347
374
  socket.connect(port, host);
348
375
  });
349
376
  }
@@ -361,18 +388,22 @@ class MeshtasticConnection extends EventEmitter {
361
388
  });
362
389
 
363
390
  socket.on('data', parser);
364
- socket.on('error', (err) => this._onDisconnected(`socket-error: ${err.message}`))
391
+ socket.on('error', (err) => this._onDisconnected(`socket-error: ${err.message}`));
365
392
  socket.on('end', () => this._onDisconnected('socket-end'));
366
393
  socket.on('close', () => this._onDisconnected('socket-closed'));
367
394
  socket.on('timeout', () => {
368
395
  this._onDisconnected('socket-timeout');
369
- socket.destroy();
370
396
  });
371
397
  }
372
398
 
373
399
  _onDisconnected(reason) {
374
400
  if (this._closing) return;
401
+ const socket = this._socket;
402
+ if (!socket) return;
403
+ this._socket = null;
375
404
  this._configured = false;
405
+ socket.removeAllListeners();
406
+ socket.destroy();
376
407
  this.emit('status', 'disconnected');
377
408
  this.emit('disconnected', { reason });
378
409
  }