neoagent 3.2.0 → 3.2.1-beta.1
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/docs/agent-run-lifecycle.md +10 -0
- package/flutter_app/lib/main_operations.dart +553 -19
- package/lib/schema_migrations.js +30 -0
- package/package.json +1 -1
- package/server/db/database.js +2 -2
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +90015 -87169
- package/server/routes/agents.js +35 -2
- package/server/services/ai/loop/agent_engine_core.js +111 -9
- package/server/services/ai/loop/conversation_loop.js +123 -21
- package/server/services/ai/loop/lifecycle.js +108 -0
- package/server/services/ai/loop/model_io.js +31 -14
- package/server/services/ai/loop/tool_dispatch.js +9 -0
- package/server/services/ai/providers/anthropic.js +2 -2
- package/server/services/ai/providers/google.js +2 -2
- package/server/services/ai/providers/grok.js +2 -2
- package/server/services/ai/providers/nvidia.js +2 -2
- package/server/services/ai/providers/ollama.js +7 -6
- package/server/services/ai/providers/openai.js +2 -2
- package/server/services/ai/providers/openaiCodex.js +4 -2
- package/server/services/ai/providers/openrouter.js +2 -2
- package/server/services/messaging/formatting_guides.js +1 -0
|
@@ -145,6 +145,14 @@ async function executeReadOnlyBatch(engine, toolCalls, context = {}) {
|
|
|
145
145
|
}, { agentId });
|
|
146
146
|
const results = await Promise.all(prepared.map(async (item) => {
|
|
147
147
|
if (item.blocked) return item;
|
|
148
|
+
const runMeta = engine.getRunMeta(runId);
|
|
149
|
+
if (!runMeta || runMeta.status !== 'running') {
|
|
150
|
+
const result = { status: 'paused', reason: 'Run paused before this read-only call started.' };
|
|
151
|
+
db.prepare(
|
|
152
|
+
`UPDATE agent_steps SET status = 'paused', result = ?, completed_at = datetime('now') WHERE id = ? AND status = 'running'`,
|
|
153
|
+
).run(JSON.stringify(result), item.stepId);
|
|
154
|
+
return { ...item, result };
|
|
155
|
+
}
|
|
148
156
|
const startedAt = Date.now();
|
|
149
157
|
try {
|
|
150
158
|
const result = await executeTool(engine, item.toolName, item.toolArgs, {
|
|
@@ -162,6 +170,7 @@ async function executeReadOnlyBatch(engine, toolCalls, context = {}) {
|
|
|
162
170
|
deliveryState: options.deliveryState || null,
|
|
163
171
|
allowMultipleProactiveMessages: options.allowMultipleProactiveMessages === true,
|
|
164
172
|
allowExternalSideEffects: false,
|
|
173
|
+
signal: runMeta.abortController?.signal,
|
|
165
174
|
});
|
|
166
175
|
const error = inferToolFailureMessage(item.toolName, result);
|
|
167
176
|
const status = error ? 'failed' : 'completed';
|
|
@@ -169,7 +169,7 @@ class AnthropicProvider extends BaseProvider {
|
|
|
169
169
|
|
|
170
170
|
if (system.length > 0) params.system = system;
|
|
171
171
|
if (tools.length > 0) params.tools = this.formatTools(tools);
|
|
172
|
-
const response = await this.client.messages.create(params);
|
|
172
|
+
const response = await this.client.messages.create(params, { signal: options.signal });
|
|
173
173
|
const responseBlocks = Array.isArray(response?.content)
|
|
174
174
|
? response.content
|
|
175
175
|
: (response?.content && typeof response.content === 'object' ? [response.content] : []);
|
|
@@ -225,7 +225,7 @@ class AnthropicProvider extends BaseProvider {
|
|
|
225
225
|
|
|
226
226
|
if (system.length > 0) params.system = system;
|
|
227
227
|
if (tools.length > 0) params.tools = this.formatTools(tools);
|
|
228
|
-
const stream = await this.client.messages.stream(params);
|
|
228
|
+
const stream = await this.client.messages.stream(params, { signal: options.signal });
|
|
229
229
|
|
|
230
230
|
let content = '';
|
|
231
231
|
let currentToolCalls = [];
|
|
@@ -151,7 +151,7 @@ class GoogleProvider extends BaseProvider {
|
|
|
151
151
|
|
|
152
152
|
const lastMessage = history.pop();
|
|
153
153
|
const chat = genModel.startChat({ history });
|
|
154
|
-
const result = await chat.sendMessage(lastMessage.parts);
|
|
154
|
+
const result = await chat.sendMessage(lastMessage.parts, { signal: options.signal });
|
|
155
155
|
const response = result.response;
|
|
156
156
|
|
|
157
157
|
let content = '';
|
|
@@ -205,7 +205,7 @@ class GoogleProvider extends BaseProvider {
|
|
|
205
205
|
|
|
206
206
|
const lastMessage = history.pop();
|
|
207
207
|
const chat = genModel.startChat({ history });
|
|
208
|
-
const result = await chat.sendMessageStream(lastMessage.parts);
|
|
208
|
+
const result = await chat.sendMessageStream(lastMessage.parts, { signal: options.signal });
|
|
209
209
|
|
|
210
210
|
let content = '';
|
|
211
211
|
const toolCalls = [];
|
|
@@ -61,7 +61,7 @@ class GrokProvider extends OpenAICompatibleProvider {
|
|
|
61
61
|
const model = options.model || 'grok-4-1-fast-reasoning';
|
|
62
62
|
const params = this._buildParams(model, messages, tools, options);
|
|
63
63
|
|
|
64
|
-
const response = await this.client.chat.completions.create(params);
|
|
64
|
+
const response = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
65
65
|
return this.normalizeResponse(response);
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -73,7 +73,7 @@ class GrokProvider extends OpenAICompatibleProvider {
|
|
|
73
73
|
stream_options: { include_usage: true }
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
const stream = await this.client.chat.completions.create(params);
|
|
76
|
+
const stream = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
77
77
|
|
|
78
78
|
let toolCalls = [];
|
|
79
79
|
let content = '';
|
|
@@ -76,7 +76,7 @@ class NvidiaProvider extends OpenAICompatibleProvider {
|
|
|
76
76
|
const params = this._buildParams(model, messages, tools, options);
|
|
77
77
|
let response;
|
|
78
78
|
try {
|
|
79
|
-
response = await this.client.chat.completions.create(params);
|
|
79
|
+
response = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
80
80
|
} catch (err) {
|
|
81
81
|
throw new Error(`NVIDIA NIM request failed: ${err?.message || String(err)}`);
|
|
82
82
|
}
|
|
@@ -93,7 +93,7 @@ class NvidiaProvider extends OpenAICompatibleProvider {
|
|
|
93
93
|
|
|
94
94
|
let stream;
|
|
95
95
|
try {
|
|
96
|
-
stream = await this.client.chat.completions.create(params);
|
|
96
|
+
stream = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
97
97
|
} catch (err) {
|
|
98
98
|
throw new Error(`NVIDIA NIM request failed: ${err?.message || String(err)}`);
|
|
99
99
|
}
|
|
@@ -122,11 +122,12 @@ class OllamaProvider extends BaseProvider {
|
|
|
122
122
|
// status for others; surface both as real errors instead of letting callers
|
|
123
123
|
// see a silently empty response. Tags models that reject tools so the caller
|
|
124
124
|
// can transparently retry without them.
|
|
125
|
-
async postChat(body) {
|
|
125
|
+
async postChat(body, signal = null) {
|
|
126
126
|
const res = await fetch(`${this.baseUrl}/api/chat`, {
|
|
127
127
|
method: 'POST',
|
|
128
128
|
headers: { 'Content-Type': 'application/json' },
|
|
129
|
-
body: JSON.stringify(body)
|
|
129
|
+
body: JSON.stringify(body),
|
|
130
|
+
signal,
|
|
130
131
|
});
|
|
131
132
|
if (!res.ok) {
|
|
132
133
|
const detail = await res.text().catch(() => '');
|
|
@@ -147,11 +148,11 @@ class OllamaProvider extends BaseProvider {
|
|
|
147
148
|
|
|
148
149
|
let res;
|
|
149
150
|
try {
|
|
150
|
-
res = await this.postChat(this.buildChatBody(messages, tools, { ...options, model }, false));
|
|
151
|
+
res = await this.postChat(this.buildChatBody(messages, tools, { ...options, model }, false), options.signal);
|
|
151
152
|
} catch (err) {
|
|
152
153
|
if (err.code === 'OLLAMA_TOOLS_UNSUPPORTED' && tools.length > 0) {
|
|
153
154
|
console.warn(`[Ollama] Model '${model}' does not support tools; retrying without them.`);
|
|
154
|
-
res = await this.postChat(this.buildChatBody(messages, [], { ...options, model }, false));
|
|
155
|
+
res = await this.postChat(this.buildChatBody(messages, [], { ...options, model }, false), options.signal);
|
|
155
156
|
} else {
|
|
156
157
|
throw err;
|
|
157
158
|
}
|
|
@@ -186,11 +187,11 @@ class OllamaProvider extends BaseProvider {
|
|
|
186
187
|
|
|
187
188
|
let res;
|
|
188
189
|
try {
|
|
189
|
-
res = await this.postChat(this.buildChatBody(messages, tools, { ...options, model }, true));
|
|
190
|
+
res = await this.postChat(this.buildChatBody(messages, tools, { ...options, model }, true), options.signal);
|
|
190
191
|
} catch (err) {
|
|
191
192
|
if (err.code === 'OLLAMA_TOOLS_UNSUPPORTED' && tools.length > 0) {
|
|
192
193
|
console.warn(`[Ollama] Model '${model}' does not support tools; retrying stream without them.`);
|
|
193
|
-
res = await this.postChat(this.buildChatBody(messages, [], { ...options, model }, true));
|
|
194
|
+
res = await this.postChat(this.buildChatBody(messages, [], { ...options, model }, true), options.signal);
|
|
194
195
|
} else {
|
|
195
196
|
throw err;
|
|
196
197
|
}
|
|
@@ -108,7 +108,7 @@ class OpenAIProvider extends OpenAICompatibleProvider {
|
|
|
108
108
|
const model = options.model || this.config.model || this.getDefaultModel();
|
|
109
109
|
const params = this._buildParams(model, messages, tools, options);
|
|
110
110
|
|
|
111
|
-
const response = await this.client.chat.completions.create(params);
|
|
111
|
+
const response = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
112
112
|
const choice = response.choices[0];
|
|
113
113
|
|
|
114
114
|
return {
|
|
@@ -125,7 +125,7 @@ class OpenAIProvider extends OpenAICompatibleProvider {
|
|
|
125
125
|
const params = this._buildParams(model, messages, tools, options);
|
|
126
126
|
params.stream = true;
|
|
127
127
|
params.stream_options = { include_usage: true };
|
|
128
|
-
const stream = await this.client.chat.completions.create(params);
|
|
128
|
+
const stream = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
129
129
|
|
|
130
130
|
let currentToolCalls = [];
|
|
131
131
|
let content = '';
|
|
@@ -436,9 +436,10 @@ class OpenAICodexProvider extends BaseProvider {
|
|
|
436
436
|
try {
|
|
437
437
|
response = await this.client.responses.create(
|
|
438
438
|
{ model, ...request },
|
|
439
|
-
{ headers: this._requestHeaders() },
|
|
439
|
+
{ headers: this._requestHeaders(), signal: options.signal },
|
|
440
440
|
);
|
|
441
441
|
} catch (err) {
|
|
442
|
+
if (options.signal?.aborted) throw err;
|
|
442
443
|
throw new Error(`OpenAI Codex request failed: ${formatOpenAIError(err)}`);
|
|
443
444
|
}
|
|
444
445
|
|
|
@@ -464,9 +465,10 @@ class OpenAICodexProvider extends BaseProvider {
|
|
|
464
465
|
try {
|
|
465
466
|
stream = await this.client.responses.create(
|
|
466
467
|
{ model, ...request, stream: true },
|
|
467
|
-
{ headers: this._requestHeaders() },
|
|
468
|
+
{ headers: this._requestHeaders(), signal: options.signal },
|
|
468
469
|
);
|
|
469
470
|
} catch (err) {
|
|
471
|
+
if (options.signal?.aborted) throw err;
|
|
470
472
|
throw new Error(`OpenAI Codex request failed: ${formatOpenAIError(err)}`);
|
|
471
473
|
}
|
|
472
474
|
|
|
@@ -72,7 +72,7 @@ class OpenRouterProvider extends OpenAICompatibleProvider {
|
|
|
72
72
|
const params = this._buildParams(model, messages, tools, options);
|
|
73
73
|
let response;
|
|
74
74
|
try {
|
|
75
|
-
response = await this.client.chat.completions.create(params);
|
|
75
|
+
response = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
76
76
|
} catch (err) {
|
|
77
77
|
throw new Error(`OpenRouter request failed: ${err?.message || String(err)}`);
|
|
78
78
|
}
|
|
@@ -95,7 +95,7 @@ class OpenRouterProvider extends OpenAICompatibleProvider {
|
|
|
95
95
|
|
|
96
96
|
let stream;
|
|
97
97
|
try {
|
|
98
|
-
stream = await this.client.chat.completions.create(params);
|
|
98
|
+
stream = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
99
99
|
} catch (err) {
|
|
100
100
|
throw new Error(`OpenRouter request failed: ${err?.message || String(err)}`);
|
|
101
101
|
}
|
|
@@ -86,6 +86,7 @@ function normalizeVisualMarkdown(text, { inlineCode = true } = {}) {
|
|
|
86
86
|
|
|
87
87
|
function adaptWhatsAppFormatting(text) {
|
|
88
88
|
return normalizeVisualMarkdown(text, { inlineCode: true })
|
|
89
|
+
.replace(/[ \t]*(?:\\n|\/n(?![a-zA-Z0-9_]))[ \t]*/g, '\n')
|
|
89
90
|
.replace(/[ \t]+\n/g, '\n')
|
|
90
91
|
.replace(/\n{3,}/g, '\n\n')
|
|
91
92
|
.trim();
|