neoagent 3.2.1-beta.2 → 3.2.1-beta.3

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.
Files changed (144) hide show
  1. package/extensions/chrome-browser/background.mjs +318 -88
  2. package/extensions/chrome-browser/http.mjs +136 -0
  3. package/extensions/chrome-browser/protocol.mjs +511 -89
  4. package/flutter_app/lib/main_chat.dart +118 -739
  5. package/flutter_app/lib/main_controller.dart +29 -20
  6. package/flutter_app/lib/main_models.dart +3 -0
  7. package/flutter_app/lib/main_operations.dart +334 -321
  8. package/flutter_app/lib/main_settings.dart +4 -3
  9. package/flutter_app/lib/main_shared.dart +14 -11
  10. package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
  11. package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
  12. package/flutter_app/windows/runner/flutter_window.cpp +143 -32
  13. package/lib/manager.js +106 -89
  14. package/lib/schema_migrations.js +67 -13
  15. package/package.json +20 -13
  16. package/runtime/paths.js +49 -5
  17. package/server/guest_agent.js +52 -30
  18. package/server/http/middleware.js +24 -0
  19. package/server/http/routes.js +4 -6
  20. package/server/public/.last_build_id +1 -1
  21. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  22. package/server/public/flutter_bootstrap.js +1 -1
  23. package/server/public/main.dart.js +30803 -30805
  24. package/server/routes/admin.js +1 -1
  25. package/server/routes/android.js +30 -34
  26. package/server/routes/browser.js +23 -15
  27. package/server/routes/desktop.js +18 -1
  28. package/server/routes/integrations.js +5 -1
  29. package/server/routes/memory.js +1 -0
  30. package/server/routes/settings.js +16 -5
  31. package/server/routes/social_reach.js +12 -3
  32. package/server/routes/social_video.js +4 -0
  33. package/server/services/ai/compaction.js +7 -2
  34. package/server/services/ai/history.js +44 -5
  35. package/server/services/ai/integrated_tools/http_request.js +8 -0
  36. package/server/services/ai/loop/agent_engine_core.js +347 -162
  37. package/server/services/ai/loop/callbacks.js +1 -0
  38. package/server/services/ai/loop/completion_judge.js +12 -0
  39. package/server/services/ai/loop/conversation_loop.js +348 -242
  40. package/server/services/ai/loop/messaging_delivery.js +129 -57
  41. package/server/services/ai/loop/model_call_guard.js +91 -0
  42. package/server/services/ai/loop/model_io.js +8 -41
  43. package/server/services/ai/loop/tool_dispatch.js +19 -8
  44. package/server/services/ai/loopPolicy.js +24 -19
  45. package/server/services/ai/model_discovery.js +227 -0
  46. package/server/services/ai/model_identity.js +71 -0
  47. package/server/services/ai/models.js +67 -162
  48. package/server/services/ai/providerRetry.js +17 -59
  49. package/server/services/ai/provider_selector.js +111 -0
  50. package/server/services/ai/providers/anthropic.js +2 -2
  51. package/server/services/ai/providers/claudeCode.js +15 -28
  52. package/server/services/ai/providers/githubCopilot.js +36 -16
  53. package/server/services/ai/providers/google.js +23 -5
  54. package/server/services/ai/providers/grok.js +4 -3
  55. package/server/services/ai/providers/grokOauth.js +13 -22
  56. package/server/services/ai/providers/nvidia.js +10 -5
  57. package/server/services/ai/providers/ollama.js +102 -82
  58. package/server/services/ai/providers/ollama_stream.js +142 -0
  59. package/server/services/ai/providers/openai.js +39 -5
  60. package/server/services/ai/providers/openaiCodex.js +11 -4
  61. package/server/services/ai/providers/openrouter.js +29 -7
  62. package/server/services/ai/providers/provider_error.js +36 -0
  63. package/server/services/ai/settings.js +26 -2
  64. package/server/services/ai/taskAnalysis.js +5 -1
  65. package/server/services/ai/terminal_reply.js +45 -0
  66. package/server/services/ai/toolEvidence.js +58 -29
  67. package/server/services/ai/tools.js +124 -111
  68. package/server/services/android/controller.js +770 -237
  69. package/server/services/android/process.js +140 -0
  70. package/server/services/android/sdk_download.js +143 -0
  71. package/server/services/android/uia.js +6 -5
  72. package/server/services/artifacts/store.js +24 -0
  73. package/server/services/browser/controller.js +736 -385
  74. package/server/services/browser/extension/gateway.js +40 -16
  75. package/server/services/browser/extension/protocol.js +12 -1
  76. package/server/services/browser/extension/provider.js +59 -47
  77. package/server/services/browser/extension/registry.js +155 -34
  78. package/server/services/cli/executor.js +62 -9
  79. package/server/services/desktop/gateway.js +41 -4
  80. package/server/services/desktop/protocol.js +3 -0
  81. package/server/services/desktop/provider.js +39 -42
  82. package/server/services/desktop/registry.js +137 -52
  83. package/server/services/integrations/figma/provider.js +78 -12
  84. package/server/services/integrations/github/common.js +11 -6
  85. package/server/services/integrations/github/provider.js +52 -53
  86. package/server/services/integrations/google/provider.js +55 -19
  87. package/server/services/integrations/home_assistant/network.js +17 -20
  88. package/server/services/integrations/home_assistant/provider.js +7 -5
  89. package/server/services/integrations/home_assistant/tools.js +17 -5
  90. package/server/services/integrations/http.js +51 -0
  91. package/server/services/integrations/manager.js +158 -53
  92. package/server/services/integrations/microsoft/provider.js +80 -13
  93. package/server/services/integrations/neoarchive/provider.js +55 -29
  94. package/server/services/integrations/neorecall/client.js +17 -10
  95. package/server/services/integrations/neorecall/provider.js +20 -11
  96. package/server/services/integrations/notion/provider.js +16 -13
  97. package/server/services/integrations/oauth_provider.js +115 -51
  98. package/server/services/integrations/slack/provider.js +98 -9
  99. package/server/services/integrations/spotify/provider.js +67 -71
  100. package/server/services/integrations/trello/provider.js +21 -7
  101. package/server/services/integrations/weather/provider.js +18 -12
  102. package/server/services/integrations/whatsapp/provider.js +76 -16
  103. package/server/services/manager.js +87 -1
  104. package/server/services/memory/embedding_index.js +20 -8
  105. package/server/services/memory/embeddings.js +151 -90
  106. package/server/services/memory/ingestion.js +50 -9
  107. package/server/services/memory/ingestion_documents.js +13 -3
  108. package/server/services/memory/manager.js +52 -19
  109. package/server/services/messaging/automation.js +84 -9
  110. package/server/services/messaging/http_platforms.js +33 -13
  111. package/server/services/messaging/inbound_queue.js +78 -24
  112. package/server/services/messaging/inbound_store.js +224 -0
  113. package/server/services/messaging/manager.js +326 -51
  114. package/server/services/messaging/typing_keepalive.js +5 -2
  115. package/server/services/network/http.js +210 -0
  116. package/server/services/network/safe_request.js +307 -0
  117. package/server/services/runtime/backends/local-vm.js +214 -66
  118. package/server/services/runtime/manager.js +17 -12
  119. package/server/services/social_reach/channels/github.js +10 -4
  120. package/server/services/social_reach/channels/reddit.js +4 -4
  121. package/server/services/social_reach/channels/rss.js +2 -2
  122. package/server/services/social_reach/channels/social_video.js +12 -7
  123. package/server/services/social_reach/channels/v2ex.js +21 -8
  124. package/server/services/social_reach/channels/x.js +2 -2
  125. package/server/services/social_reach/channels/xueqiu.js +5 -5
  126. package/server/services/social_reach/service.js +9 -6
  127. package/server/services/social_reach/utils.js +65 -14
  128. package/server/services/social_video/service.js +160 -50
  129. package/server/services/tasks/integration_runtime.js +18 -8
  130. package/server/services/tasks/runtime.js +39 -4
  131. package/server/services/voice/agentBridge.js +17 -4
  132. package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
  133. package/server/services/voice/liveSession.js +31 -0
  134. package/server/services/voice/openaiSpeech.js +33 -8
  135. package/server/services/voice/providers.js +233 -151
  136. package/server/services/voice/runtimeManager.js +118 -20
  137. package/server/services/voice/turnRunner.js +6 -0
  138. package/server/services/wearable/firmware_manifest.js +51 -13
  139. package/server/services/wearable/service.js +1 -0
  140. package/server/utils/abort.js +96 -0
  141. package/server/utils/cloud-security.js +110 -3
  142. package/server/utils/files.js +31 -0
  143. package/server/utils/image_payload.js +95 -0
  144. package/server/utils/retry.js +107 -0
