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
@@ -2,9 +2,19 @@
2
2
 
3
3
  const { decryptValue } = require('./secrets');
4
4
  const { getConnectionAccessMode } = require('./access');
5
+ const { fetchResponseText } = require('./http');
6
+ const { isAbortError } = require('../../utils/abort');
7
+ const {
8
+ abortableDelay,
9
+ computeBackoffMs,
10
+ getHttpStatus,
11
+ isTransientIoError,
12
+ retryAfterMilliseconds,
13
+ } = require('../../utils/retry');
5
14
 
6
- const DEFAULT_OAUTH_HTTP_TIMEOUT_MS = 15000;
7
15
  const OAUTH_STATE_PATTERN = /^[a-f0-9]{32,128}$/i;
16
+ const RETRYABLE_INTEGRATION_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
17
+ const MAX_INTEGRATION_RETRY_DELAY_MS = 10000;
8
18
 
9
19
  function escapeScope(scopes) {
10
20
  return Array.from(new Set((scopes || []).filter(Boolean))).join(' ');
@@ -21,6 +31,17 @@ function appendQuery(url, params) {
21
31
  return resolved.toString();
22
32
  }
23
33
 
34
+ function sanitizeRemoteError(value) {
35
+ return String(value || '')
36
+ .slice(0, 1000)
37
+ .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, '$1 [redacted]')
38
+ .replace(
39
+ /\b(access_token|refresh_token|client_secret|authorization|token)\b\s*[:=]\s*["']?[^\s,"'}\]]+/gi,
40
+ '$1=[redacted]',
41
+ )
42
+ .trim();
43
+ }
44
+
24
45
  async function fetchJson(url, options = {}, context = {}) {
25
46
  const headers = {
26
47
  Accept: 'application/json',
@@ -42,54 +63,68 @@ async function fetchJson(url, options = {}, context = {}) {
42
63
  ).toString();
43
64
  }
44
65
 
45
- const controller = new AbortController();
46
- const timeoutMs = Number(options.timeoutMs) > 0
47
- ? Number(options.timeoutMs)
48
- : DEFAULT_OAUTH_HTTP_TIMEOUT_MS;
49
- const timer = setTimeout(() => controller.abort(), timeoutMs);
66
+ const method = String(options.method || 'GET').toUpperCase();
67
+ const maxAttempts =
68
+ options.retry === false || !RETRYABLE_INTEGRATION_METHODS.has(method)
69
+ ? 1
70
+ : 2;
71
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
72
+ try {
73
+ const { response, text } = await fetchResponseText(
74
+ url,
75
+ {
76
+ method,
77
+ headers,
78
+ body,
79
+ signal: options.signal,
80
+ timeoutMs: options.timeoutMs,
81
+ maxResponseBytes: options.maxResponseBytes,
82
+ },
83
+ context,
84
+ );
50
85
 
51
- let response;
52
- try {
53
- response = await fetch(url, {
54
- method: options.method || 'GET',
55
- headers,
56
- body,
57
- signal: controller.signal,
58
- });
59
- } catch (error) {
60
- if (error?.name === 'AbortError') {
61
- const label = String(context.serviceName || 'OAuth provider').trim() || 'OAuth provider';
62
- throw new Error(`${label} request timed out after ${timeoutMs}ms.`);
63
- }
64
- throw error;
65
- } finally {
66
- clearTimeout(timer);
67
- }
86
+ let data = null;
87
+ try {
88
+ data = text ? JSON.parse(text) : null;
89
+ } catch {
90
+ data = null;
91
+ }
68
92
 
69
- const text = await response.text();
70
- let data = null;
71
- try {
72
- data = text ? JSON.parse(text) : null;
73
- } catch {
74
- data = null;
75
- }
93
+ const notOkSlackStyle =
94
+ data &&
95
+ typeof data === 'object' &&
96
+ Object.prototype.hasOwnProperty.call(data, 'ok') &&
97
+ data.ok === false;
76
98
 
77
- const notOkSlackStyle =
78
- data &&
79
- typeof data === 'object' &&
80
- Object.prototype.hasOwnProperty.call(data, 'ok') &&
81
- data.ok === false;
99
+ if (!response.ok || notOkSlackStyle) {
100
+ const label = String(context.serviceName || 'OAuth provider').trim() || 'OAuth provider';
101
+ const rawMessage =
102
+ (data && (data.error_description || data.error || data.message)) ||
103
+ text ||
104
+ `${response.status} ${response.statusText}`;
105
+ const error = new Error(
106
+ `${label} request failed: ${sanitizeRemoteError(rawMessage)}`,
107
+ );
108
+ error.status = response.status;
109
+ error.data = data;
110
+ error.headers = response.headers;
111
+ throw error;
112
+ }
82
113
 
83
- if (!response.ok || notOkSlackStyle) {
84
- const label = String(context.serviceName || 'OAuth provider').trim() || 'OAuth provider';
85
- const message =
86
- (data && (data.error_description || data.error || data.message)) ||
87
- text ||
88
- `${response.status} ${response.statusText}`;
89
- throw new Error(`${label} request failed: ${String(message).trim()}`);
114
+ return data;
115
+ } catch (error) {
116
+ if (isAbortError(error, options.signal) || attempt >= maxAttempts) throw error;
117
+ const retryAfterMs = retryAfterMilliseconds(error.headers);
118
+ const rateLimited =
119
+ (getHttpStatus(error) === 403 && retryAfterMs !== null)
120
+ || String(error.data?.error || '').toLowerCase() === 'ratelimited';
121
+ if (!rateLimited && !isTransientIoError(error)) throw error;
122
+ const delayMs = retryAfterMs ?? computeBackoffMs(attempt, 250, 2000);
123
+ if (delayMs > MAX_INTEGRATION_RETRY_DELAY_MS) throw error;
124
+ await abortableDelay(delayMs, options.signal);
125
+ }
90
126
  }
91
-
92
- return data;
127
+ throw new Error('Integration request exhausted its retry attempts.');
93
128
  }
94
129
 
95
130
  function sortConnections(rows) {
@@ -318,7 +353,7 @@ function createOAuthProvider(options = {}) {
318
353
  connectionMethod: this.connectionMethod,
319
354
  };
320
355
  },
321
- async beginOAuth({ state, codeVerifier, appKey, userId, agentId }) {
356
+ async beginOAuth({ state, codeVerifier, appKey, userId, agentId, signal }) {
322
357
  const app = getApp(appKey);
323
358
  if (!app) {
324
359
  throw new Error(`Unknown ${this.label} app: ${appKey}`);
@@ -335,9 +370,18 @@ function createOAuthProvider(options = {}) {
335
370
  agentId,
336
371
  app,
337
372
  env,
373
+ signal,
338
374
  });
339
375
  },
340
- async finishOAuth({ code, codeVerifier, appKey, userId, agentId, state }) {
376
+ async finishOAuth({
377
+ code,
378
+ codeVerifier,
379
+ appKey,
380
+ userId,
381
+ agentId,
382
+ state,
383
+ signal,
384
+ }) {
341
385
  const app = getApp(appKey);
342
386
  if (!app) {
343
387
  throw new Error(`Unknown ${this.label} app: ${appKey}`);
@@ -351,15 +395,16 @@ function createOAuthProvider(options = {}) {
351
395
  state: normalizedState,
352
396
  app,
353
397
  env: this.getEnvStatus({ userId, agentId }),
398
+ signal,
354
399
  });
355
400
  },
356
- async disconnect(connectionRow) {
401
+ async disconnect(connectionRow, executionOptions = {}) {
357
402
  if (typeof options.disconnect === 'function') {
358
- return options.disconnect(connectionRow);
403
+ return options.disconnect(connectionRow, executionOptions);
359
404
  }
360
405
  return null;
361
406
  },
362
- async executeTool(toolName, args, connectionRow) {
407
+ async executeTool(toolName, args, connectionRow, executionOptions = {}) {
363
408
  if (typeof options.executeTool !== 'function') {
364
409
  return null;
365
410
  }
@@ -371,7 +416,8 @@ function createOAuthProvider(options = {}) {
371
416
  } catch {
372
417
  credentials = {};
373
418
  }
374
- return options.executeTool(toolName, args, {
419
+ let credentialsUpdated = false;
420
+ const integrationContext = {
375
421
  appId: toolAppMap.get(String(toolName || '').trim()) || connectionRow.app_key,
376
422
  connection: connectionRow,
377
423
  credentials,
@@ -379,7 +425,25 @@ function createOAuthProvider(options = {}) {
379
425
  userId: connectionRow?.user_id,
380
426
  agentId: connectionRow?.agent_id,
381
427
  }),
382
- });
428
+ signal: executionOptions.signal || null,
429
+ updateCredentials(nextCredentials) {
430
+ if (!nextCredentials || typeof nextCredentials !== 'object') return;
431
+ integrationContext.credentials = nextCredentials;
432
+ credentialsUpdated = true;
433
+ },
434
+ };
435
+ const execution = await options.executeTool(
436
+ toolName,
437
+ args,
438
+ integrationContext,
439
+ );
440
+ if (!execution || execution.credentials || !credentialsUpdated) {
441
+ return execution;
442
+ }
443
+ return {
444
+ ...execution,
445
+ credentials: integrationContext.credentials,
446
+ };
383
447
  },
384
448
  summarizeConnection(connectionRows) {
385
449
  const snapshot = this.buildSnapshot(connectionRows);
@@ -130,7 +130,11 @@ function requireText(value, label) {
130
130
  return text;
131
131
  }
132
132
 
133
- async function slackApi(credentials, methodName, { httpMethod = 'POST', query, body } = {}) {
133
+ async function slackApi(
134
+ credentials,
135
+ methodName,
136
+ { httpMethod = 'POST', query, body, signal } = {},
137
+ ) {
134
138
  const normalizedMethod = requireText(methodName, 'method_name');
135
139
  if (!/^[a-zA-Z0-9_.]+$/.test(normalizedMethod)) {
136
140
  throw new Error('method_name must be a Slack Web API method name.');
@@ -149,16 +153,92 @@ async function slackApi(credentials, methodName, { httpMethod = 'POST', query, b
149
153
  method: normalizedHttpMethod,
150
154
  headers: { Authorization: `Bearer ${credentials.access_token}` },
151
155
  ...(body === undefined ? {} : { json: body }),
156
+ signal,
152
157
  },
153
158
  { serviceName: 'Slack' },
154
159
  );
155
160
  }
156
161
 
157
- async function executeSlackTool(toolName, args, { credentials }) {
162
+ function expiresAtFromSeconds(expiresIn) {
163
+ const seconds = Number(expiresIn);
164
+ if (!Number.isFinite(seconds) || seconds <= 0) return null;
165
+ return new Date(Date.now() + seconds * 1000).toISOString();
166
+ }
167
+
168
+ function tokenExpiresSoon(credentials) {
169
+ const expiresAt = Date.parse(String(credentials?.expires_at || ''));
170
+ return Number.isFinite(expiresAt) && expiresAt <= Date.now() + 60 * 1000;
171
+ }
172
+
173
+ async function refreshSlackCredentials(credentials, signal) {
174
+ const refreshToken = String(credentials?.refresh_token || '').trim();
175
+ if (!refreshToken) {
176
+ throw new Error('Slack refresh token is missing. Reconnect this integration account.');
177
+ }
178
+ const config = resolveSlackOAuthConfig();
179
+ const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64');
180
+ const token = await fetchJson(
181
+ 'https://slack.com/api/oauth.v2.access',
182
+ {
183
+ method: 'POST',
184
+ headers: { Authorization: `Basic ${basic}` },
185
+ form: {
186
+ grant_type: 'refresh_token',
187
+ refresh_token: refreshToken,
188
+ },
189
+ signal,
190
+ },
191
+ { serviceName: 'Slack token refresh' },
192
+ );
193
+ const userToken = token?.authed_user?.access_token || token.access_token;
194
+ if (!userToken) {
195
+ throw new Error('Slack token refresh did not return an access token.');
196
+ }
197
+ const nextRefreshToken =
198
+ token?.authed_user?.refresh_token || token.refresh_token || refreshToken;
199
+ const expiresIn = token?.authed_user?.expires_in || token.expires_in;
200
+ return {
201
+ ...credentials,
202
+ access_token: userToken,
203
+ refresh_token: nextRefreshToken,
204
+ expires_in: expiresIn,
205
+ expires_at: expiresAtFromSeconds(expiresIn),
206
+ bot_access_token: token.access_token || credentials.bot_access_token,
207
+ token_type: token?.authed_user?.token_type || token.token_type || credentials.token_type,
208
+ team: token.team || credentials.team || null,
209
+ enterprise: token.enterprise || credentials.enterprise || null,
210
+ authed_user: token.authed_user
211
+ ? { ...(credentials.authed_user || {}), ...token.authed_user }
212
+ : credentials.authed_user || null,
213
+ };
214
+ }
215
+
216
+ async function slackRequest(context, methodName, options = {}) {
217
+ const { signal } = context;
218
+ let credentials = context.credentials;
219
+ if (tokenExpiresSoon(credentials)) {
220
+ credentials = await refreshSlackCredentials(credentials, signal);
221
+ context.updateCredentials(credentials);
222
+ }
223
+
224
+ try {
225
+ return await slackApi(credentials, methodName, { ...options, signal });
226
+ } catch (error) {
227
+ const code = String(error?.data?.error || '').toLowerCase();
228
+ if (!credentials.refresh_token || !['invalid_auth', 'token_expired'].includes(code)) {
229
+ throw error;
230
+ }
231
+ credentials = await refreshSlackCredentials(credentials, signal);
232
+ context.updateCredentials(credentials);
233
+ return slackApi(credentials, methodName, { ...options, signal });
234
+ }
235
+ }
236
+
237
+ async function executeSlackTool(toolName, args, context) {
158
238
  switch (toolName) {
159
239
  case 'slack_list_conversations':
160
240
  return {
161
- result: await slackApi(credentials, 'conversations.list', {
241
+ result: await slackRequest(context, 'conversations.list', {
162
242
  httpMethod: 'GET',
163
243
  query: {
164
244
  types: String(args.types || 'public_channel,private_channel,im,mpim'),
@@ -168,7 +248,7 @@ async function executeSlackTool(toolName, args, { credentials }) {
168
248
  };
169
249
  case 'slack_get_conversation_history':
170
250
  return {
171
- result: await slackApi(credentials, 'conversations.history', {
251
+ result: await slackRequest(context, 'conversations.history', {
172
252
  httpMethod: 'GET',
173
253
  query: {
174
254
  channel: requireText(args.channel, 'channel'),
@@ -178,7 +258,7 @@ async function executeSlackTool(toolName, args, { credentials }) {
178
258
  };
179
259
  case 'slack_post_message':
180
260
  return {
181
- result: await slackApi(credentials, 'chat.postMessage', {
261
+ result: await slackRequest(context, 'chat.postMessage', {
182
262
  body: {
183
263
  channel: requireText(args.channel, 'channel'),
184
264
  text: requireText(args.text, 'text'),
@@ -188,7 +268,7 @@ async function executeSlackTool(toolName, args, { credentials }) {
188
268
  };
189
269
  case 'slack_search_messages':
190
270
  return {
191
- result: await slackApi(credentials, 'search.messages', {
271
+ result: await slackRequest(context, 'search.messages', {
192
272
  httpMethod: 'GET',
193
273
  query: {
194
274
  query: requireText(args.query, 'query'),
@@ -198,7 +278,7 @@ async function executeSlackTool(toolName, args, { credentials }) {
198
278
  };
199
279
  case 'slack_get_user_info':
200
280
  return {
201
- result: await slackApi(credentials, 'users.info', {
281
+ result: await slackRequest(context, 'users.info', {
202
282
  httpMethod: 'GET',
203
283
  query: { user: requireText(args.user, 'user') },
204
284
  }),
@@ -208,7 +288,7 @@ async function executeSlackTool(toolName, args, { credentials }) {
208
288
  throw new Error('slack_api_request is disabled by default. Set NEOAGENT_ENABLE_SLACK_DYNAMIC_API_REQUEST=true to enable it.');
209
289
  }
210
290
  return {
211
- result: await slackApi(credentials, requireText(args.method_name, 'method_name'), {
291
+ result: await slackRequest(context, requireText(args.method_name, 'method_name'), {
212
292
  httpMethod: args.http_method || 'POST',
213
293
  query: args.query,
214
294
  body: args.body,
@@ -247,7 +327,7 @@ function createSlackProvider() {
247
327
  appId: app.id,
248
328
  };
249
329
  },
250
- async finishOAuth({ code, app }) {
330
+ async finishOAuth({ code, app, signal }) {
251
331
  const config = resolveSlackOAuthConfig();
252
332
  const token = await fetchJson(
253
333
  'https://slack.com/api/oauth.v2.access',
@@ -260,18 +340,24 @@ function createSlackProvider() {
260
340
  grant_type: 'authorization_code',
261
341
  redirect_uri: config.redirectUri,
262
342
  },
343
+ signal,
263
344
  },
264
345
  { serviceName: 'Slack' },
265
346
  );
266
347
 
267
348
  const userToken = token?.authed_user?.access_token || token.access_token;
349
+ const refreshToken =
350
+ token?.authed_user?.refresh_token || token.refresh_token || null;
351
+ const expiresIn = token?.authed_user?.expires_in || token.expires_in || null;
268
352
  const authTest = await slackApi({ access_token: userToken }, 'auth.test', {
269
353
  httpMethod: 'GET',
354
+ signal,
270
355
  });
271
356
  const profile = authTest?.user_id
272
357
  ? await slackApi({ access_token: userToken }, 'users.info', {
273
358
  httpMethod: 'GET',
274
359
  query: { user: authTest.user_id },
360
+ signal,
275
361
  }).catch(() => null)
276
362
  : null;
277
363
  const user = profile?.user || {};
@@ -287,6 +373,9 @@ function createSlackProvider() {
287
373
  accountEmail,
288
374
  credentials: {
289
375
  access_token: userToken,
376
+ refresh_token: refreshToken,
377
+ expires_in: expiresIn,
378
+ expires_at: expiresAtFromSeconds(expiresIn),
290
379
  bot_access_token: token.access_token,
291
380
  token_type: token.token_type,
292
381
  team: token.team || null,