neoagent 3.2.1-beta.1 → 3.2.1-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.
- package/extensions/chrome-browser/background.mjs +318 -88
- package/extensions/chrome-browser/http.mjs +136 -0
- package/extensions/chrome-browser/protocol.mjs +654 -90
- package/flutter_app/lib/main_chat.dart +118 -739
- package/flutter_app/lib/main_controller.dart +111 -20
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_models.dart +3 -0
- package/flutter_app/lib/main_operations.dart +334 -321
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/main_settings.dart +4 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/backend_client.dart +78 -0
- 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/landing/index.html +3 -1
- package/lib/manager.js +106 -89
- package/lib/schema_migrations.js +115 -13
- package/package.json +30 -15
- package/runtime/paths.js +49 -5
- package/server/db/database.js +2 -2
- package/server/guest-agent.cli.package.json +13 -0
- package/server/guest_agent.js +85 -40
- package/server/http/middleware.js +24 -0
- package/server/http/routes.js +11 -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 +2 -2
- package/server/public/main.dart.js +73083 -72209
- 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 +107 -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/agents/manager.js +1 -1
- package/server/services/ai/capabilityHealth.js +62 -96
- package/server/services/ai/compaction.js +7 -2
- package/server/services/ai/history.js +45 -6
- package/server/services/ai/integrated_tools/http_request.js +8 -0
- package/server/services/ai/loop/agent_engine_core.js +390 -162
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/callbacks.js +1 -0
- package/server/services/ai/loop/completion_judge.js +121 -5
- package/server/services/ai/loop/conversation_loop.js +510 -332
- 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 +20 -45
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loop/tool_dispatch.js +19 -8
- package/server/services/ai/loopPolicy.js +48 -21
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_discovery.js +227 -0
- package/server/services/ai/model_failure_cache.js +108 -0
- package/server/services/ai/model_identity.js +71 -0
- package/server/services/ai/models.js +68 -163
- package/server/services/ai/providerRetry.js +17 -59
- package/server/services/ai/provider_selector.js +166 -0
- package/server/services/ai/providers/anthropic.js +2 -2
- package/server/services/ai/providers/claudeCode.js +21 -33
- package/server/services/ai/providers/githubCopilot.js +41 -20
- package/server/services/ai/providers/google.js +135 -97
- package/server/services/ai/providers/grok.js +4 -3
- package/server/services/ai/providers/grokOauth.js +19 -27
- package/server/services/ai/providers/nvidia.js +10 -5
- package/server/services/ai/providers/ollama.js +111 -84
- 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/systemPrompt.js +19 -12
- package/server/services/ai/taskAnalysis.js +58 -10
- package/server/services/ai/terminal_reply.js +18 -0
- package/server/services/ai/toolEvidence.js +350 -29
- package/server/services/ai/tools.js +190 -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 +843 -385
- package/server/services/browser/extension/gateway.js +40 -16
- package/server/services/browser/extension/protocol.js +15 -1
- package/server/services/browser/extension/provider.js +71 -47
- package/server/services/browser/extension/registry.js +155 -34
- package/server/services/cli/executor.js +62 -9
- package/server/services/credentials/bitwarden_cli.js +322 -0
- package/server/services/credentials/broker.js +594 -0
- 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/bitwarden/constants.js +14 -0
- package/server/services/integrations/bitwarden/provider.js +197 -0
- package/server/services/integrations/bitwarden/snapshot.js +65 -0
- 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 +159 -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/registry.js +2 -0
- 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 +110 -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 +85 -10
- package/server/services/messaging/formatting_guides.js +7 -4
- 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 +227 -67
- package/server/services/runtime/docker-vm-manager.js +9 -0
- package/server/services/runtime/guest_bootstrap.js +30 -4
- package/server/services/runtime/guest_image.js +43 -12
- package/server/services/runtime/manager.js +77 -23
- package/server/services/runtime/validation.js +7 -6
- package/server/services/security/tool_categories.js +6 -0
- 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/message.js +1 -1
- package/server/services/voice/openaiSpeech.js +33 -8
- package/server/services/voice/providers.js +233 -151
- package/server/services/voice/runtime.js +2 -2
- 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
|
|
|
@@ -54,8 +59,10 @@ class IntegrationManager {
|
|
|
54
59
|
constructor(options = {}) {
|
|
55
60
|
this.app = options.app || null;
|
|
56
61
|
this.registry = createIntegrationRegistry({
|
|
62
|
+
app: this.app,
|
|
57
63
|
io: this.app?.locals?.io || null,
|
|
58
64
|
});
|
|
65
|
+
this.connectionExecutionQueues = new Map();
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
parseCredentials(credentialsJson) {
|
|
@@ -152,6 +159,23 @@ class IntegrationManager {
|
|
|
152
159
|
).run();
|
|
153
160
|
}
|
|
154
161
|
|
|
162
|
+
claimOAuthState(state) {
|
|
163
|
+
return db.transaction(() => {
|
|
164
|
+
const stateRow = db
|
|
165
|
+
.prepare(
|
|
166
|
+
`SELECT * FROM integration_oauth_states
|
|
167
|
+
WHERE state = ? AND datetime(expires_at) > datetime('now')`,
|
|
168
|
+
)
|
|
169
|
+
.get(state);
|
|
170
|
+
if (!stateRow) return null;
|
|
171
|
+
const deleted = db.prepare(
|
|
172
|
+
`DELETE FROM integration_oauth_states
|
|
173
|
+
WHERE state = ? AND datetime(expires_at) > datetime('now')`,
|
|
174
|
+
).run(state);
|
|
175
|
+
return deleted.changes === 1 ? stateRow : null;
|
|
176
|
+
})();
|
|
177
|
+
}
|
|
178
|
+
|
|
155
179
|
listConnections(userId, providerKey = null, agentId = null) {
|
|
156
180
|
const scopedAgentId = resolveAgentId(userId, agentId);
|
|
157
181
|
const query = providerKey
|
|
@@ -225,6 +249,7 @@ class IntegrationManager {
|
|
|
225
249
|
userId,
|
|
226
250
|
agentId,
|
|
227
251
|
appKey,
|
|
252
|
+
signal: options.signal || null,
|
|
228
253
|
});
|
|
229
254
|
const url = String(result?.url || '').trim();
|
|
230
255
|
const absoluteUrl = url.startsWith('http')
|
|
@@ -247,6 +272,7 @@ class IntegrationManager {
|
|
|
247
272
|
userId,
|
|
248
273
|
agentId,
|
|
249
274
|
appKey,
|
|
275
|
+
signal: options.signal || null,
|
|
250
276
|
});
|
|
251
277
|
|
|
252
278
|
db.prepare(
|
|
@@ -290,7 +316,8 @@ class IntegrationManager {
|
|
|
290
316
|
);
|
|
291
317
|
}
|
|
292
318
|
|
|
293
|
-
async finishOAuth(state, code) {
|
|
319
|
+
async finishOAuth(state, code, options = {}) {
|
|
320
|
+
throwIfAborted(options.signal, 'OAuth callback request aborted.');
|
|
294
321
|
this.cleanupExpiredOauthStates();
|
|
295
322
|
const normalizedState = String(state || '').trim();
|
|
296
323
|
if (!OAUTH_STATE_PATTERN.test(normalizedState)) {
|
|
@@ -300,12 +327,7 @@ class IntegrationManager {
|
|
|
300
327
|
if (!normalizedCode) {
|
|
301
328
|
throw new Error('OAuth authorization code is required.');
|
|
302
329
|
}
|
|
303
|
-
const stateRow =
|
|
304
|
-
.prepare(
|
|
305
|
-
`SELECT * FROM integration_oauth_states
|
|
306
|
-
WHERE state = ? AND datetime(expires_at) > datetime('now')`,
|
|
307
|
-
)
|
|
308
|
-
.get(normalizedState);
|
|
330
|
+
const stateRow = this.claimOAuthState(normalizedState);
|
|
309
331
|
if (!stateRow) {
|
|
310
332
|
throw new Error('OAuth state is missing or expired.');
|
|
311
333
|
}
|
|
@@ -322,6 +344,7 @@ class IntegrationManager {
|
|
|
322
344
|
code: normalizedCode,
|
|
323
345
|
codeVerifier: decryptValue(stateRow.code_verifier),
|
|
324
346
|
appKey: stateRow.app_key,
|
|
347
|
+
signal: options.signal || null,
|
|
325
348
|
});
|
|
326
349
|
|
|
327
350
|
const mergedCredentials = this.mergeWithReusableCredentials(
|
|
@@ -386,10 +409,6 @@ class IntegrationManager {
|
|
|
386
409
|
result.accountEmail,
|
|
387
410
|
);
|
|
388
411
|
|
|
389
|
-
db.prepare('DELETE FROM integration_oauth_states WHERE state = ?').run(
|
|
390
|
-
stateRow.state,
|
|
391
|
-
);
|
|
392
|
-
|
|
393
412
|
return {
|
|
394
413
|
provider: provider.key,
|
|
395
414
|
appId: stateRow.app_key,
|
|
@@ -420,7 +439,11 @@ class IntegrationManager {
|
|
|
420
439
|
};
|
|
421
440
|
}
|
|
422
441
|
|
|
423
|
-
|
|
442
|
+
if (typeof provider.disconnect === 'function') {
|
|
443
|
+
await provider.disconnect(connection, {
|
|
444
|
+
signal: options.signal || null,
|
|
445
|
+
}).catch(() => {});
|
|
446
|
+
}
|
|
424
447
|
db.prepare('DELETE FROM integration_connections WHERE user_id = ? AND agent_id = ? AND id = ?').run(
|
|
425
448
|
userId,
|
|
426
449
|
agentId,
|
|
@@ -636,7 +659,46 @@ class IntegrationManager {
|
|
|
636
659
|
};
|
|
637
660
|
}
|
|
638
661
|
|
|
639
|
-
|
|
662
|
+
connectionExecutionKey(connection) {
|
|
663
|
+
return [
|
|
664
|
+
connection.user_id,
|
|
665
|
+
connection.agent_id,
|
|
666
|
+
connection.provider_key,
|
|
667
|
+
String(connection.account_email || '').trim().toLowerCase(),
|
|
668
|
+
].join(':');
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async acquireConnectionExecution(connection, signal) {
|
|
672
|
+
const key = this.connectionExecutionKey(connection);
|
|
673
|
+
const previous = this.connectionExecutionQueues.get(key) || Promise.resolve();
|
|
674
|
+
let releaseGate;
|
|
675
|
+
const gate = new Promise((resolve) => {
|
|
676
|
+
releaseGate = resolve;
|
|
677
|
+
});
|
|
678
|
+
const tail = previous.catch(() => {}).then(() => gate);
|
|
679
|
+
this.connectionExecutionQueues.set(key, tail);
|
|
680
|
+
void tail.finally(() => {
|
|
681
|
+
if (this.connectionExecutionQueues.get(key) === tail) {
|
|
682
|
+
this.connectionExecutionQueues.delete(key);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
try {
|
|
687
|
+
await waitForAbortableResult(previous.catch(() => {}), signal);
|
|
688
|
+
} catch (error) {
|
|
689
|
+
releaseGate();
|
|
690
|
+
throw error;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
let released = false;
|
|
694
|
+
return () => {
|
|
695
|
+
if (released) return;
|
|
696
|
+
released = true;
|
|
697
|
+
releaseGate();
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async executeTool(userId, toolName, args, agentId = null, options = {}) {
|
|
640
702
|
let foundSupportingProvider = false;
|
|
641
703
|
for (const provider of this.registry.list()) {
|
|
642
704
|
if (!provider.supportsTool(toolName)) continue;
|
|
@@ -661,54 +723,86 @@ class IntegrationManager {
|
|
|
661
723
|
};
|
|
662
724
|
}
|
|
663
725
|
|
|
664
|
-
|
|
726
|
+
const storedCredentials = this.parseCredentials(
|
|
727
|
+
selection.connection.credentials_json,
|
|
728
|
+
);
|
|
729
|
+
const needsCredentialLock =
|
|
730
|
+
provider.requiresRefreshToken === true ||
|
|
731
|
+
Boolean(storedCredentials.refresh_token);
|
|
732
|
+
const releaseExecution = needsCredentialLock
|
|
733
|
+
? await this.acquireConnectionExecution(selection.connection, options.signal)
|
|
734
|
+
: () => {};
|
|
665
735
|
try {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
736
|
+
const connection = this.getConnectionById(
|
|
737
|
+
userId,
|
|
738
|
+
selection.connection.id,
|
|
739
|
+
agentId,
|
|
670
740
|
);
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
741
|
+
if (!connection || connection.status !== 'connected') {
|
|
742
|
+
return {
|
|
743
|
+
error: `The selected ${provider.label} connection is no longer available.`,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
let execution;
|
|
748
|
+
try {
|
|
749
|
+
execution = await provider.executeTool(
|
|
750
|
+
toolName,
|
|
751
|
+
args,
|
|
752
|
+
connection,
|
|
753
|
+
{ signal: options.signal || null },
|
|
754
|
+
);
|
|
755
|
+
} catch (err) {
|
|
756
|
+
if (
|
|
757
|
+
options.signal?.aborted ||
|
|
758
|
+
err?.name === 'AbortError' ||
|
|
759
|
+
err?.code === 'ABORT_ERR'
|
|
760
|
+
) {
|
|
761
|
+
throw options.signal?.aborted ? abortError(options.signal) : err;
|
|
762
|
+
}
|
|
763
|
+
if (isLikelyExpiredConnectionError(err)) {
|
|
764
|
+
db.prepare(
|
|
765
|
+
`UPDATE integration_connections
|
|
766
|
+
SET status = 'expired', updated_at = datetime('now')
|
|
767
|
+
WHERE id = ? AND user_id = ? AND agent_id = ?`,
|
|
768
|
+
).run(
|
|
769
|
+
connection.id,
|
|
770
|
+
userId,
|
|
771
|
+
resolveAgentId(userId, agentId),
|
|
772
|
+
);
|
|
773
|
+
return {
|
|
774
|
+
error: `Your ${provider.label} account connection has expired and needs to be reconnected. Open Official Integrations to reconnect your account.`,
|
|
775
|
+
expired: true,
|
|
776
|
+
connectionId: connection.id,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
return { error: err?.message || 'execution_error' };
|
|
780
|
+
}
|
|
781
|
+
if (!execution) {
|
|
782
|
+
return { error: 'execution_failed' };
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (execution.credentials) {
|
|
786
|
+
const existingCredentials = this.parseCredentials(
|
|
787
|
+
connection.credentials_json,
|
|
788
|
+
);
|
|
789
|
+
const mergedCredentials = this.mergeUpdatedCredentials(
|
|
790
|
+
existingCredentials,
|
|
791
|
+
execution.credentials,
|
|
792
|
+
);
|
|
793
|
+
this.persistSharedCredentials(
|
|
679
794
|
userId,
|
|
680
795
|
resolveAgentId(userId, agentId),
|
|
796
|
+
provider.key,
|
|
797
|
+
connection.account_email,
|
|
798
|
+
mergedCredentials,
|
|
681
799
|
);
|
|
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
800
|
}
|
|
688
|
-
return { error: err?.message || 'execution_error' };
|
|
689
|
-
}
|
|
690
|
-
if (!execution) {
|
|
691
|
-
return { error: 'execution_failed' };
|
|
692
|
-
}
|
|
693
801
|
|
|
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
|
-
);
|
|
802
|
+
return execution.result;
|
|
803
|
+
} finally {
|
|
804
|
+
releaseExecution();
|
|
709
805
|
}
|
|
710
|
-
|
|
711
|
-
return execution.result;
|
|
712
806
|
}
|
|
713
807
|
|
|
714
808
|
return foundSupportingProvider
|
|
@@ -716,6 +810,18 @@ class IntegrationManager {
|
|
|
716
810
|
: { error: 'no_provider_support' };
|
|
717
811
|
}
|
|
718
812
|
|
|
813
|
+
async shutdown() {
|
|
814
|
+
const providers = this.registry.list();
|
|
815
|
+
await Promise.allSettled(
|
|
816
|
+
providers.map((provider) => {
|
|
817
|
+
if (typeof provider.shutdown !== 'function') return null;
|
|
818
|
+
return provider.shutdown();
|
|
819
|
+
}),
|
|
820
|
+
);
|
|
821
|
+
this.connectionExecutionQueues.clear();
|
|
822
|
+
return { state: 'stopped', providerCount: providers.length };
|
|
823
|
+
}
|
|
824
|
+
|
|
719
825
|
summarizeConnectedProviders(userId, agentId = null) {
|
|
720
826
|
const scopedAgentId = resolveAgentId(userId, agentId);
|
|
721
827
|
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
|
},
|