@@ -10,6 +10,11 @@ const {
10
10
  normalizeAccessMode,
11
11
  withConnectionAccessMode,
12
12
  } = require('./access');
13
+ const {
14
+ abortError,
15
+ waitForAbortableResult,
16
+ } = require('./http');
17
+ const { throwIfAborted } = require('../../utils/abort');
13
18
 
14
19
  const OAUTH_STATE_PATTERN = /^[a-f0-9]{32,128}$/i;
15
20
 
@@ -56,6 +61,7 @@ class IntegrationManager {
56
61
  this.registry = createIntegrationRegistry({
57
62
  io: this.app?.locals?.io || null,
58
63
  });
64
+ this.connectionExecutionQueues = new Map();
59
65
  }
60
66
 
61
67
  parseCredentials(credentialsJson) {
@@ -152,6 +158,23 @@ class IntegrationManager {
152
158
  ).run();
153
159
  }
154
160
 
161
+ claimOAuthState(state) {
162
+ return db.transaction(() => {
163
+ const stateRow = db
164
+ .prepare(
165
+ `SELECT * FROM integration_oauth_states
166
+ WHERE state = ? AND datetime(expires_at) > datetime('now')`,
167
+ )
168
+ .get(state);
169
+ if (!stateRow) return null;
170
+ const deleted = db.prepare(
171
+ `DELETE FROM integration_oauth_states
172
+ WHERE state = ? AND datetime(expires_at) > datetime('now')`,
173
+ ).run(state);
174
+ return deleted.changes === 1 ? stateRow : null;
175
+ })();
176
+ }
177
+
155
178
  listConnections(userId, providerKey = null, agentId = null) {
156
179
  const scopedAgentId = resolveAgentId(userId, agentId);
157
180
  const query = providerKey
@@ -225,6 +248,7 @@ class IntegrationManager {
225
248
  userId,
226
249
  agentId,
227
250
  appKey,
251
+ signal: options.signal || null,
228
252
  });
