neoagent 3.2.1-beta.1 → 3.2.1-beta.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "3.2.1-beta.1",
3
+ "version": "3.2.1-beta.2",
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",
@@ -1 +1 @@
1
- 93bc60f11ff8c552f052d374db0c2c06
1
+ 1c5d94e505331e4ad70cf059dc621b70
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"69c8c61792f04cc809dfef0c910414fb9afc06
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "1664863442" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "1866288644" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -143689,7 +143689,7 @@ if(r){r=s.d
143689
143689
  r===$&&A.b()
143690
143690
  p.push(A.jt(q,A.jw(!1,new A.Y(B.wO,A.cS(new A.cF(B.kE,new A.acS(r,q),q),q,q),q),!1,B.I,!0),q,q,0,0,0,q))}if(!s.ay){r=s.e
143691
143691
  r===$&&A.b()
143692
- r=B.b.t("mrwbc2ah-7309fe8").length!==0&&r.b}else r=!1
143692
+ r=B.b.t("mrwc4gsw-5a86a62").length!==0&&r.b}else r=!1
143693
143693
  if(r){r=s.d
143694
143694
  r===$&&A.b()
143695
143695
  r=r.aU&&!r.ag?84:0
@@ -149556,7 +149556,7 @@ $S:0}
149556
149556
  A.a3g.prototype={}
149557
149557
  A.WA.prototype={
149558
149558
  r6(a){var s=this
149559
- if(B.b.t("mrwbc2ah-7309fe8").length===0||s.a!=null)return
149559
+ if(B.b.t("mrwc4gsw-5a86a62").length===0||s.a!=null)return
149560
149560
  s.Cn()
149561
149561
  s.a=A.mJ(B.Y6,new A.bmI(s))},
149562
149562
  Cn(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -149574,7 +149574,7 @@ if(!t.f.b(k)){s=1
149574
149574
  break}i=J.a3(k,"buildId")
149575
149575
  h=i==null?null:B.b.t(J.q(i))
149576
149576
  j=h==null?"":h
149577
- if(J.br(j)===0||J.e(j,"mrwbc2ah-7309fe8")){s=1
149577
+ if(J.br(j)===0||J.e(j,"mrwc4gsw-5a86a62")){s=1
149578
149578
  break}n.b=!0
149579
149579
  n.H()
149580
149580
  p=2
@@ -149591,7 +149591,7 @@ case 2:return A.i(o.at(-1),r)}})
149591
149591
  return A.k($async$Cn,r)},
149592
149592
  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
