neoagent 3.2.1-beta.6 → 3.2.1-beta.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "3.2.1-beta.6",
3
+ "version": "3.2.1-beta.8",
4
4
  "description": "Self-hosted AI agent for long-running tasks, automation, messaging, device control, and local memory",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -68,7 +68,7 @@
68
68
  "dependencies": {
69
69
  "@anthropic-ai/sdk": "^0.39.0",
70
70
  "@bitwarden/cli": "^2026.6.0",
71
- "@google/generative-ai": "^0.24.0",
71
+ "@google/genai": "^2.13.0",
72
72
  "@modelcontextprotocol/sdk": "^1.29.0",
73
73
  "@remotion/cli": "^4.0.459",
74
74
  "@slidev/cli": "^52.18.0",
@@ -56,6 +56,13 @@ function registerApiRoutes(app) {
56
56
  if (isBillingEnabled()) {
57
57
  app.use('/api/billing/webhook', require('../routes/billing_webhook'));
58
58
  app.use('/api/billing', require('../routes/billing'));
59
+ } else {
60
+ // The Flutter client probes this public endpoint to decide whether to show
61
+ // billing. Return an explicit disabled state instead of generating a noisy
62
+ // 404 on every client startup.
63
+ app.get('/api/billing/plans', (_req, res) => {
64
+ res.json({ enabled: false, plans: null });
65
+ });
59
66
  }
60
67
 
61
68
  setupTelnyxWebhook(app);
@@ -1 +1 @@
1
- 47dfb68683e209ba52dcaade6052a7c2
1
+ 38fc05bea2f5bf14dda4c0f58b31ff9a
@@ -33,10 +33,10 @@ addEventListener("message", eventListener);
33
33
  if (!window._flutter) {
34
34
  window._flutter = {};
35
35
  }
36
- _flutter.buildConfig = {"engineRevision":"69c8c61792f04cc809dfef0c910414fb9afc06cd","builds":[{"compileTarget":"dart2js","renderer":"canvaskit","mainJsPath":"main.dart.js"},{}]};
36
+ _flutter.buildConfig = {"engineRevision":"0cd610717bde95fd88343c64f81c11ba4e5c0010","builds":[{"compileTarget":"dart2js","renderer":"canvaskit","mainJsPath":"main.dart.js"},{}]};
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "1886361738" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "222530411" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -44367,7 +44367,7 @@ $S:4}
44367
44367
  A.bnI.prototype={
44368
44368
  $1(a){var s=A.fp().b
44369
44369
  s=s==null?null:s.canvasKitBaseUrl
44370
- return(s==null?"https://www.gstatic.com/flutter-canvaskit/69c8c61792f04cc809dfef0c910414fb9afc06cd/":s)+a},
44370
+ return(s==null?"https://www.gstatic.com/flutter-canvaskit/0cd610717bde95fd88343c64f81c11ba4e5c0010/":s)+a},
44371
44371
  $S:42}
44372
44372
  A.a2F.prototype={
44373
44373
  gF(a){var s=this.a
@@ -144506,7 +144506,7 @@ if(r){r=s.d
144506
144506
  r===$&&A.b()
144507
144507
  p.push(A.ju(q,A.jx(!1,new A.Y(B.wP,A.cU(new A.cH(B.kE,new A.acU(r,q),q),q,q),q),!1,B.J,!0),q,q,0,0,0,q))}if(!s.ay){r=s.e
144508
144508
  r===$&&A.b()
144509
- r=B.b.t("mry17udu-e9492af").length!==0&&r.b}else r=!1
144509
+ r=B.b.t("mryrwx0x-27df61b").length!==0&&r.b}else r=!1
144510
144510
  if(r){r=s.d
144511
144511
  r===$&&A.b()
144512
144512
  r=r.aU&&!r.ag?84:0
@@ -150393,7 +150393,7 @@ $S:0}
150393
150393
  A.a3j.prototype={}
150394
150394
  A.WC.prototype={
150395
150395
  r9(a){var s=this
150396
- if(B.b.t("mry17udu-e9492af").length===0||s.a!=null)return
150396
+ if(B.b.t("mryrwx0x-27df61b").length===0||s.a!=null)return
150397
150397
  s.Co()
150398
150398
  s.a=A.mL(B.Yh,new A.bmK(s))},
150399
150399
  Co(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -150411,7 +150411,7 @@ if(!t.f.b(k)){s=1
150411
150411
  break}i=J.a0(k,"buildId")
150412
150412
  h=i==null?null:B.b.t(J.q(i))
150413
150413
  j=h==null?"":h
150414
- if(J.bv(j)===0||J.e(j,"mry17udu-e9492af")){s=1
150414
+ if(J.bv(j)===0||J.e(j,"mryrwx0x-27df61b")){s=1
150415
150415
  break}n.b=!0
150416
150416
  n.H()
150417
150417
  p=2
@@ -150428,7 +150428,7 @@ case 2:return A.i(o.at(-1),r)}})
150428
150428
  return A.k($async$Co,r)},