229
253
  const url = String(result?.url || '').trim();
230
254
  const absoluteUrl = url.startsWith('http')
@@ -247,6 +271,7 @@ class IntegrationManager {
247
271
  userId,
248
272
  agentId,
249
273
  appKey,
274
+ signal: options.signal || null,
250
275
  });
251
276
 
252
277
  db.prepare(
@@ -290,7 +315,8 @@ class IntegrationManager {
290
315
  );
291
316
  }
292
317
 
293
- async finishOAuth(state, code) {
318
+ async finishOAuth(state, code, options = {}) {
319
+ throwIfAborted(options.signal, 'OAuth callback request aborted.');
294
320
  this.cleanupExpiredOauthStates();
295
321
  const normalizedState = String(state || '').trim();
296
322
  if (!OAUTH_STATE_PATTERN.test(normalizedState)) {
@@ -300,12 +326,7 @@ class IntegrationManager {
300
326
  if (!normalizedCode) {
301
327
  throw new Error('OAuth authorization code is required.');
302
328
  }
303
- const stateRow = db
304
- .prepare(
305
- `SELECT * FROM integration_oauth_states
306
- WHERE state = ? AND datetime(expires_at) > datetime('now')`,
307
- )
308
- .get(normalizedState);
329
+ const stateRow = this.claimOAuthState(normalizedState);
309
330
  if (!stateRow) {
310
331
  throw new Error('OAuth state is missing or expired.');
311
332
  }
@@ -322,6 +343,7 @@ class IntegrationManager {
322
343
  code: normalizedCode,
323
344
  codeVerifier: decryptValue(stateRow.code_verifier),
324
345
  appKey: stateRow.app_key,
346
+ signal: options.signal || null,
325
347
  });
326
348
 
327
349
  const mergedCredentials = this.mergeWithReusableCredentials(
@@ -386,10 +408,6 @@ class IntegrationManager {
386
408
  result.accountEmail,
387
409
  );
388
410
 
389
- db.prepare('DELETE FROM integration_oauth_states WHERE state = ?').run(
390
- stateRow.state,
391
- );
392
-
393
411
  return {
394
412
  provider: provider.key,
395
413
  appId: stateRow.app_key,
@@ -420,7 +438,11 @@ class IntegrationManager {
420
438
  };
421
439
  }
422
440
 
423
- await provider.disconnect(connection).catch(() => {});
441
+ if (typeof provider.disconnect === 'function') {
442
+ await provider.disconnect(connection, {
443
+ signal: options.signal || null,
444
+ }).catch(() => {});
445
+ }
424
446
  db.prepare('DELETE FROM integration_connections WHERE user_id = ? AND agent_id = ? AND id = ?').run(
425
447
  userId,
426
448
  agentId,
@@ -636,7 +658,46 @@ class IntegrationManager {
636
658
  };
637
659
  }
638
660
 
639
- async executeTool(userId, toolName, args, agentId = null) {
661
+ connectionExecutionKey(connection) {
662
+ return [
663
+ connection.user_id,
664
+ connection.agent_id,
665
+ connection.provider_key,
666
+ String(connection.account_email || '').trim().toLowerCase(),
667
+ ].join(':');
668
+ }
669
+
670
+ async acquireConnectionExecution(connection, signal) {
671
+ const key = this.connectionExecutionKey(connection);
672
+ const previous = this.connectionExecutionQueues.get(key) || Promise.resolve();
673
+ let releaseGate;
674
+ const gate = new Promise((resolve) => {
675
+ releaseGate = resolve;
676
+ });
677
+ const tail = previous.catch(() => {}).then(() => gate);
678
+ this.connectionExecutionQueues.set(key, tail);
679
+ void tail.finally(() => {
680
+ if (this.connectionExecutionQueues.get(key) === tail) {
681
+ this.connectionExecutionQueues.delete(key);
682
+ }
683
+ });
684
+
685
+ try {
686
+ await waitForAbortableResult(previous.catch(() => {}), signal);
687
+ } catch (error) {
688
+ releaseGate();
689
+ throw error;
690
+ }
691
+
692
+ let released = false;
693
+ return () => {
694
+ if (released) return;
695
+ released = true;
696
+ releaseGate();
697
+ };
698
+ }
699
+
700
+ async executeTool(userId, toolName, args, agentId = null, options = {}) {
640
701
  let foundSupportingProvider = false;
641
702
  for (const provider of this.registry.list()) {
642
703
  if (!provider.supportsTool(toolName)) continue;
@@ -661,54 +722,86 @@ class IntegrationManager {
661
722
  };
662
723
  }
663
724
 
664
- let execution;
725
+ const storedCredentials = this.parseCredentials(
726
+ selection.connection.credentials_json,
727
+ );
728
+ const needsCredentialLock =
729
+ provider.requiresRefreshToken === true ||
730
+ Boolean(storedCredentials.refresh_token);
731
+ const releaseExecution = needsCredentialLock
732
+ ? await this.acquireConnectionExecution(selection.connection, options.signal)
733
+ : () => {};
665
734
  try {
666
- execution = await provider.executeTool(
667
- toolName,
668
- args,
669
- selection.connection,
735
+ const connection = this.getConnectionById(
736
+ userId,
737
+ selection.connection.id,
738
+ agentId,
670
739
  );
671
- } catch (err) {
672
- if (isLikelyExpiredConnectionError(err)) {
673
- db.prepare(
674
- `UPDATE integration_connections
675
- SET status = 'expired', updated_at = datetime('now')
676
- WHERE id = ? AND user_id = ? AND agent_id = ?`,
677
- ).run(
678
- selection.connection.id,
740
+ if (!connection || connection.status !== 'connected') {
741
+ return {
742
+ error: `The selected ${provider.label} connection is no longer available.`,
743
+ };
744
+ }
745
+
746
+ let execution;
747
+ try {
748
+ execution = await provider.executeTool(
749
+ toolName,
750
+ args,
751
+ connection,
752
+ { signal: options.signal || null },
753
+ );
754
+ } catch (err) {
755
+ if (
756
+ options.signal?.aborted ||
757
+ err?.name === 'AbortError' ||
758
+ err?.code === 'ABORT_ERR'
759
+ ) {
760
+ throw options.signal?.aborted ? abortError(options.signal) : err;
761
+ }
762
+ if (isLikelyExpiredConnectionError(err)) {
763
+ db.prepare(
764
+ `UPDATE integration_connections
765
+ SET status = 'expired', updated_at = datetime('now')
766
+ WHERE id = ? AND user_id = ? AND agent_id = ?`,
767
+ ).run(
768
+ connection.id,
769
+ userId,
770
+ resolveAgentId(userId, agentId),
771
+ );
772
+ return {
773
+ error: `Your ${provider.label} account connection has expired and needs to be reconnected. Open Official Integrations to reconnect your account.`,
774
+ expired: true,
775
+ connectionId: connection.id,
776
+ };
777
+ }
778
+ return { error: err?.message || 'execution_error' };
779
+ }
780
+ if (!execution) {
781
+ return { error: 'execution_failed' };
782
+ }
783
+
784
+ if (execution.credentials) {
785
+ const existingCredentials = this.parseCredentials(
786
+ connection.credentials_json,
787
+ );
788
+ const mergedCredentials = this.mergeUpdatedCredentials(
789
+ existingCredentials,
790
+ execution.credentials,
791
+ );
792
+ this.persistSharedCredentials(
679
793
  userId,
680
794
  resolveAgentId(userId, agentId),
795
+ provider.key,
796
+ connection.account_email,
797
+ mergedCredentials,
681
798
  );
682
- return {
683
- error: `Your ${provider.label} account connection has expired and needs to be reconnected. Open Official Integrations to reconnect your account.`,
684
- expired: true,
685
- connectionId: selection.connection.id,
686
- };
687
799
  }
688
- return { error: err?.message || 'execution_error' };
689
- }
690
- if (!execution) {
691
- return { error: 'execution_failed' };
692
- }
693
800
 
694
- if (execution.credentials) {
695
- const existingCredentials = this.parseCredentials(
696
- selection.connection.credentials_json,
697
- );
698
- const mergedCredentials = this.mergeUpdatedCredentials(
699
- existingCredentials,
700
- execution.credentials,
701
- );
702
- this.persistSharedCredentials(
703
- userId,
704
- resolveAgentId(userId, agentId),
705
- provider.key,
706
- selection.connection.account_email,
707
- mergedCredentials,
708
- );
801
+ return execution.result;
802
+ } finally {
803
+ releaseExecution();
709
804
  }
710
-
711
- return execution.result;
712
805
  }
713
806
 
714
807
  return foundSupportingProvider
@@ -716,6 +809,18 @@ class IntegrationManager {
716
809
  : { error: 'no_provider_support' };
717
810
  }
718
811
 
812
+ async shutdown() {
813
+ const providers = this.registry.list();
814
+ await Promise.allSettled(
815
+ providers.map((provider) => {
816
+ if (typeof provider.shutdown !== 'function') return null;
817
+ return provider.shutdown();
818
+ }),
819
+ );
820
+ this.connectionExecutionQueues.clear();
821
+ return { state: 'stopped', providerCount: providers.length };
822
+ }
823
+
719
824
  summarizeConnectedProviders(userId, agentId = null) {
720
825
  const scopedAgentId = resolveAgentId(userId, agentId);
721
826
  const ingestionService = this.app?.locals?.memoryIngestionService || null;
@@ -202,19 +202,83 @@ function graphUrl(path, query) {
202
202
  return url.toString();
203
203
  }
204
204
 
205
- async function graphRequest(credentials, { method = 'GET', path, query, body }) {
206
- return fetchJson(
205
+ function expiresAtFromSeconds(expiresIn) {
206
+ return new Date(
207
+ Date.now() + Math.max(1, Number(expiresIn) || 3600) * 1000,
208
+ ).toISOString();
209
+ }
210
+
211
+ function tokenExpiresSoon(credentials) {
212
+ const expiresAt = Date.parse(String(credentials?.expires_at || ''));
213
+ return Number.isFinite(expiresAt) && expiresAt <= Date.now() + 60 * 1000;
214
+ }
215
+
216
+ async function refreshMicrosoftCredentials(credentials, signal) {
217
+ const refreshToken = String(credentials?.refresh_token || '').trim();
218
+ if (!refreshToken) {
219
+ throw new Error(
220
+ 'Microsoft 365 refresh token is missing. Reconnect this integration account.',
221
+ );
222
+ }
223
+ const { config, tokenUrl } = getMicrosoftEndpoints();
224
+ const token = await fetchJson(
225
+ tokenUrl,
226
+ {
227
+ method: 'POST',
228
+ form: {
229
+ client_id: config.clientId,
230
+ client_secret: config.clientSecret,
231
+ grant_type: 'refresh_token',
232
+ refresh_token: refreshToken,
233
+ scope: credentials.scope || undefined,
234
+ },
235
+ signal,
236
+ },
237
+ { serviceName: 'Microsoft 365 token refresh' },
238
+ );
239
+ if (!String(token?.access_token || '').trim()) {
240
+ throw new Error('Microsoft 365 token refresh did not return an access token.');
241
+ }
242
+ return {
243
+ ...credentials,
244
+ access_token: token.access_token,
245
+ refresh_token: token.refresh_token || refreshToken,
246
+ expires_in: token.expires_in,
247
+ expires_at: expiresAtFromSeconds(token.expires_in),
248
+ scope: token.scope || credentials.scope,
249
+ token_type: token.token_type || credentials.token_type,
250
+ };
251
+ }
252
+
253
+ async function graphRequest(context, { method = 'GET', path, query, body }) {
254
+ const { signal } = context;
255
+ let credentials = context.credentials;
256
+ if (tokenExpiresSoon(credentials)) {
257
+ credentials = await refreshMicrosoftCredentials(credentials, signal);
258
+ context.updateCredentials(credentials);
259
+ }
260
+ const performRequest = (activeCredentials) => fetchJson(
207
261
  graphUrl(path, query),
208
262
  {
209
263
  method: String(method || 'GET').toUpperCase(),
210
- headers: { Authorization: `Bearer ${credentials.access_token}` },
264
+ headers: { Authorization: `Bearer ${activeCredentials.access_token}` },
211
265
  ...(body === undefined ? {} : { json: body }),
266
+ signal,
212
267
  },
213
268
  { serviceName: 'Microsoft Graph' },
214
269
  );
270
+
271
+ try {
272
+ return await performRequest(credentials);
273
+ } catch (error) {
274
+ if (error?.status !== 401 || !credentials.refresh_token) throw error;
275
+ credentials = await refreshMicrosoftCredentials(credentials, signal);
276
+ context.updateCredentials(credentials);
277
+ return performRequest(credentials);
278
+ }
215
279
  }
216
280
 
217
- async function executeMicrosoftTool(toolName, args, { credentials }) {
281
+ async function executeMicrosoftTool(toolName, args, context) {
218
282
  switch (toolName) {
219
283
  case 'microsoft_365_outlook_list_messages': {
220
284
  const folder = String(args.folder_id || 'inbox').trim();
@@ -222,7 +286,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) {
222
286
  ? '/v1.0/me/mailFolders/inbox/messages'
223
287
  : `/v1.0/me/mailFolders/${encodeURIComponent(folder)}/messages`;
224
288
  return {
225
- result: await graphRequest(credentials, {
289
+ result: await graphRequest(context, {
226
290
  path,
227
291
  query: {
228
292
  '$top': Math.max(1, Math.min(Number(args.top) || 10, 50)),
@@ -233,7 +297,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) {
233
297
  };
234
298
  }
235
299
  case 'microsoft_365_outlook_send_mail':
236
- await graphRequest(credentials, {
300
+ await graphRequest(context, {
237
301
  method: 'POST',
238
302
  path: '/v1.0/me/sendMail',
239
303
  body: {
@@ -255,7 +319,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) {
255
319
  case 'microsoft_365_calendar_list_events': {
256
320
  const hasWindow = args.start && args.end;
257
321
  return {
258
- result: await graphRequest(credentials, {
322
+ result: await graphRequest(context, {
259
323
  path: hasWindow ? '/v1.0/me/calendarView' : '/v1.0/me/events',
260
324
  query: {
261
325
  '$top': Math.max(1, Math.min(Number(args.top) || 10, 50)),
@@ -268,7 +332,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) {
268
332
  }
269
333
  case 'microsoft_365_calendar_create_event':
270
334
  return {
271
- result: await graphRequest(credentials, {
335
+ result: await graphRequest(context, {
272
336
  method: 'POST',
273
337
  path: '/v1.0/me/events',
274
338
  body: {
@@ -294,7 +358,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) {
294
358
  };
295
359
  case 'microsoft_365_onedrive_list_children':
296
360
  return {
297
- result: await graphRequest(credentials, {
361
+ result: await graphRequest(context, {
298
362
  path: args.item_id
299
363
  ? `/v1.0/me/drive/items/${encodeURIComponent(String(args.item_id))}/children`
300
364
  : '/v1.0/me/drive/root/children',
@@ -303,14 +367,14 @@ async function executeMicrosoftTool(toolName, args, { credentials }) {
303
367
  };
304
368
  case 'microsoft_365_teams_list_chats':
305
369
  return {
306
- result: await graphRequest(credentials, {
370
+ result: await graphRequest(context, {
307
371
  path: '/v1.0/me/chats',
308
372
  query: { '$top': Math.max(1, Math.min(Number(args.top) || 20, 50)) },
309
373
  }),
310
374
  };
311
375
  case 'microsoft_365_teams_send_chat_message':
312
376
  return {
313
- result: await graphRequest(credentials, {
377
+ result: await graphRequest(context, {
314
378
  method: 'POST',
315
379
  path: `/v1.0/chats/${encodeURIComponent(requireText(args.chat_id, 'chat_id'))}/messages`,
316
380
  body: {
@@ -327,7 +391,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) {
327
391
  throw new Error('microsoft_365_*_graph_request tools are disabled by default. Set NEOAGENT_ENABLE_MICROSOFT_DYNAMIC_GRAPH_REQUEST=true to enable them.');
328
392
  }
329
393
  return {
330
- result: await graphRequest(credentials, {
394
+ result: await graphRequest(context, {
331
395
  method: args.method,
332
396
  path: requireText(args.path, 'path'),
333
397
  query: args.query,
@@ -371,7 +435,7 @@ function createMicrosoftProvider() {
371
435
  appId: app.id,
372
436
  };
373
437
  },
374
- async finishOAuth({ code, app }) {
438
+ async finishOAuth({ code, app, signal }) {
375
439
  const { config, tokenUrl } = getMicrosoftEndpoints();
376
440
  const token = await fetchJson(
377
441
  tokenUrl,
@@ -385,6 +449,7 @@ function createMicrosoftProvider() {
385
449
  redirect_uri: config.redirectUri,
386
450
  scope: escapeScope(app.scopes),
387
451
  },
452
+ signal,
388
453
  },
389
454
  { serviceName: 'Microsoft 365' },
390
455
  );
@@ -395,6 +460,7 @@ function createMicrosoftProvider() {
395
460
  headers: {
396
461
  Authorization: `Bearer ${token.access_token}`,
397
462
  },
463
+ signal,
398
464
  },
399
465
  { serviceName: 'Microsoft 365' },
400
466
  );
@@ -413,6 +479,7 @@ function createMicrosoftProvider() {
413
479
  access_token: token.access_token,
414
480
  refresh_token: token.refresh_token,
415
481
  expires_in: token.expires_in,
482
+ expires_at: expiresAtFromSeconds(token.expires_in),
416
483
  scope: token.scope,
417
484
  token_type: token.token_type,
418
485
  },