149593
149593
  var $async$wV=A.h(function(a2,a3){if(a2===1){o.push(a3)
149594
- s=p}for(;;)switch(s){case 0:if(B.b.t("mrwbc2ah-7309fe8").length===0||n.c){s=1
149594
+ s=p}for(;;)switch(s){case 0:if(B.b.t("mrwc4gsw-5a86a62").length===0||n.c){s=1
149595
149595
  break}n.c=!0
149596
149596
  n.H()
149597
149597
  p=4
@@ -1119,6 +1119,15 @@ class AgentEngine {
1119
1119
 
1120
1120
  await new Promise((resolve) => { runMeta.resumeRun = resolve; });
1121
1121
  runMeta.resumeRun = null;
1122
+ if (runMeta.status === 'stopped' || runMeta.status === 'interrupted') {
1123
+ return { action: runMeta.status === 'interrupted' ? 'interrupt' : 'stop' };
1124
+ }
1125
+ const persistedStatus = db.prepare('SELECT status FROM agent_runs WHERE id = ?').get(runId)?.status;
1126
+ if (persistedStatus !== 'running') {
1127
+ const action = persistedStatus === 'interrupted' ? 'interrupt' : 'stop';
1128
+ runMeta.status = persistedStatus || 'stopped';
1129
+ return { action };
1130
+ }
1122
1131
  runMeta.abortController = new AbortController();
1123
1132
  runMeta.status = 'running';
1124
1133
  this.startMessagingProgressSupervisor(runId);
@@ -1665,6 +1674,7 @@ class AgentEngine {
1665
1674
  runMeta.stopReason = reason;
1666
1675
  runMeta.aborted = true;
1667
1676
  runMeta.abortController?.abort(reason);
1677
+ runMeta.resumeRun?.();
1668
1678
  this.emit(runMeta.userId, 'run:stopping', { runId });
1669
1679
  for (const pid of runMeta.toolPids) {
1670
1680
  if (this.runtimeManager && typeof this.runtimeManager.killCommand === 'function') {
@@ -1708,6 +1718,7 @@ class AgentEngine {
1708
1718
  runMeta.status = 'stopped';
1709
1719
  runMeta.aborted = true;
1710
1720
  runMeta.abortController?.abort('Run stopped.');
1721
+ runMeta.resumeRun?.();
1711
1722
  this.emit(runMeta.userId, 'run:stopping', { runId });
1712
1723
  for (const pid of runMeta.toolPids) {
1713
1724
  if (this.runtimeManager && typeof this.runtimeManager.killCommand === 'function') {
@@ -62,7 +62,11 @@ async function requestStructuredJson(engine, {
62
62
  }) {
63
63
  const startedAt = Date.now();
64
64
  const structuredStep = `model:${phase}`;
65
- const signal = telemetry?.signal;
65
+ const modelAbortController = new AbortController();
66
+ const parentSignal = telemetry?.signal;
67
+ const abortFromParent = () => modelAbortController.abort(parentSignal?.reason);
68
+ if (parentSignal?.aborted) abortFromParent();
69
+ else parentSignal?.addEventListener('abort', abortFromParent, { once: true });
66
70
  if (telemetry?.runId) {
67
71
  engine.updateRunProgress(telemetry.runId, {
68
72
  currentPhase: 'model',
@@ -86,13 +90,16 @@ async function requestStructuredJson(engine, {
86
90
  model,
87
91
  maxTokens,
88
92
  reasoningEffort: reasoningEffort || engine.getReasoningEffort(providerName, {}),
89
- signal,
93
+ signal: modelAbortController.signal,
90
94
  }
91
95
  ),
92
- telemetry || {},
96
+ { ...(telemetry || {}), modelAbortController },
93
97
  `${phase} model call`,
94
98
  ),
95
- { label: `Engine ${model} (structured)` }
99
+ {
100
+ label: `Engine ${model} (structured)`,
101
+ isRetryable: (err) => !modelAbortController.signal.aborted && isTransientError(err),
102
+ }
96
103
  );
97
104
  completed = true;
98
105
  if (telemetry?.runId && telemetry?.userId) {
@@ -117,6 +124,7 @@ async function requestStructuredJson(engine, {
117
124
  usage: normalizedUsage?.totalTokens || 0,
118
125
  };
119
126
  } finally {
127
+ parentSignal?.removeEventListener('abort', abortFromParent);
120
128
  const runMeta = telemetry?.runId ? engine.getRunMeta(telemetry.runId) : null;
121
129
  if (runMeta?.progressLedger?.currentStep === structuredStep) {
122
130
  engine.updateRunProgress(telemetry.runId, {
@@ -102,7 +102,7 @@ function persistEnvValue(key, value) {
102
102
  } catch { }
103
103
  }
104
104
 
105
- async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch) {
105
+ async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch, signal = null) {
106
106
  if (!refreshToken) return null;
107
107
  const response = await fetchImpl(CLAUDE_CODE_TOKEN_URL, {
108
108
  method: 'POST',
@@ -116,6 +116,7 @@ async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch) {
116
116
  refresh_token: refreshToken,
117
117
  client_id: CLAUDE_CODE_CLIENT_ID,
118
118
  }),
119
+ signal,
119
120
  });
120
121
 
121
122
  const text = await response.text();
@@ -209,8 +210,8 @@ class ClaudeCodeProvider extends AnthropicProvider {
209
210
  });
210
211
  }
211
212
 
212
- async refreshClient() {
213
- const refreshed = await refreshClaudeCodeAccessToken(this.refreshToken, this.fetchImpl);
213
+ async refreshClient(signal = null) {
214
+ const refreshed = await refreshClaudeCodeAccessToken(this.refreshToken, this.fetchImpl, signal);
214
215
  if (!refreshed?.access) return false;
215
216
  this.authToken = refreshed.access;
216
217
  this.refreshToken = refreshed.refresh || this.refreshToken;
@@ -244,7 +245,7 @@ class ClaudeCodeProvider extends AnthropicProvider {
244
245
  if ((!isAuthenticationError(err) && !isInferenceScopeError(err)) || !this.refreshToken) {
245
246
  throw formatClaudeCodeCredentialError(err);
246
247
  }
247
- await this.refreshClient();
248
+ await this.refreshClient(options.signal);
248
249
  try {
249
250
  return await super.chat(messages, tools, options);
250
251
  } catch (retryErr) {
@@ -260,7 +261,7 @@ class ClaudeCodeProvider extends AnthropicProvider {
260
261
  if ((!isAuthenticationError(err) && !isInferenceScopeError(err)) || !this.refreshToken) {
261
262
  throw formatClaudeCodeCredentialError(err);
262
263
  }
263
- await this.refreshClient();
264
+ await this.refreshClient(options.signal);
264
265
  try {
265
266
  yield* super.stream(messages, tools, options);
266
267
  } catch (retryErr) {
@@ -26,7 +26,7 @@ class GithubCopilotProvider extends OpenAIProvider {
26
26
  this._refreshPromise = null;
27
27
  }
28
28
 
29
- async _refreshCopilotToken() {
29
+ async _refreshCopilotToken(signal = null) {
30
30
  if (this._refreshPromise) return this._refreshPromise;
31
31
 
32
32
  const now = Math.floor(Date.now() / 1000);
@@ -46,7 +46,8 @@ class GithubCopilotProvider extends OpenAIProvider {
46
46
  'Authorization': `token ${this.githubToken}`,
47
47
  'Accept': 'application/json',
48
48
  'User-Agent': 'NeoAgent/1.0.0'
49
- }
49
+ },
50
+ signal,
50
51
  });
51
52
 
52
53
  if (!res.ok) {
@@ -79,17 +80,17 @@ class GithubCopilotProvider extends OpenAIProvider {
79
80
  }
80
81
 
81
82
  async chat(messages, tools = [], options = {}) {
82
- await this._refreshCopilotToken();
83
+ await this._refreshCopilotToken(options.signal);
83
84
  return super.chat(messages, tools, options);
84
85
  }
85
86
 
86
87
  async *stream(messages, tools = [], options = {}) {
87
- await this._refreshCopilotToken();
88
+ await this._refreshCopilotToken(options.signal);
88
89
  yield* super.stream(messages, tools, options);
89
90
  }
90
91
 
91
92
  async analyzeImage(options = {}) {
92
- await this._refreshCopilotToken();
93
+ await this._refreshCopilotToken(options.signal);
93
94
  return super.analyzeImage(options);
94
95
  }
95
96
  }
@@ -41,7 +41,7 @@ function persistEnvValue(key, value) {
41
41
  } catch { }
42
42
  }
43
43
 
44
- async function refreshGrokOAuthAccessToken(refreshToken, fetchImpl = fetch) {
44
+ async function refreshGrokOAuthAccessToken(refreshToken, fetchImpl = fetch, signal = null) {
45
45
  if (!refreshToken) return null;
46
46
  const response = await fetchImpl(GROK_OAUTH_TOKEN_URL, {
47
47
  method: 'POST',
@@ -54,6 +54,7 @@ async function refreshGrokOAuthAccessToken(refreshToken, fetchImpl = fetch) {
54
54
  refresh_token: refreshToken,
55
55
  client_id: GROK_OAUTH_CLIENT_ID,
56
56
  }),
57
+ signal,
57
58
  });
58
59
 
59
60
  const text = await response.text();
@@ -102,8 +103,8 @@ class GrokOAuthProvider extends GrokProvider {
102
103
  this.fetchImpl = config.fetch || fetch;
103
104
  }
104
105
 
105
- async refreshClient() {
106
- const refreshed = await refreshGrokOAuthAccessToken(this.refreshToken, this.fetchImpl);
106
+ async refreshClient(signal = null) {
107
+ const refreshed = await refreshGrokOAuthAccessToken(this.refreshToken, this.fetchImpl, signal);
107
108
  if (!refreshed?.access) return false;
108
109
  this.authToken = refreshed.access;
109
110
  this.refreshToken = refreshed.refresh || this.refreshToken;
@@ -122,7 +123,7 @@ class GrokOAuthProvider extends GrokProvider {
122
123
  return await super.chat(messages, tools, options);
123
124
  } catch (err) {
124
125
  if (err?.status !== 401 || !this.refreshToken) throw err;
125
- await this.refreshClient();
126
+ await this.refreshClient(options.signal);
126
127
  return await super.chat(messages, tools, options);
127
128
  }
128
129
  }
@@ -132,7 +133,7 @@ class GrokOAuthProvider extends GrokProvider {
132
133
  yield* super.stream(messages, tools, options);
133
134
  } catch (err) {
134
135
  if (err?.status !== 401 || !this.refreshToken) throw err;
135
- await this.refreshClient();
136
+ await this.refreshClient(options.signal);
136
137
  yield* super.stream(messages, tools, options);
137
138
  }
138
139
  }
@@ -8,22 +8,28 @@ class OllamaProvider extends BaseProvider {
8
8
  this.models = [];
9
9
  }
10
10
 
11
- async listModels() {
11
+ async listModels(signal = null) {
12
+ const controller = new AbortController();
13
+ const abortFromParent = () => controller.abort(signal?.reason);
14
+ if (signal?.aborted) abortFromParent();
15
+ else signal?.addEventListener('abort', abortFromParent, { once: true });
16
+ const timer = setTimeout(() => controller.abort(), 5000);
12
17
  try {
13
- const controller = new AbortController();
14
- const timer = setTimeout(() => controller.abort(), 5000);
15
18
  const res = await fetch(`${this.baseUrl}/api/tags`, { signal: controller.signal });
16
- clearTimeout(timer);
17
19
  const data = await res.json();
18
20
  this.models = (data.models || []).map(m => m.name);
19
21
  return this.models;
20
- } catch {
22
+ } catch (err) {
23
+ if (signal?.aborted) throw err;
21
24
  return [];
25
+ } finally {
26
+ clearTimeout(timer);
27
+ signal?.removeEventListener('abort', abortFromParent);
22
28
  }
23
29
  }
24
30
 
25
- async ensureModel(model) {
26
- const models = await this.listModels();
31
+ async ensureModel(model, signal = null) {
32
+ const models = await this.listModels(signal);
27
33
  // Normalization: Ollama often adds :latest if no tag is specified
28
34
  const normalizedModel = model.includes(':') ? model : `${model}:latest`;
29
35
  const found = models.some(m => m === model || m === normalizedModel);
@@ -42,7 +48,8 @@ class OllamaProvider extends BaseProvider {
42
48
  const res = await fetch(`${this.baseUrl}/api/pull`, {
43
49
  method: 'POST',
44
50
  headers: { 'Content-Type': 'application/json' },
45
- body: JSON.stringify({ name: model, stream: false })
51
+ body: JSON.stringify({ name: model, stream: false }),
52
+ signal,
46
53
  });
47
54
  if (!res.ok) throw new Error(`Pull failed: ${res.statusText}`);
48
55
  console.log(`[Ollama] Model '${model}' pulled successfully.`);
@@ -54,7 +61,7 @@ class OllamaProvider extends BaseProvider {
54
61
  message: `Local Ollama model '${model}' is ready.`
55
62
  });
56
63
  // Refresh local model list
57
- await this.listModels();
64
+ await this.listModels(signal);
58
65
  return true;
59
66
  } catch (e) {
60
67
  this.onStatus?.({
@@ -144,7 +151,7 @@ class OllamaProvider extends BaseProvider {
144
151
 
145
152
  async chat(messages, tools = [], options = {}) {
146
153
  const model = options.model || this.config.model || 'llama3.1';
147
- await this.ensureModel(model);
154
+ await this.ensureModel(model, options.signal);
148
155
 
149
156
  let res;
150
157
  try {
@@ -183,7 +190,7 @@ class OllamaProvider extends BaseProvider {
183
190
 
184
191
  async *stream(messages, tools = [], options = {}) {
185
192
  const model = options.model || this.config.model || 'llama3.1';
186
- await this.ensureModel(model);
193
+ await this.ensureModel(model, options.signal);
187
194
 
188
195
  let res;
189
196
  try {