150429
150429
  wV(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
150430
150430
  var $async$wV=A.h(function(a2,a3){if(a2===1){o.push(a3)
150431
- s=p}for(;;)switch(s){case 0:if(B.b.t("mry17udu-e9492af").length===0||n.c){s=1
150431
+ s=p}for(;;)switch(s){case 0:if(B.b.t("mryrwx0x-27df61b").length===0||n.c){s=1
150432
150432
  break}n.c=!0
150433
150433
  n.H()
150434
150434
  p=4
@@ -39,6 +39,9 @@ async function getBrowserHealth(userId, app, engine) {
39
39
  const runtimeSettings = typeof runtimeManager?.getSettings === 'function'
40
40
  ? runtimeManager.getSettings(userId)
41
41
  : null;
42
+ const runtimeSnapshot = typeof runtimeManager?.getCapabilitySnapshot === 'function'
43
+ ? runtimeManager.getCapabilitySnapshot(userId)
44
+ : null;
42
45
  const extensionStatus = runtimeSettings?.browser_backend === 'extension'
43
46
  ? app?.locals?.browserExtensionRegistry?.getStatus(userId)
44
47
  : null;
@@ -63,122 +66,81 @@ async function getBrowserHealth(userId, app, engine) {
63
66
  });
64
67
  }
65
68
  }
66
- let controller = null;
67
- let resolutionError = null;
68
-
69
- if (runtimeManager && typeof runtimeManager.getBrowserProviderForUser === 'function') {
70
- try {
71
- controller = await runtimeManager.getBrowserProviderForUser(userId);
72
- } catch (err) {
73
- resolutionError = err;
74
- }
75
- }
76
-
77
- if (!controller) {
78
- resolutionError = resolutionError || new Error('Browser provider is unavailable. VM runtime is required.');
79
- }
80
-
81
- if (!controller && resolutionError) {
69
+ if (!runtimeManager) {
82
70
  return capabilityEntry({
83
- configured: true,
84
- healthy: false,
85
- degraded: true,
86
- summary: `Browser controller resolution failed: ${resolutionError.message}`,
87
- details: {
88
- error: resolutionError.message,
89
- },
71
+ summary: 'Browser runtime is not available in this environment.',
90
72
  });
91
73
  }
92
- let pageInfo = null;
93
- let launched = false;
94
- let error = null;
95
74
 
96
- try {
97
- launched = typeof controller?.isLaunched === 'function'
98
- ? await Promise.resolve(controller.isLaunched())
99
- : false;
100
- pageInfo = typeof controller?.getPageInfo === 'function' ? await controller.getPageInfo() : null;
101
- } catch (err) {
102
- error = err.message;
103
- }
75
+ // Capability reporting runs on every agent turn. It must never resolve a
76
+ // provider because the VM provider performs image builds and container boot.
77
+ // Tool execution remains the authoritative health check and starts/restarts
78
+ // the runtime only when the model actually uses a browser tool.
79
+ const preferredBackend = runtimeSettings?.browser_backend
80
+ || runtimeSnapshot?.browser?.preferredBackend
81
+ || 'vm';
82
+ const vmInitialized = runtimeSnapshot?.browser?.vmInitialized === true;
83
+ const extensionConnected = extensionStatus?.connected === true
84
+ || runtimeSnapshot?.browser?.extensionConnected === true;
85
+ const extensionPreferredButOffline = preferredBackend === 'extension' && !extensionConnected;
104
86
 
105
87
  return capabilityEntry({
106
- connected: launched,
88
+ connected: vmInitialized || extensionConnected,
107
89
  configured: true,
108
- healthy: !error,
109
- degraded: Boolean(error) || runtimeSettings?.browser_backend === 'extension',
110
- summary: error
111
- ? `Browser runtime error: ${error}`
112
- : runtimeSettings?.browser_backend === 'extension'
113
- ? `No extension device is active. Falling back to the vm browser runtime.`
114
- : (launched ? 'Browser runtime is ready.' : 'Browser runtime is available but not launched.'),
90
+ healthy: true,
91
+ degraded: extensionPreferredButOffline,
92
+ summary: extensionPreferredButOffline
93
+ ? 'No extension device is active. The VM browser will start on first use.'
94
+ : vmInitialized
95
+ ? 'Browser VM session is initialized.'
96
+ : 'Browser runtime is available and will start on first use.',
115
97
  details: {
116
- preferredBackend: runtimeSettings?.browser_backend || null,
117
- backend: runtimeSettings?.browser_backend === 'extension'
118
- ? 'vm'
119
- : runtimeSettings?.browser_backend || null,
120
- extensionConnected: extensionStatus?.connected === true,
98
+ preferredBackend,
99
+ backend: extensionConnected ? 'extension' : 'vm',
100
+ extensionConnected,
121
101
  activeTokenCount: Array.isArray(extensionStatus?.tokens)
122
102
  ? extensionStatus.tokens.filter((token) => token.status === 'active').length
123
103
  : 0,
124
- launched,
125
- pageInfo,
104
+ runtimeInitialized: vmInitialized,
126
105
  },
127
106
  });
128
107
  }
129
108
 
130
109
  async function getAndroidHealth(userId, app, engine) {
131
110
  const runtimeManager = app?.locals?.runtimeManager || engine?.runtimeManager || null;
132
- let controller = null;
133
- let resolutionError = null;
134
-
135
- if (runtimeManager && typeof runtimeManager.getAndroidProviderForUser === 'function') {
136
- try {
137
- controller = await runtimeManager.getAndroidProviderForUser(userId);
138
- } catch (err) {
139
- resolutionError = err;
140
- }
141
- }
142
-
143
- if (!controller) {
144
- resolutionError = resolutionError || new Error('Android provider is unavailable. VM runtime is required.');
145
- }
146
-
147
- if (!controller || typeof controller.getStatus !== 'function') {
111
+ if (!runtimeManager) {
148
112
  return capabilityEntry({
149
- degraded: Boolean(resolutionError),
150
- summary: resolutionError
151
- ? `Android controller resolution failed: ${resolutionError.message}`
152
- : 'Android controller is not available.',
153
- details: resolutionError ? { error: resolutionError.message } : {},
113
+ summary: 'Android runtime is not available in this environment.',
154
114
  });
155
115
  }
156
116
 
157
- try {
158
- const status = await controller.getStatus();
159
- const bootstrapped = status.bootstrapped === true;
160
- const canBootstrap = status.canBootstrap === true;
161
- return capabilityEntry({
162
- connected: Array.isArray(status.devices) && status.devices.some((device) => device.status === 'device'),
163
- configured: canBootstrap || bootstrapped,
164
- healthy: canBootstrap || bootstrapped,
165
- degraded: Boolean(status.lastStartError),
166
- summary: status.lastStartError
167
- ? `Android tooling reported: ${status.lastStartError}`
168
- : bootstrapped
169
- ? 'Android environment is ready on this host.'
170
- : (canBootstrap ? 'Android environment can be bootstrapped on this host.' : 'Android tooling cannot bootstrap on this host.'),
171
- details: status,
172
- });
173
- } catch (err) {
174
- return capabilityEntry({
175
- configured: true,
176
- healthy: false,
177
- degraded: true,
178
- summary: `Android status check failed: ${err.message}`,
179
- details: { error: err.message },
180
- });
181
- }
117
+ // Like browser health, Android health is a snapshot. Creating a controller or
118
+ // running adb here makes unrelated chat turns pay runtime startup/status costs.
119
+ const runtimeSnapshot = typeof runtimeManager.getCapabilitySnapshot === 'function'
120
+ ? runtimeManager.getCapabilitySnapshot(userId)
121
+ : null;
122
+ const status = runtimeSnapshot?.android?.status || null;
123
+ const bootstrapped = status?.bootstrapped === true;
124
+ const starting = status?.starting === true;
125
+ const lastStartError = String(status?.lastStartError || '').trim();
126
+ return capabilityEntry({
127
+ connected: bootstrapped,
128
+ configured: true,
129
+ healthy: !lastStartError,
130
+ degraded: Boolean(lastStartError),
131
+ summary: lastStartError
132
+ ? `Android tooling reported: ${lastStartError}`
133
+ : bootstrapped
134
+ ? 'Android environment is ready.'
135
+ : starting
136
+ ? 'Android environment is starting.'
137
+ : 'Android runtime is available and will initialize on first use.',
138
+ details: status || {
139
+ initialized: runtimeSnapshot?.android?.initialized === true,
140
+ bootstrapped: false,
141
+ starting: false,
142
+ },
143
+ });
182
144
  }
183
145
 
184
146
  function getMessagingHealth(userId, app, engine, agentId = null) {
@@ -342,7 +304,9 @@ function getTaskHealth(userId, agentId = null) {
342
304
  }
343
305
 
344
306
  async function getCapabilityHealth({ userId, agentId = null, app, engine }) {
345
- const providers = await getProviderHealthCatalog(userId, agentId);
307
+ const providers = await getProviderHealthCatalog(userId, agentId, {
308
+ probeLocal: false,
309
+ });
346
310
 
347
311
  return {
348
312
  providers,
@@ -363,6 +327,8 @@ async function getCapabilityHealth({ userId, agentId = null, app, engine }) {
363
327
  }
364
328
 
365
329
  module.exports = {
330
+ getAndroidHealth,
331
+ getBrowserHealth,
366
332
  getCapabilityHealth,
367
333
  summarizeCapabilityHealth,
368
334
  };
@@ -2,7 +2,10 @@
2
2
 
3
3
  const { normalizeCompletionConfidence } = require('../completion');
4
4
  const { normalizeOutgoingMessage } = require('../messagingFallback');
5
- const { isDeferredWorkReply } = require('../terminal_reply');
5
+ const {
6
+ isDeferredWorkReply,
7
+ isTerminalQuestionOrBlockerReply,
8
+ } = require('../terminal_reply');
6
9
  const {
7
10
  summarizeAvailableTools,
8
11
  summarizeToolExecutions,
@@ -243,13 +246,19 @@ function normalizeCompletionDecision(raw, fallbackStatus = 'continue') {
243
246
  }
244
247
 
245
248
  function enforceTerminalReplyDecision(decision, lastReply) {
246
- if (decision?.status === 'continue' || !isDeferredWorkReply(lastReply)) {
247
- return decision;
249
+ if (isDeferredWorkReply(lastReply)) {
250
+ return {
251
+ status: 'continue',
252
+ reason: 'The latest reply only announces or promises unfinished work; the run must continue or return a concrete blocker.',
253
+ };
248
254
  }
249
- return {
250
- status: 'continue',
251
- reason: 'The latest reply only announces or promises unfinished work; the run must continue or return a concrete blocker.',
252
- };
255
+ if (decision?.status === 'continue' && isTerminalQuestionOrBlockerReply(lastReply)) {
256
+ return {
257
+ status: 'blocked',
258
+ reason: 'The latest reply asks for user input or states a concrete blocker, so the run must wait instead of repeating it.',
259
+ };
260
+ }
261
+ return decision;
253
262
  }
254
263
 
255
264
  // Intentionally lightweight (200-token cap, self-contained) so the model can