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.
@@ -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();