neoagent 3.2.1-beta.2 → 3.2.1-beta.4
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/extensions/chrome-browser/background.mjs +318 -88
- package/extensions/chrome-browser/http.mjs +136 -0
- package/extensions/chrome-browser/protocol.mjs +511 -89
- package/flutter_app/lib/main_chat.dart +118 -739
- package/flutter_app/lib/main_controller.dart +29 -20
- package/flutter_app/lib/main_models.dart +3 -0
- package/flutter_app/lib/main_operations.dart +334 -321
- package/flutter_app/lib/main_settings.dart +4 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
- package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
- package/flutter_app/windows/runner/flutter_window.cpp +143 -32
- package/lib/manager.js +106 -89
- package/lib/schema_migrations.js +67 -13
- package/package.json +20 -13
- package/runtime/paths.js +49 -5
- package/server/guest_agent.js +52 -30
- package/server/http/middleware.js +24 -0
- package/server/http/routes.js +4 -6
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +30803 -30805
- package/server/routes/admin.js +1 -1
- package/server/routes/android.js +30 -34
- package/server/routes/browser.js +23 -15
- package/server/routes/desktop.js +18 -1
- package/server/routes/integrations.js +5 -1
- package/server/routes/memory.js +1 -0
- package/server/routes/settings.js +16 -5
- package/server/routes/social_reach.js +12 -3
- package/server/routes/social_video.js +4 -0
- package/server/services/ai/compaction.js +7 -2
- package/server/services/ai/history.js +44 -5
- package/server/services/ai/integrated_tools/http_request.js +8 -0
- package/server/services/ai/loop/agent_engine_core.js +347 -162
- package/server/services/ai/loop/callbacks.js +1 -0
- package/server/services/ai/loop/completion_judge.js +12 -0
- package/server/services/ai/loop/conversation_loop.js +348 -242
- package/server/services/ai/loop/messaging_delivery.js +129 -57
- package/server/services/ai/loop/model_call_guard.js +91 -0
- package/server/services/ai/loop/model_io.js +8 -41
- package/server/services/ai/loop/tool_dispatch.js +19 -8
- package/server/services/ai/loopPolicy.js +24 -19
- package/server/services/ai/model_discovery.js +227 -0
- package/server/services/ai/model_identity.js +71 -0
- package/server/services/ai/models.js +67 -162
- package/server/services/ai/providerRetry.js +17 -59
- package/server/services/ai/provider_selector.js +111 -0
- package/server/services/ai/providers/anthropic.js +2 -2
- package/server/services/ai/providers/claudeCode.js +15 -28
- package/server/services/ai/providers/githubCopilot.js +36 -16
- package/server/services/ai/providers/google.js +23 -5
- package/server/services/ai/providers/grok.js +4 -3
- package/server/services/ai/providers/grokOauth.js +13 -22
- package/server/services/ai/providers/nvidia.js +10 -5
- package/server/services/ai/providers/ollama.js +102 -82
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +39 -5
- package/server/services/ai/providers/openaiCodex.js +11 -4
- package/server/services/ai/providers/openrouter.js +29 -7
- package/server/services/ai/providers/provider_error.js +36 -0
- package/server/services/ai/settings.js +26 -2
- package/server/services/ai/taskAnalysis.js +5 -1
- package/server/services/ai/terminal_reply.js +45 -0
- package/server/services/ai/toolEvidence.js +58 -29
- package/server/services/ai/tools.js +124 -111
- package/server/services/android/controller.js +770 -237
- package/server/services/android/process.js +140 -0
- package/server/services/android/sdk_download.js +143 -0
- package/server/services/android/uia.js +6 -5
- package/server/services/artifacts/store.js +24 -0
- package/server/services/browser/controller.js +736 -385
- package/server/services/browser/extension/gateway.js +40 -16
- package/server/services/browser/extension/protocol.js +12 -1
- package/server/services/browser/extension/provider.js +59 -47
- package/server/services/browser/extension/registry.js +155 -34
- package/server/services/cli/executor.js +62 -9
- package/server/services/desktop/gateway.js +41 -4
- package/server/services/desktop/protocol.js +3 -0
- package/server/services/desktop/provider.js +39 -42
- package/server/services/desktop/registry.js +137 -52
- package/server/services/integrations/figma/provider.js +78 -12
- package/server/services/integrations/github/common.js +11 -6
- package/server/services/integrations/github/provider.js +52 -53
- package/server/services/integrations/google/provider.js +55 -19
- package/server/services/integrations/home_assistant/network.js +17 -20
- package/server/services/integrations/home_assistant/provider.js +7 -5
- package/server/services/integrations/home_assistant/tools.js +17 -5
- package/server/services/integrations/http.js +51 -0
- package/server/services/integrations/manager.js +158 -53
- package/server/services/integrations/microsoft/provider.js +80 -13
- package/server/services/integrations/neoarchive/provider.js +55 -29
- package/server/services/integrations/neorecall/client.js +17 -10
- package/server/services/integrations/neorecall/provider.js +20 -11
- package/server/services/integrations/notion/provider.js +16 -13
- package/server/services/integrations/oauth_provider.js +115 -51
- package/server/services/integrations/slack/provider.js +98 -9
- package/server/services/integrations/spotify/provider.js +67 -71
- package/server/services/integrations/trello/provider.js +21 -7
- package/server/services/integrations/weather/provider.js +18 -12
- package/server/services/integrations/whatsapp/provider.js +76 -16
- package/server/services/manager.js +87 -1
- package/server/services/memory/embedding_index.js +20 -8
- package/server/services/memory/embeddings.js +151 -90
- package/server/services/memory/ingestion.js +50 -9
- package/server/services/memory/ingestion_documents.js +13 -3
- package/server/services/memory/manager.js +52 -19
- package/server/services/messaging/automation.js +84 -9
- package/server/services/messaging/http_platforms.js +33 -13
- package/server/services/messaging/inbound_queue.js +78 -24
- package/server/services/messaging/inbound_store.js +224 -0
- package/server/services/messaging/manager.js +326 -51
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +22 -14
- package/server/services/network/http.js +210 -0
- package/server/services/network/safe_request.js +307 -0
- package/server/services/runtime/backends/local-vm.js +214 -66
- package/server/services/runtime/manager.js +17 -12
- package/server/services/social_reach/channels/github.js +10 -4
- package/server/services/social_reach/channels/reddit.js +4 -4
- package/server/services/social_reach/channels/rss.js +2 -2
- package/server/services/social_reach/channels/social_video.js +12 -7
- package/server/services/social_reach/channels/v2ex.js +21 -8
- package/server/services/social_reach/channels/x.js +2 -2
- package/server/services/social_reach/channels/xueqiu.js +5 -5
- package/server/services/social_reach/service.js +9 -6
- package/server/services/social_reach/utils.js +65 -14
- package/server/services/social_video/service.js +160 -50
- package/server/services/tasks/integration_runtime.js +18 -8
- package/server/services/tasks/runtime.js +39 -4
- package/server/services/voice/agentBridge.js +17 -4
- package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
- package/server/services/voice/liveSession.js +31 -0
- package/server/services/voice/openaiSpeech.js +33 -8
- package/server/services/voice/providers.js +233 -151
- package/server/services/voice/runtimeManager.js +118 -20
- package/server/services/voice/turnRunner.js +6 -0
- package/server/services/wearable/firmware_manifest.js +51 -13
- package/server/services/wearable/service.js +1 -0
- package/server/utils/abort.js +96 -0
- package/server/utils/cloud-security.js +110 -3
- package/server/utils/files.js +31 -0
- package/server/utils/image_payload.js +95 -0
- 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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
735
|
+
const connection = this.getConnectionById(
|
|
736
|
+
userId,
|
|
737
|
+
selection.connection.id,
|
|
738
|
+
agentId,
|
|
670
739
|
);
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
206
|
-
return
|
|
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 ${
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
},
|