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 +2 -2
- package/server/http/routes.js +7 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +5 -5
- package/server/services/ai/capabilityHealth.js +62 -96
- package/server/services/ai/loop/completion_judge.js +16 -7
- package/server/services/ai/loop/conversation_loop.js +131 -122
- package/server/services/ai/model_failure_cache.js +108 -0
- package/server/services/ai/models.js +1 -1
- package/server/services/ai/provider_selector.js +58 -3
- package/server/services/ai/providers/google.js +112 -92
- package/server/services/ai/terminal_reply.js +13 -1
- package/server/services/runtime/docker-vm-manager.js +9 -0
- package/server/services/runtime/guest_bootstrap.js +7 -0
- package/server/services/runtime/guest_image.js +39 -9
- package/server/services/runtime/manager.js +35 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neoagent",
|
|
3
|
-
"version": "3.2.1-beta.
|
|
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/
|
|
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",
|
package/server/http/routes.js
CHANGED
|
@@ -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
|
-
|
|
1
|
+
38fc05bea2f5bf14dda4c0f58b31ff9a
|
|
@@ -33,10 +33,10 @@ addEventListener("message", eventListener);
|
|
|
33
33
|
if (!window._flutter) {
|
|
34
34
|
window._flutter = {};
|
|
35
35
|
}
|
|
36
|
-
_flutter.buildConfig = {"engineRevision":"
|
|
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: "
|
|
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/
|
|
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("
|
|
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("
|
|
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,"
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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:
|
|
88
|
+
connected: vmInitialized || extensionConnected,
|
|
107
89
|
configured: true,
|
|
108
|
-
healthy:
|
|
109
|
-
degraded:
|
|
110
|
-
summary:
|
|
111
|
-
?
|
|
112
|
-
:
|
|
113
|
-
?
|
|
114
|
-
:
|
|
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
|
|
117
|
-
backend:
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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 {
|
|
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 (
|
|
247
|
-
return
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|