neoagent 2.1.14 → 2.1.15

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.
@@ -7,6 +7,12 @@ const { requireAuth } = require('../middleware/auth');
7
7
  const { normalizeWhatsAppWhitelist } = require('../utils/whatsapp');
8
8
  const { getVersionInfo } = require('../utils/version');
9
9
  const { UPDATE_STATUS_FILE, APP_DIR } = require('../../runtime/paths');
10
+ const {
11
+ parseReleaseChannel,
12
+ getReleaseChannelBranch,
13
+ getReleaseChannelDistTag,
14
+ writeReleaseChannelToEnvFile,
15
+ } = require('../../runtime/release_channel');
10
16
  const {
11
17
  createDefaultAiSettings,
12
18
  ensureDefaultAiSettings,
@@ -316,6 +322,27 @@ router.post('/update', (req, res) => {
316
322
  res.json({ success: true, message: 'Update triggered', pid: child.pid });
317
323
  });
318
324
 
325
+ router.put('/update/channel', (req, res) => {
326
+ const requested = req.body?.channel;
327
+ const releaseChannel = parseReleaseChannel(requested);
328
+ if (!releaseChannel) {
329
+ return res.status(400).json({
330
+ success: false,
331
+ error: 'Release channel must be "stable" or "beta".',
332
+ });
333
+ }
334
+
335
+ writeReleaseChannelToEnvFile(releaseChannel);
336
+ process.env.NEOAGENT_RELEASE_CHANNEL = releaseChannel;
337
+
338
+ res.json({
339
+ success: true,
340
+ releaseChannel,
341
+ targetBranch: getReleaseChannelBranch(releaseChannel),
342
+ npmDistTag: getReleaseChannelDistTag(releaseChannel),
343
+ });
344
+ });
345
+
319
346
  router.get('/update/status', (req, res) => {
320
347
  const status = readUpdateStatus();
321
348
  const version = getVersionInfo();
@@ -325,7 +352,11 @@ router.get('/update/status', (req, res) => {
325
352
  installedVersion: version.installedVersion,
326
353
  packageVersion: version.packageVersion,
327
354
  gitVersion: version.gitVersion,
328
- gitSha: version.gitSha
355
+ gitSha: version.gitSha,
356
+ gitBranch: version.gitBranch,
357
+ releaseChannel: version.releaseChannel,
358
+ targetBranch: version.targetBranch,
359
+ npmDistTag: version.npmDistTag,
329
360
  });
330
361
  });
331
362
 
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ const HEADLESS_BROWSER_SETTING_KEY = 'headless_browser';
4
+
5
+ function getErrorMessage(error) {
6
+ if (error instanceof Error && error.message) {
7
+ return error.message;
8
+ }
9
+
10
+ return String(error);
11
+ }
12
+
13
+ function isEnabledSetting(value) {
14
+ return value !== 'false' && value !== false && value !== '0';
15
+ }
16
+
17
+ function restoreBrowserHeadlessPreference(browserController, database) {
18
+ const userCount =
19
+ database.prepare('SELECT COUNT(*) AS count FROM users').get()?.count || 0;
20
+ const headlessSetting =
21
+ userCount === 1
22
+ ? database
23
+ .prepare(
24
+ 'SELECT value FROM user_settings WHERE user_id = (SELECT id FROM users LIMIT 1) AND key = ?',
25
+ )
26
+ .get(HEADLESS_BROWSER_SETTING_KEY)
27
+ : null;
28
+
29
+ if (!headlessSetting) {
30
+ return { restored: false, userCount };
31
+ }
32
+
33
+ browserController.headless = isEnabledSetting(headlessSetting.value);
34
+ return {
35
+ restored: true,
36
+ userCount,
37
+ headless: browserController.headless,
38
+ };
39
+ }
40
+
41
+ function runBackgroundTask(errorPrefix, task, logger = console.error) {
42
+ return Promise.resolve()
43
+ .then(task)
44
+ .catch((error) => {
45
+ logger(errorPrefix, getErrorMessage(error));
46
+ });
47
+ }
48
+
49
+ module.exports = {
50
+ getErrorMessage,
51
+ isEnabledSetting,
52
+ restoreBrowserHeadlessPreference,
53
+ runBackgroundTask,
54
+ };
@@ -15,173 +15,309 @@ const { setupWebSocket } = require('./websocket');
15
15
  const { registerMessagingAutomation } = require('./messaging/automation');
16
16
  const { RecordingManager } = require('./recordings/manager');
17
17
  const { CLIExecutor } = require('./cli/executor');
18
+ const {
19
+ getErrorMessage,
20
+ restoreBrowserHeadlessPreference,
21
+ runBackgroundTask,
22
+ } = require('./bootstrap_helpers');
23
+
24
+ function registerLocal(app, key, value) {
25
+ app.locals[key] = value;
26
+ return value;
27
+ }
28
+
29
+ function logServiceReady(message) {
30
+ console.log(`[Services] ${message}`);
31
+ }
32
+
33
+ function createCliExecutor(app) {
34
+ const cliExecutor = registerLocal(app, 'cliExecutor', new CLIExecutor());
35
+ logServiceReady('CLI executor ready');
36
+ return cliExecutor;
37
+ }
38
+
39
+ function createMemoryManager(app) {
40
+ const memoryManager = registerLocal(app, 'memoryManager', new MemoryManager());
41
+ logServiceReady('Memory manager ready');
42
+ return memoryManager;
43
+ }
44
+
45
+ function createMcpClient(app) {
46
+ const mcpClient = registerLocal(app, 'mcpClient', new MCPClient());
47
+ logServiceReady('MCP client ready');
48
+ return mcpClient;
49
+ }
50
+
51
+ function createBrowserController(app) {
52
+ const browserController = registerLocal(
53
+ app,
54
+ 'browserController',
55
+ new BrowserController(),
56
+ );
57
+ const { restored, userCount, headless } = restoreBrowserHeadlessPreference(
58
+ browserController,
59
+ db,
60
+ );
61
+
62
+ if (restored) {
63
+ logServiceReady(`Browser headless setting restored to ${headless}`);
64
+ }
65
+
66
+ logServiceReady(`Browser controller ready for ${userCount} user(s)`);
67
+ return browserController;
68
+ }
69
+
70
+ function createAndroidController(app) {
71
+ const androidController = registerLocal(
72
+ app,
73
+ 'androidController',
74
+ new AndroidController(),
75
+ );
76
+ logServiceReady('Android controller ready');
77
+ return androidController;
78
+ }
79
+
80
+ async function createSkillRunner(app, cliExecutor) {
81
+ const skillRunner = registerLocal(
82
+ app,
83
+ 'skillRunner',
84
+ new SkillRunner({ executor: cliExecutor }),
85
+ );
86
+ await skillRunner.loadSkills();
87
+ logServiceReady('Skills loaded');
88
+ return skillRunner;
89
+ }
90
+
91
+ function createLearningManager(app, skillRunner, io) {
92
+ const learningManager = registerLocal(
93
+ app,
94
+ 'learningManager',
95
+ new LearningManager(skillRunner, io),
96
+ );
97
+ logServiceReady('Learning manager ready');
98
+ return learningManager;
99
+ }
100
+
101
+ function createAgentEngine(
102
+ app,
103
+ io,
104
+ {
105
+ cliExecutor,
106
+ memoryManager,
107
+ mcpClient,
108
+ browserController,
109
+ androidController,
110
+ skillRunner,
111
+ learningManager,
112
+ },
113
+ ) {
114
+ const agentEngine = registerLocal(
115
+ app,
116
+ 'agentEngine',
117
+ new AgentEngine(io, {
118
+ cliExecutor,
119
+ memoryManager,
120
+ mcpClient,
121
+ browserController,
122
+ androidController,
123
+ messagingManager: null,
124
+ skillRunner,
125
+ learningManager,
126
+ }),
127
+ );
128
+ logServiceReady('Agent engine ready');
129
+ return agentEngine;
130
+ }
131
+
132
+ function createMultiStep(app, agentEngine, io) {
133
+ const multiStep = registerLocal(
134
+ app,
135
+ 'multiStep',
136
+ new MultiStepOrchestrator(agentEngine, io),
137
+ );
138
+ logServiceReady('Multi-step orchestrator ready');
139
+ return multiStep;
140
+ }
141
+
142
+ function createMessagingManager(app, io, agentEngine) {
143
+ const messagingManager = registerLocal(
144
+ app,
145
+ 'messagingManager',
146
+ new MessagingManager(io),
147
+ );
148
+ agentEngine.messagingManager = messagingManager;
149
+ logServiceReady('Messaging manager ready');
150
+ return messagingManager;
151
+ }
152
+
153
+ function createRecordingManager(app, io) {
154
+ const recordingManager = registerLocal(
155
+ app,
156
+ 'recordingManager',
157
+ new RecordingManager(io),
158
+ );
159
+ logServiceReady('Recording manager ready');
160
+ return recordingManager;
161
+ }
162
+
163
+ function restoreMessagingConnections(messagingManager) {
164
+ void runBackgroundTask('[Messaging] Restore error:', () =>
165
+ messagingManager.restoreConnections(),
166
+ );
167
+ }
168
+
169
+ function restoreMcpClients(mcpClient) {
170
+ const users = db.prepare('SELECT id FROM users').all();
171
+ logServiceReady(`Restoring MCP clients for ${users.length} user(s)`);
172
+
173
+ for (const user of users) {
174
+ void runBackgroundTask('[MCP] Auto-start error:', () =>
175
+ mcpClient.loadFromDB(user.id),
176
+ );
177
+ }
178
+ }
179
+
180
+ function startScheduler(app, io, agentEngine) {
181
+ const scheduler = registerLocal(app, 'scheduler', new Scheduler(io, agentEngine, app));
182
+ agentEngine.scheduler = scheduler;
183
+ scheduler.start();
184
+ logServiceReady('Scheduler started');
185
+ return scheduler;
186
+ }
187
+
188
+ function configureRealtime(app, io, services) {
189
+ setupWebSocket(io, {
190
+ agentEngine: services.agentEngine,
191
+ messagingManager: services.messagingManager,
192
+ mcpClient: services.mcpClient,
193
+ scheduler: services.scheduler,
194
+ recordingManager: services.recordingManager,
195
+ memoryManager: services.memoryManager,
196
+ app,
197
+ });
198
+ app.locals.io = io;
199
+ logServiceReady('WebSocket handlers registered');
200
+ }
201
+
202
+ function resumePendingRecordingSessions(recordingManager) {
203
+ void runBackgroundTask('[Recordings] Resume error:', () =>
204
+ recordingManager.resumePendingSessions(),
205
+ );
206
+ }
18
207
 
19
208
  async function startServices(app, io) {
20
- try {
21
- console.log('[Services] Starting service initialization');
22
- const cliExecutor = new CLIExecutor();
23
- app.locals.cliExecutor = cliExecutor;
24
- console.log('[Services] CLI executor ready');
25
-
26
- const memoryManager = new MemoryManager();
27
- app.locals.memoryManager = memoryManager;
28
- console.log('[Services] Memory manager ready');
29
-
30
- const mcpClient = new MCPClient();
31
- app.locals.mcpClient = mcpClient;
32
- console.log('[Services] MCP client ready');
33
-
34
- const browserController = new BrowserController();
35
- const userCount = db.prepare('SELECT COUNT(*) AS count FROM users').get()?.count || 0;
36
- const headlessSetting = userCount === 1
37
- ? db.prepare('SELECT value FROM user_settings WHERE user_id = (SELECT id FROM users LIMIT 1) AND key = ?').get('headless_browser')
38
- : null;
39
- if (headlessSetting) {
40
- const val = headlessSetting.value;
41
- browserController.headless = val !== 'false' && val !== false && val !== '0';
42
- console.log(`[Services] Browser headless setting restored to ${browserController.headless}`);
43
- }
44
- app.locals.browserController = browserController;
45
- console.log(`[Services] Browser controller ready for ${userCount} user(s)`);
46
-
47
- const androidController = new AndroidController();
48
- app.locals.androidController = androidController;
49
- console.log('[Services] Android controller ready');
50
-
51
- const skillRunner = new SkillRunner({ executor: cliExecutor });
52
- await skillRunner.loadSkills();
53
- app.locals.skillRunner = skillRunner;
54
- console.log('[Services] Skills loaded');
55
-
56
- const learningManager = new LearningManager(skillRunner, io);
57
- app.locals.learningManager = learningManager;
58
- console.log('[Services] Learning manager ready');
59
-
60
- const agentEngine = new AgentEngine(io, {
61
- cliExecutor,
62
- memoryManager,
63
- mcpClient,
64
- browserController,
65
- androidController,
66
- messagingManager: null,
67
- skillRunner,
68
- learningManager
69
- });
70
- app.locals.agentEngine = agentEngine;
71
- console.log('[Services] Agent engine ready');
72
-
73
- const multiStep = new MultiStepOrchestrator(agentEngine, io);
74
- app.locals.multiStep = multiStep;
75
- console.log('[Services] Multi-step orchestrator ready');
76
-
77
- const messagingManager = new MessagingManager(io);
78
- app.locals.messagingManager = messagingManager;
79
- agentEngine.messagingManager = messagingManager;
80
- console.log('[Services] Messaging manager ready');
81
-
82
- messagingManager.restoreConnections().catch(err => console.error('[Messaging] Restore error:', err.message));
83
-
84
- const recordingManager = new RecordingManager(io);
85
- app.locals.recordingManager = recordingManager;
86
- console.log('[Services] Recording manager ready');
87
-
88
- const users = db.prepare('SELECT id FROM users').all();
89
- console.log(`[Services] Restoring MCP clients for ${users.length} user(s)`);
90
- for (const u of users) {
91
- mcpClient.loadFromDB(u.id).catch(err => console.error('[MCP] Auto-start error:', err.message));
92
- }
93
-
94
- registerMessagingAutomation({
95
- app,
96
- io,
97
- messagingManager,
98
- agentEngine,
99
- });
100
-
101
- const scheduler = new Scheduler(io, agentEngine, app);
102
- app.locals.scheduler = scheduler;
103
- agentEngine.scheduler = scheduler;
104
- scheduler.start();
105
- console.log('[Services] Scheduler started');
106
-
107
- setupWebSocket(io, {
108
- agentEngine,
109
- messagingManager,
110
- mcpClient,
111
- scheduler,
112
- recordingManager,
113
- memoryManager,
114
- app
115
- });
116
- app.locals.io = io;
117
- console.log('[Services] WebSocket handlers registered');
118
-
119
- recordingManager.resumePendingSessions().catch((err) => {
120
- console.error('[Recordings] Resume error:', err.message);
121
- });
122
-
123
- console.log('All services initialized');
124
- } catch (err) {
125
- console.error('Service init error:', err);
126
- }
209
+ console.log('[Services] Starting service initialization');
210
+
211
+ try {
212
+ const cliExecutor = createCliExecutor(app);
213
+ const memoryManager = createMemoryManager(app);
214
+ const mcpClient = createMcpClient(app);
215
+ const browserController = createBrowserController(app);
216
+ const androidController = createAndroidController(app);
217
+ const skillRunner = await createSkillRunner(app, cliExecutor);
218
+ const learningManager = createLearningManager(app, skillRunner, io);
219
+ const agentEngine = createAgentEngine(app, io, {
220
+ cliExecutor,
221
+ memoryManager,
222
+ mcpClient,
223
+ browserController,
224
+ androidController,
225
+ skillRunner,
226
+ learningManager,
227
+ });
228
+
229
+ createMultiStep(app, agentEngine, io);
230
+
231
+ const messagingManager = createMessagingManager(app, io, agentEngine);
232
+ const recordingManager = createRecordingManager(app, io);
233
+
234
+ restoreMessagingConnections(messagingManager);
235
+ restoreMcpClients(mcpClient);
236
+
237
+ registerMessagingAutomation({
238
+ app,
239
+ io,
240
+ messagingManager,
241
+ agentEngine,
242
+ });
243
+
244
+ const scheduler = startScheduler(app, io, agentEngine);
245
+
246
+ configureRealtime(app, io, {
247
+ agentEngine,
248
+ messagingManager,
249
+ mcpClient,
250
+ scheduler,
251
+ recordingManager,
252
+ memoryManager,
253
+ });
254
+
255
+ resumePendingRecordingSessions(recordingManager);
256
+
257
+ console.log('All services initialized');
258
+ } catch (err) {
259
+ console.error('Service init error:', err);
260
+ await stopServices(app);
261
+ throw err;
262
+ }
127
263
  }
128
264
 
129
265
  async function stopServices(app) {
130
- const tasks = [];
131
- console.log('[Services] Stopping services');
132
-
133
- if (app.locals.scheduler) {
134
- try {
135
- app.locals.scheduler.stop();
136
- console.log('[Services] Scheduler stopped');
137
- } catch (err) {
138
- console.error('[Scheduler] Stop error:', err.message);
139
- }
140
- }
266
+ const tasks = [];
267
+ console.log('[Services] Stopping services');
141
268
 
142
- if (app.locals.mcpClient) {
143
- tasks.push(
144
- app.locals.mcpClient.shutdown().catch((err) => {
145
- console.error('[MCP] Shutdown error:', err.message);
146
- }),
147
- );
269
+ if (app.locals.scheduler) {
270
+ try {
271
+ app.locals.scheduler.stop();
272
+ logServiceReady('Scheduler stopped');
273
+ } catch (err) {
274
+ console.error('[Scheduler] Stop error:', getErrorMessage(err));
148
275
  }
276
+ }
149
277
 
150
- if (app.locals.browserController) {
151
- tasks.push(
152
- app.locals.browserController.closeBrowser().catch((err) => {
153
- console.error('[Browser] Shutdown error:', err.message);
154
- }),
155
- );
156
- }
278
+ if (app.locals.mcpClient) {
279
+ tasks.push(
280
+ app.locals.mcpClient.shutdown().catch((err) => {
281
+ console.error('[MCP] Shutdown error:', getErrorMessage(err));
282
+ }),
283
+ );
284
+ }
157
285
 
158
- if (app.locals.androidController) {
159
- tasks.push(
160
- app.locals.androidController.close().catch((err) => {
161
- console.error('[Android] Shutdown error:', err.message);
162
- }),
163
- );
164
- }
286
+ if (app.locals.browserController) {
287
+ tasks.push(
288
+ app.locals.browserController.closeBrowser().catch((err) => {
289
+ console.error('[Browser] Shutdown error:', getErrorMessage(err));
290
+ }),
291
+ );
292
+ }
165
293
 
166
- if (app.locals.messagingManager) {
167
- tasks.push(
168
- app.locals.messagingManager.shutdown().catch((err) => {
169
- console.error('[Messaging] Shutdown error:', err.message);
170
- }),
171
- );
172
- }
294
+ if (app.locals.androidController) {
295
+ tasks.push(
296
+ app.locals.androidController.close().catch((err) => {
297
+ console.error('[Android] Shutdown error:', getErrorMessage(err));
298
+ }),
299
+ );
300
+ }
173
301
 
174
- if (app.locals.cliExecutor) {
175
- try {
176
- app.locals.cliExecutor.killAll('shutdown');
177
- console.log('[Services] CLI executor processes terminated');
178
- } catch (err) {
179
- console.error('[CLI] Shutdown error:', err.message);
180
- }
302
+ if (app.locals.messagingManager) {
303
+ tasks.push(
304
+ app.locals.messagingManager.shutdown().catch((err) => {
305
+ console.error('[Messaging] Shutdown error:', getErrorMessage(err));
306
+ }),
307
+ );
308
+ }
309
+
310
+ if (app.locals.cliExecutor) {
311
+ try {
312
+ app.locals.cliExecutor.killAll('shutdown');
313
+ logServiceReady('CLI executor processes terminated');
314
+ } catch (err) {
315
+ console.error('[CLI] Shutdown error:', getErrorMessage(err));
181
316
  }
317
+ }
182
318
 
183
- await Promise.allSettled(tasks);
184
- console.log('[Services] Shutdown tasks settled');
319
+ await Promise.allSettled(tasks);
320
+ logServiceReady('Shutdown tasks settled');
185
321
  }
186
322
 
187
323
  module.exports = { startServices, stopServices };
@@ -4,6 +4,11 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { execSync } = require('child_process');
6
6
  const { APP_DIR } = require('../../runtime/paths');
7
+ const {
8
+ getReleaseChannelBranch,
9
+ getReleaseChannelDistTag,
10
+ readConfiguredReleaseChannel,
11
+ } = require('../../runtime/release_channel');
7
12
 
8
13
  const PACKAGE_JSON_PATH = path.join(APP_DIR, 'package.json');
9
14
 
@@ -18,9 +23,11 @@ function readPackageVersion() {
18
23
 
19
24
  function getVersionInfo() {
20
25
  const packageVersion = readPackageVersion() || '0.0.0';
26
+ const releaseChannel = readConfiguredReleaseChannel();
21
27
  let version = packageVersion;
22
28
  let gitSha = null;
23
29
  let gitVersion = null;
30
+ let gitBranch = null;
24
31
 
25
32
  try {
26
33
  gitVersion =
@@ -36,6 +43,11 @@ function getVersionInfo() {
36
43
  encoding: 'utf8',
37
44
  stdio: ['ignore', 'pipe', 'ignore']
38
45
  }).trim();
46
+ gitBranch = execSync('git rev-parse --abbrev-ref HEAD', {
47
+ cwd: APP_DIR,
48
+ encoding: 'utf8',
49
+ stdio: ['ignore', 'pipe', 'ignore']
50
+ }).trim();
39
51
  } catch {
40
52
  gitSha = process.env.GIT_SHA || null;
41
53
  }
@@ -51,8 +63,12 @@ function getVersionInfo() {
51
63
  version,
52
64
  packageVersion,
53
65
  gitVersion,
66
+ gitBranch,
54
67
  gitSha,
55
- installedVersion: packageVersion
68
+ installedVersion: packageVersion,
69
+ releaseChannel,
70
+ targetBranch: getReleaseChannelBranch(releaseChannel),
71
+ npmDistTag: getReleaseChannelDistTag(releaseChannel),
56
72
  };
57
73
  }
58
74