neoagent 2.5.2-beta.0 → 2.5.2-beta.10

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.
@@ -9,6 +9,7 @@ const {
9
9
  normalizeOutgoingMessageForPlatform,
10
10
  } = require('../messaging/formatting_guides');
11
11
  const { INTERIM_KINDS, normalizeInterimKind } = require('./interim');
12
+ const { normalizeWhatsAppId } = require('../../utils/whatsapp');
12
13
  const {
13
14
  executeIntegratedTool,
14
15
  getIntegratedToolDefinitions,
@@ -255,22 +256,32 @@ function hasAlreadySentProactiveMessage({ triggerSource, runState, deliveryState
255
256
  }
256
257
 
257
258
  function markProactiveMessageSent({ runState, deliveryState, content }) {
258
- const message = String(content || '');
259
- if (runState) {
260
- runState.messagingSent = true;
261
- runState.lastSentMessage = message;
262
- if (Array.isArray(runState.sentMessages)) {
263
- runState.sentMessages.push(message);
264
- }
259
+ const message = String(content || '');
260
+ const sentAt = new Date().toISOString();
261
+ if (runState) {
262
+ runState.messagingSent = true;
263
+ runState.finalDeliverySent = true;
264
+ runState.lastFinalDeliveryAt = sentAt;
265
+ if (runState.progressLedger && typeof runState.progressLedger === 'object') {
266
+ runState.progressLedger.lastUserVisibleUpdateAt = sentAt;
267
+ runState.progressLedger.lastFinalDeliveryAt = sentAt;
268
+ runState.progressLedger.progressState = 'complete';
265
269
  }
266
-
267
- if (deliveryState) {
268
- deliveryState.messagingSent = true;
269
- deliveryState.lastSentMessage = message;
270
- if (!Array.isArray(deliveryState.sentMessages)) {
271
- deliveryState.sentMessages = [];
272
- }
273
- deliveryState.sentMessages.push(message);
270
+ runState.lastSentMessage = message;
271
+ if (Array.isArray(runState.sentMessages)) {
272
+ runState.sentMessages.push(message);
273
+ }
274
+ }
275
+
276
+ if (deliveryState) {
277
+ deliveryState.messagingSent = true;
278
+ deliveryState.finalDeliverySent = true;
279
+ deliveryState.lastFinalDeliveryAt = sentAt;
280
+ deliveryState.lastSentMessage = message;
281
+ if (!Array.isArray(deliveryState.sentMessages)) {
282
+ deliveryState.sentMessages = [];
283
+ }
284
+ deliveryState.sentMessages.push(message);
274
285
  }
275
286
  }
276
287
 
@@ -310,6 +321,31 @@ function normalizeMessagingTarget(target = {}) {
310
321
  return { platform, to };
311
322
  }
312
323
 
324
+ function canonicalMessagingAddress(platform, value) {
325
+ const normalizedPlatform = String(platform || '').trim().toLowerCase();
326
+ const raw = String(value || '').trim();
327
+ if (!normalizedPlatform || !raw) return '';
328
+ if (normalizedPlatform !== 'whatsapp') return raw;
329
+
330
+ const lower = raw.toLowerCase();
331
+ const normalizedId = normalizeWhatsAppId(lower);
332
+ if (!normalizedId) return '';
333
+ if (lower.includes('@g.us')) return `group:${normalizedId}`;
334
+ if (lower.includes('@lid')) return `lid:${normalizedId}`;
335
+ return `direct:${normalizedId}`;
336
+ }
337
+
338
+ function isOriginMessagingDelivery({ triggerSource, source, chatId, platform, to }) {
339
+ if (triggerSource !== 'messaging') return true;
340
+ const originPlatform = String(source || '').trim().toLowerCase();
341
+ const targetPlatform = String(platform || '').trim().toLowerCase();
342
+ if (!originPlatform || !targetPlatform || originPlatform !== targetPlatform) return false;
343
+
344
+ const originAddress = canonicalMessagingAddress(originPlatform, chatId);
345
+ const targetAddress = canonicalMessagingAddress(targetPlatform, to);
346
+ return Boolean(originAddress && targetAddress && originAddress === targetAddress);
347
+ }
348
+
313
349
  function buildAndroidUiMatchProperties(extra = {}) {
314
350
  return {
315
351
  x: { type: 'number', description: 'Absolute X coordinate' },
@@ -1613,6 +1649,7 @@ async function executeTool(toolName, args, context, engine) {
1613
1649
  case 'browser_extract': {
1614
1650
  const { provider, backend } = await bc();
1615
1651
  if (!provider) return { error: 'Browser controller not available' };
1652
+ if (!args.selector) return { error: 'browser_extract requires a "selector" argument' };
1616
1653
  return { ...await provider.extract(args.selector, args.attribute, args.all), backend };
1617
1654
  }
1618
1655
 
@@ -1625,7 +1662,9 @@ async function executeTool(toolName, args, context, engine) {
1625
1662
  case 'browser_evaluate': {
1626
1663
  const { provider, backend } = await bc();
1627
1664
  if (!provider) return { error: 'Browser controller not available' };
1628
- return { ...await provider.evaluate(args.script), backend };
1665
+ const script = args.script ?? args.javascript;
1666
+ if (!script) return { error: 'browser_evaluate requires a "script" argument' };
1667
+ return { ...await provider.evaluate(script), backend };
1629
1668
  }
1630
1669
 
1631
1670
  case 'android_start_emulator': {
@@ -2234,7 +2273,18 @@ async function executeTool(toolName, args, context, engine) {
2234
2273
  persistConversation: triggerSource === 'schedule' || triggerSource === 'tasks'
2235
2274
  });
2236
2275
  // Track that the agent explicitly sent a message during this run
2237
- if (!suppressReply && sendResult?.suppressed !== true) {
2276
+ if (
2277
+ !suppressReply
2278
+ && sendResult?.success === true
2279
+ && sendResult?.suppressed !== true
2280
+ && isOriginMessagingDelivery({
2281
+ triggerSource,
2282
+ source: context.source,
2283
+ chatId: context.chatId,
2284
+ platform: args.platform,
2285
+ to: args.to,
2286
+ })
2287
+ ) {
2238
2288
  markProactiveMessageSent({ runState, deliveryState, content: normalizedMessage });
2239
2289
  if (runState && triggerSource === 'messaging') {
2240
2290
  runState.explicitMessageSent = true;
@@ -515,6 +515,13 @@ class MessagingManager extends EventEmitter {
515
515
  }
516
516
 
517
517
  const result = await platform.sendMessage(to, normalizedContent, sendOptions);
518
+ if (result?.success === false) {
519
+ const reason = result.error || result.reason || 'platform rejected the message';
520
+ const error = new Error(`Platform ${platformName} delivery failed: ${reason}`);
521
+ error.code = 'MESSAGING_DELIVERY_FAILED';
522
+ error.deliveryResult = result;
523
+ throw error;
524
+ }
518
525
 
519
526
  db.prepare('INSERT INTO messages (user_id, agent_id, run_id, role, content, platform, platform_chat_id, media_path, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
520
527
  .run(userId, agentId, runId, 'assistant', normalizedContent, platformName, to, mediaPath, metadata ? JSON.stringify(metadata) : null);
@@ -250,8 +250,8 @@ class VmBrowserProvider {
250
250
  async typeText(text, options = {}) { return this.#materialize(await this.client.request('POST', '/browser/type-text', { text, ...options })); }
251
251
  async pressKey(key, screenshot = true) { return this.#materialize(await this.client.request('POST', '/browser/press-key', { key, screenshot })); }
252
252
  async scroll(deltaX, deltaY, screenshot = true) { return this.#materialize(await this.client.request('POST', '/browser/scroll', { deltaX, deltaY, screenshot })); }
253
- extract(selector, attribute, all = false) { return this.client.request('POST', '/browser/extract', { selector, attribute, all }); }
254
- evaluate(script) { return this.client.request('POST', '/browser/execute', { code: script }); }
253
+ async extract(selector, attribute, all = false) { return this.client.request('POST', '/browser/extract', { selector, attribute, all }); }
254
+ async evaluate(script) { return this.client.request('POST', '/browser/execute', { code: script }); }
255
255
  async screenshot(options = {}) { return this.#materialize(await this.client.request('POST', '/browser/screenshot', options)); }
256
256
  async screenshotJpeg(quality = 80, options = {}) {
257
257
  const result = await this.client.request('POST', '/browser/screenshot-jpeg', { ...options, quality });
@@ -259,11 +259,11 @@ class VmBrowserProvider {
259
259
  if (!content) throw new Error('VM browser screenshot-jpeg returned no data.');
260
260
  return Buffer.from(content, 'base64');
261
261
  }
262
- launch(options = {}) { return this.client.request('POST', '/browser/launch', options); }
263
- closeBrowser() { return this.client.request('POST', '/browser/close'); }
264
- fill(selector, value) { return this.type(selector, value); }
265
- extractContent(options = {}) { return this.client.request('POST', '/browser/extract', options); }
266
- executeJS(code) { return this.evaluate(code); }
262
+ async launch(options = {}) { return this.client.request('POST', '/browser/launch', options); }
263
+ async closeBrowser() { return this.client.request('POST', '/browser/close'); }
264
+ async fill(selector, value) { return this.type(selector, value); }
265
+ async extractContent(options = {}) { return this.client.request('POST', '/browser/extract', options); }
266
+ async executeJS(code) { return this.evaluate(code); }
267
267
  async getPageInfo() {
268
268
  const status = await this.client.request('GET', '/browser/status');
269
269
  this.headless = status?.headless !== false;