neoagent 3.2.1-beta.1 → 3.2.1-beta.11
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 +157 -24
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_models.dart +7 -2
- package/flutter_app/lib/main_operations.dart +334 -370
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/main_settings.dart +342 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/backend_client.dart +97 -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 +12 -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 +81930 -80509
- package/server/routes/admin.js +1 -1
- package/server/routes/android.js +30 -34
- package/server/routes/behavior.js +80 -0
- 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 +20 -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 +452 -176
- 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 +291 -8
- package/server/services/ai/loop/conversation_loop.js +515 -342
- package/server/services/ai/loop/messaging_delivery.js +176 -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 +32 -123
- package/server/services/ai/taskAnalysis.js +104 -11
- package/server/services/ai/toolEvidence.js +256 -29
- package/server/services/ai/tools.js +248 -117
- 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/behavior/config.js +251 -0
- package/server/services/behavior/defaults.js +68 -0
- package/server/services/behavior/delivery.js +176 -0
- package/server/services/behavior/index.js +43 -0
- package/server/services/behavior/model_client.js +35 -0
- package/server/services/behavior/modules/agent_identity.js +37 -0
- package/server/services/behavior/modules/channel_style.js +28 -0
- package/server/services/behavior/modules/index.js +29 -0
- package/server/services/behavior/modules/norms.js +101 -0
- package/server/services/behavior/modules/persona.js +48 -0
- package/server/services/behavior/modules/persona_prompt.js +33 -0
- package/server/services/behavior/modules/social_memory.js +86 -0
- package/server/services/behavior/modules/social_observability.js +94 -0
- package/server/services/behavior/modules/social_signals.js +41 -0
- package/server/services/behavior/modules/theory_of_mind.js +110 -0
- package/server/services/behavior/modules/turn_taking.js +237 -0
- package/server/services/behavior/pipeline.js +285 -0
- package/server/services/behavior/registry.js +78 -0
- package/server/services/behavior/signals.js +107 -0
- package/server/services/behavior/state.js +99 -0
- package/server/services/behavior/system_prompt.js +75 -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 +66 -52
- package/server/services/messaging/access_policy.js +10 -6
- package/server/services/messaging/automation.js +240 -34
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +5 -4
- package/server/services/messaging/http_platforms.js +37 -16
- package/server/services/messaging/inbound_queue.js +143 -28
- package/server/services/messaging/inbound_store.js +257 -0
- package/server/services/messaging/manager.js +344 -51
- package/server/services/messaging/telegram.js +10 -1
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +33 -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 +13 -8
- 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/captions.js +2 -2
- package/server/services/social_video/service.js +343 -68
- 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/logger.js +19 -0
- package/server/utils/retry.js +107 -0
|
@@ -13,9 +13,11 @@ const {
|
|
|
13
13
|
const { getConnectionAccessMode } = require("../access");
|
|
14
14
|
const { decryptValue } = require("../secrets");
|
|
15
15
|
const { appendQuery, fetchJson } = require("../oauth_provider");
|
|
16
|
+
const { fetchResponseText } = require("../http");
|
|
16
17
|
const { resolvePublicBaseUrl } = require("../env");
|
|
17
18
|
|
|
18
19
|
const KEY = "neoarchive";
|
|
20
|
+
const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
19
21
|
const APP = Object.freeze({
|
|
20
22
|
id: "archive",
|
|
21
23
|
label: "Archive",
|
|
@@ -277,24 +279,25 @@ function snapshot(provider, rows, context) {
|
|
|
277
279
|
connectionMethod: "oauth",
|
|
278
280
|
};
|
|
279
281
|
}
|
|
280
|
-
async function bootstrap(baseUrl) {
|
|
282
|
+
async function bootstrap(baseUrl, options = {}) {
|
|
281
283
|
return fetchJson(
|
|
282
284
|
`${baseUrl}/api/oauth/companion/neoagent/bootstrap`,
|
|
283
285
|
{
|
|
284
286
|
method: "POST",
|
|
285
287
|
json: { redirectUri: callbackUrl(), appName: "NeoAgent" },
|
|
288
|
+
signal: options.signal,
|
|
286
289
|
},
|
|
287
290
|
{ serviceName: "NeoArchive companion bootstrap" },
|
|
288
291
|
);
|
|
289
292
|
}
|
|
290
|
-
async function token(baseUrl, form) {
|
|
293
|
+
async function token(baseUrl, form, options = {}) {
|
|
291
294
|
return fetchJson(
|
|
292
295
|
`${baseUrl}/oauth/token`,
|
|
293
|
-
{ method: "POST", form },
|
|
296
|
+
{ method: "POST", form, signal: options.signal },
|
|
294
297
|
{ serviceName: "NeoArchive OAuth token" },
|
|
295
298
|
);
|
|
296
299
|
}
|
|
297
|
-
async function access(connection) {
|
|
300
|
+
async function access(connection, options = {}) {
|
|
298
301
|
const saved = credentials(connection);
|
|
299
302
|
const baseUrl = normalizeBaseUrl(saved.baseUrl);
|
|
300
303
|
if (saved.access_token && Number(saved.expires_at_ms) > Date.now() + 60000)
|
|
@@ -307,7 +310,7 @@ async function access(connection) {
|
|
|
307
310
|
grant_type: "refresh_token",
|
|
308
311
|
client_id: saved.client_id,
|
|
309
312
|
refresh_token: saved.refresh_token,
|
|
310
|
-
});
|
|
313
|
+
}, options);
|
|
311
314
|
const next = {
|
|
312
315
|
...saved,
|
|
313
316
|
access_token: text(refreshed.access_token),
|
|
@@ -319,13 +322,14 @@ async function access(connection) {
|
|
|
319
322
|
return { baseUrl, accessToken: next.access_token, credentials: next };
|
|
320
323
|
}
|
|
321
324
|
async function request(connection, apiPath, options = {}) {
|
|
322
|
-
const auth = await access(connection);
|
|
325
|
+
const auth = await access(connection, options);
|
|
323
326
|
const response = await fetchJson(
|
|
324
327
|
`${auth.baseUrl}${apiPath}`,
|
|
325
328
|
{
|
|
326
329
|
method: options.method || "GET",
|
|
327
330
|
headers: { Authorization: `Bearer ${auth.accessToken}` },
|
|
328
331
|
...(options.json === undefined ? {} : { json: options.json }),
|
|
332
|
+
signal: options.signal,
|
|
329
333
|
},
|
|
330
334
|
{ serviceName: "NeoArchive API" },
|
|
331
335
|
);
|
|
@@ -336,7 +340,7 @@ function required(value, name) {
|
|
|
336
340
|
if (!normalized) throw new Error(`${name} is required.`);
|
|
337
341
|
return normalized;
|
|
338
342
|
}
|
|
339
|
-
async function upload(connection, filePath) {
|
|
343
|
+
async function upload(connection, filePath, options = {}) {
|
|
340
344
|
const raw = text(filePath);
|
|
341
345
|
if (!raw || raw.split(/[\\/]+/).includes(".."))
|
|
342
346
|
throw new Error(
|
|
@@ -346,27 +350,40 @@ async function upload(connection, filePath) {
|
|
|
346
350
|
const stats = await fs.promises.stat(resolved);
|
|
347
351
|
if (!stats.isFile())
|
|
348
352
|
throw new Error("file_path must point to a readable file.");
|
|
353
|
+
if (stats.size > MAX_UPLOAD_BYTES)
|
|
354
|
+
throw new Error(`file_path exceeds the ${MAX_UPLOAD_BYTES}-byte upload limit.`);
|
|
349
355
|
await fs.promises.access(resolved, fs.constants.R_OK);
|
|
350
|
-
const auth = await access(connection);
|
|
356
|
+
const auth = await access(connection, options);
|
|
351
357
|
const form = new FormData();
|
|
352
358
|
form.append(
|
|
353
359
|
"files",
|
|
354
|
-
new Blob([await fs.promises.readFile(resolved)]),
|
|
360
|
+
new Blob([await fs.promises.readFile(resolved, { signal: options.signal })]),
|
|
355
361
|
path.basename(resolved),
|
|
356
362
|
);
|
|
357
|
-
const response = await
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
+
const { response, text: responseText } = await fetchResponseText(
|
|
364
|
+
`${auth.baseUrl}/api/v1/documents`,
|
|
365
|
+
{
|
|
366
|
+
method: "POST",
|
|
367
|
+
headers: { Authorization: `Bearer ${auth.accessToken}` },
|
|
368
|
+
body: form,
|
|
369
|
+
signal: options.signal,
|
|
370
|
+
timeoutMs: 120000,
|
|
371
|
+
},
|
|
372
|
+
{ serviceName: "NeoArchive upload" },
|
|
373
|
+
);
|
|
374
|
+
let result = null;
|
|
375
|
+
try {
|
|
376
|
+
result = responseText ? JSON.parse(responseText) : null;
|
|
377
|
+
} catch {
|
|
378
|
+
result = null;
|
|
379
|
+
}
|
|
363
380
|
if (!response.ok)
|
|
364
381
|
throw new Error(
|
|
365
382
|
`NeoArchive upload request failed: ${result?.error || response.statusText}`,
|
|
366
383
|
);
|
|
367
384
|
return { result, credentials: auth.credentials };
|
|
368
385
|
}
|
|
369
|
-
async function execute(toolName, args, connection) {
|
|
386
|
+
async function execute(toolName, args, connection, options = {}) {
|
|
370
387
|
let pathName;
|
|
371
388
|
let method = "GET";
|
|
372
389
|
let json;
|
|
@@ -401,7 +418,7 @@ async function execute(toolName, args, connection) {
|
|
|
401
418
|
pathName = "/api/v1/document-types";
|
|
402
419
|
break;
|
|
403
420
|
case "neoarchive_upload_document":
|
|
404
|
-
return upload(connection, args.file_path);
|
|
421
|
+
return upload(connection, args.file_path, options);
|
|
405
422
|
case "neoarchive_update_document":
|
|
406
423
|
pathName = `/api/v1/documents/${encodeURIComponent(required(args.document_id, "document_id"))}`;
|
|
407
424
|
method = "PATCH";
|
|
@@ -426,7 +443,11 @@ async function execute(toolName, args, connection) {
|
|
|
426
443
|
default:
|
|
427
444
|
throw new Error(`Unsupported NeoArchive tool: ${toolName}`);
|
|
428
445
|
}
|
|
429
|
-
const result = await request(connection, pathName, {
|
|
446
|
+
const result = await request(connection, pathName, {
|
|
447
|
+
method,
|
|
448
|
+
json,
|
|
449
|
+
signal: options.signal,
|
|
450
|
+
});
|
|
430
451
|
return { result: result.response, credentials: result.credentials };
|
|
431
452
|
}
|
|
432
453
|
|
|
@@ -483,7 +504,7 @@ function createNeoArchiveProvider() {
|
|
|
483
504
|
? "NeoArchive: setup is ready, but no archive account is connected."
|
|
484
505
|
: "NeoArchive: connected with document search, metadata, text, upload, archive, and reprocessing tools.";
|
|
485
506
|
},
|
|
486
|
-
async beginOAuth({ state, codeVerifier, userId, agentId, appKey }) {
|
|
507
|
+
async beginOAuth({ state, codeVerifier, userId, agentId, appKey, signal }) {
|
|
487
508
|
if (text(appKey) !== APP.id) throw new Error("Unknown NeoArchive app.");
|
|
488
509
|
const stored = config(
|
|
489
510
|
getProviderConfig(
|
|
@@ -493,7 +514,7 @@ function createNeoArchiveProvider() {
|
|
|
493
514
|
),
|
|
494
515
|
);
|
|
495
516
|
const baseUrl = normalizeBaseUrl(stored.baseUrl);
|
|
496
|
-
const boot = await bootstrap(baseUrl);
|
|
517
|
+
const boot = await bootstrap(baseUrl, { signal });
|
|
497
518
|
const challenge = crypto
|
|
498
519
|
.createHash("sha256")
|
|
499
520
|
.update(String(codeVerifier))
|
|
@@ -515,7 +536,7 @@ function createNeoArchiveProvider() {
|
|
|
515
536
|
),
|
|
516
537
|
};
|
|
517
538
|
},
|
|
518
|
-
async finishOAuth({ userId, agentId, code, codeVerifier }) {
|
|
539
|
+
async finishOAuth({ userId, agentId, code, codeVerifier, signal }) {
|
|
519
540
|
const stored = config(
|
|
520
541
|
getProviderConfig(
|
|
521
542
|
Number(userId),
|
|
@@ -524,21 +545,24 @@ function createNeoArchiveProvider() {
|
|
|
524
545
|
),
|
|
525
546
|
);
|
|
526
547
|
const baseUrl = normalizeBaseUrl(stored.baseUrl);
|
|
527
|
-
const boot = await bootstrap(baseUrl);
|
|
548
|
+
const boot = await bootstrap(baseUrl, { signal });
|
|
528
549
|
const issued = await token(baseUrl, {
|
|
529
550
|
grant_type: "authorization_code",
|
|
530
551
|
client_id: boot.clientId,
|
|
531
552
|
code: text(code),
|
|
532
553
|
redirect_uri: text(boot.redirectUri) || callbackUrl(),
|
|
533
554
|
code_verifier: text(codeVerifier),
|
|
534
|
-
});
|
|
555
|
+
}, { signal });
|
|
535
556
|
const accessToken = text(issued.access_token);
|
|
536
557
|
const refreshToken = text(issued.refresh_token);
|
|
537
558
|
if (!accessToken || !refreshToken)
|
|
538
559
|
throw new Error("NeoArchive did not return durable OAuth credentials.");
|
|
539
560
|
const info = await fetchJson(
|
|
540
561
|
`${baseUrl}/oauth/userinfo`,
|
|
541
|
-
{
|
|
562
|
+
{
|
|
563
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
564
|
+
signal,
|
|
565
|
+
},
|
|
542
566
|
{ serviceName: "NeoArchive userinfo" },
|
|
543
567
|
);
|
|
544
568
|
const host = new URL(baseUrl).host;
|
|
@@ -565,8 +589,10 @@ function createNeoArchiveProvider() {
|
|
|
565
589
|
},
|
|
566
590
|
};
|
|
567
591
|
},
|
|
568
|
-
async executeTool(toolName, args, connection) {
|
|
569
|
-
return execute(toolName, args || {}, connection
|
|
592
|
+
async executeTool(toolName, args, connection, executionOptions = {}) {
|
|
593
|
+
return execute(toolName, args || {}, connection, {
|
|
594
|
+
signal: executionOptions.signal || null,
|
|
595
|
+
});
|
|
570
596
|
},
|
|
571
597
|
getUserConfig({ userId, agentId }) {
|
|
572
598
|
const scoped = resolveAgentId(Number(userId), agentId);
|
|
@@ -584,17 +610,17 @@ function createNeoArchiveProvider() {
|
|
|
584
610
|
hasConnectedAccount: accountCount > 0,
|
|
585
611
|
};
|
|
586
612
|
},
|
|
587
|
-
async saveUserConfig({ userId, agentId, config: input }) {
|
|
613
|
+
async saveUserConfig({ userId, agentId, config: input, signal }) {
|
|
588
614
|
const scoped = resolveAgentId(Number(userId), agentId);
|
|
589
615
|
const existing = config(getProviderConfig(Number(userId), KEY, scoped));
|
|
590
616
|
const parsed = config(input, existing);
|
|
591
617
|
const baseUrl = normalizeBaseUrl(parsed.baseUrl);
|
|
592
618
|
await fetchJson(
|
|
593
619
|
`${baseUrl}/api/v1/health`,
|
|
594
|
-
{ method: "GET" },
|
|
620
|
+
{ method: "GET", signal },
|
|
595
621
|
{ serviceName: "NeoArchive health check" },
|
|
596
622
|
);
|
|
597
|
-
await bootstrap(baseUrl);
|
|
623
|
+
await bootstrap(baseUrl, { signal });
|
|
598
624
|
setProviderConfig(Number(userId), KEY, { baseUrl }, scoped);
|
|
599
625
|
if (existing.baseUrl && existing.baseUrl !== baseUrl)
|
|
600
626
|
db.prepare(
|
|
@@ -29,17 +29,22 @@ function normalizeBaseUrl(value) {
|
|
|
29
29
|
return url.toString().replace(/\/+$/, '');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
async function bootstrap(baseUrl, callbackUrl) {
|
|
32
|
+
async function bootstrap(baseUrl, callbackUrl, options = {}) {
|
|
33
33
|
return fetchJson(`${baseUrl}/api/oauth/companion/neoagent/bootstrap`, {
|
|
34
34
|
method: 'POST', json: { redirectUri: callbackUrl, appName: 'NeoAgent' },
|
|
35
|
+
signal: options.signal,
|
|
35
36
|
}, { serviceName: 'NeoRecall companion bootstrap' });
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
async function token(baseUrl, form) {
|
|
39
|
-
return fetchJson(
|
|
39
|
+
async function token(baseUrl, form, options = {}) {
|
|
40
|
+
return fetchJson(
|
|
41
|
+
`${baseUrl}/oauth/token`,
|
|
42
|
+
{ method: 'POST', form, signal: options.signal },
|
|
43
|
+
{ serviceName: 'NeoRecall OAuth token' },
|
|
44
|
+
);
|
|
40
45
|
}
|
|
41
46
|
|
|
42
|
-
async function revoke(credentials) {
|
|
47
|
+
async function revoke(credentials, options = {}) {
|
|
43
48
|
const saved = credentials && typeof credentials === 'object' ? credentials : {};
|
|
44
49
|
const baseUrl = normalizeBaseUrl(saved.baseUrl);
|
|
45
50
|
const clientId = text(saved.client_id);
|
|
@@ -47,12 +52,13 @@ async function revoke(credentials) {
|
|
|
47
52
|
const tokens = [saved.refresh_token, saved.access_token].map(text).filter(Boolean);
|
|
48
53
|
const outcomes = await Promise.allSettled(tokens.map((value) => fetchJson(`${baseUrl}/oauth/revoke`, {
|
|
49
54
|
method: 'POST', form: { client_id: clientId, token: value },
|
|
55
|
+
signal: options.signal,
|
|
50
56
|
}, { serviceName: 'NeoRecall OAuth revocation' })));
|
|
51
57
|
const failed = outcomes.find((outcome) => outcome.status === 'rejected');
|
|
52
58
|
if (failed) throw failed.reason;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
async function authenticated(credentials) {
|
|
61
|
+
async function authenticated(credentials, options = {}) {
|
|
56
62
|
const saved = credentials && typeof credentials === 'object' ? credentials : {};
|
|
57
63
|
const baseUrl = normalizeBaseUrl(saved.baseUrl);
|
|
58
64
|
if (text(saved.access_token) && Number(saved.expires_at_ms) > Date.now() + 60_000) {
|
|
@@ -63,7 +69,7 @@ async function authenticated(credentials) {
|
|
|
63
69
|
}
|
|
64
70
|
const refreshed = await token(baseUrl, {
|
|
65
71
|
grant_type: 'refresh_token', client_id: saved.client_id, refresh_token: saved.refresh_token,
|
|
66
|
-
});
|
|
72
|
+
}, options);
|
|
67
73
|
const next = {
|
|
68
74
|
...saved,
|
|
69
75
|
access_token: text(refreshed.access_token),
|
|
@@ -97,15 +103,16 @@ function appendApiQuery(path, query) {
|
|
|
97
103
|
return encoded ? `${path}?${encoded}` : path;
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
async function request(credentials, apiPath) {
|
|
101
|
-
const authorization = await authenticated(credentials);
|
|
106
|
+
async function request(credentials, apiPath, options = {}) {
|
|
107
|
+
const authorization = await authenticated(credentials, options);
|
|
102
108
|
const response = await fetchJson(`${authorization.baseUrl}${apiPath}`, {
|
|
103
109
|
headers: { Authorization: `Bearer ${authorization.accessToken}` },
|
|
110
|
+
signal: options.signal,
|
|
104
111
|
}, { serviceName: 'NeoRecall API' });
|
|
105
112
|
return { result: response, credentials: authorization.credentials };
|
|
106
113
|
}
|
|
107
114
|
|
|
108
|
-
async function executeTool(toolName, args, credentials) {
|
|
115
|
+
async function executeTool(toolName, args, credentials, options = {}) {
|
|
109
116
|
let apiPath;
|
|
110
117
|
switch (toolName) {
|
|
111
118
|
case 'neorecall_search': {
|
|
@@ -134,7 +141,7 @@ async function executeTool(toolName, args, credentials) {
|
|
|
134
141
|
default:
|
|
135
142
|
throw new Error(`Unsupported NeoRecall tool: ${toolName}`);
|
|
136
143
|
}
|
|
137
|
-
return request(credentials, apiPath);
|
|
144
|
+
return request(credentials, apiPath, options);
|
|
138
145
|
}
|
|
139
146
|
|
|
140
147
|
module.exports = { bootstrap, executeTool, normalizeBaseUrl, revoke, text, token };
|
|
@@ -71,9 +71,9 @@ function createNeoRecallProvider() {
|
|
|
71
71
|
connectionMethod: 'user_config',
|
|
72
72
|
requiresRefreshToken: true,
|
|
73
73
|
getEnvStatus: envStatus,
|
|
74
|
-
async beginOAuth({ state, codeVerifier, userId, agentId }) {
|
|
74
|
+
async beginOAuth({ state, codeVerifier, userId, agentId, signal }) {
|
|
75
75
|
const baseUrl = normalizeBaseUrl(storedConfig(userId, resolveAgentId(userId, agentId)).baseUrl);
|
|
76
|
-
const boot = await bootstrap(baseUrl, callbackUrl());
|
|
76
|
+
const boot = await bootstrap(baseUrl, callbackUrl(), { signal });
|
|
77
77
|
const challenge = crypto.createHash('sha256').update(String(codeVerifier)).digest('base64url');
|
|
78
78
|
return {
|
|
79
79
|
url: appendQuery(text(boot.authorizationEndpoint) || `${baseUrl}/oauth/authorize`, {
|
|
@@ -83,18 +83,19 @@ function createNeoRecallProvider() {
|
|
|
83
83
|
}),
|
|
84
84
|
};
|
|
85
85
|
},
|
|
86
|
-
async finishOAuth({ userId, agentId, code, codeVerifier }) {
|
|
86
|
+
async finishOAuth({ userId, agentId, code, codeVerifier, signal }) {
|
|
87
87
|
const baseUrl = normalizeBaseUrl(storedConfig(userId, resolveAgentId(userId, agentId)).baseUrl);
|
|
88
|
-
const boot = await bootstrap(baseUrl, callbackUrl());
|
|
88
|
+
const boot = await bootstrap(baseUrl, callbackUrl(), { signal });
|
|
89
89
|
const issued = await token(baseUrl, {
|
|
90
90
|
grant_type: 'authorization_code', client_id: boot.clientId, code: text(code),
|
|
91
91
|
redirect_uri: text(boot.redirectUri) || callbackUrl(), code_verifier: text(codeVerifier),
|
|
92
|
-
});
|
|
92
|
+
}, { signal });
|
|
93
93
|
const accessToken = text(issued.access_token);
|
|
94
94
|
const refreshToken = text(issued.refresh_token);
|
|
95
95
|
if (!accessToken || !refreshToken) throw new Error('NeoRecall did not return durable OAuth credentials.');
|
|
96
96
|
const info = await fetchJson(`${baseUrl}/oauth/userinfo`, {
|
|
97
97
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
98
|
+
signal,
|
|
98
99
|
}, { serviceName: 'NeoRecall userinfo' });
|
|
99
100
|
const host = new URL(baseUrl).host;
|
|
100
101
|
const accountEmail = text(info.email) || text(info.preferred_username) || `neorecall:${text(info.sub) || host}`;
|
|
@@ -109,10 +110,14 @@ function createNeoRecallProvider() {
|
|
|
109
110
|
};
|
|
110
111
|
},
|
|
111
112
|
executeTool(toolName, args, context) {
|
|
112
|
-
return executeTool(toolName, args || {}, context.credentials
|
|
113
|
+
return executeTool(toolName, args || {}, context.credentials, {
|
|
114
|
+
signal: context.signal,
|
|
115
|
+
});
|
|
113
116
|
},
|
|
114
|
-
disconnect(connection) {
|
|
115
|
-
return revoke(connectionCredentials(connection)
|
|
117
|
+
disconnect(connection, executionOptions = {}) {
|
|
118
|
+
return revoke(connectionCredentials(connection), {
|
|
119
|
+
signal: executionOptions.signal || null,
|
|
120
|
+
});
|
|
116
121
|
},
|
|
117
122
|
});
|
|
118
123
|
|
|
@@ -122,16 +127,20 @@ function createNeoRecallProvider() {
|
|
|
122
127
|
const accountCount = connectedAccountCount(Number(userId), scoped);
|
|
123
128
|
return { baseUrl: stored.baseUrl, configured: Boolean(stored.baseUrl), accountCount, hasConnectedAccount: accountCount > 0 };
|
|
124
129
|
};
|
|
125
|
-
provider.saveUserConfig = async ({ userId, agentId, config }) => {
|
|
130
|
+
provider.saveUserConfig = async ({ userId, agentId, config, signal }) => {
|
|
126
131
|
const normalizedUserId = Number(userId);
|
|
127
132
|
const scoped = resolveAgentId(normalizedUserId, agentId);
|
|
128
133
|
const existing = storedConfig(normalizedUserId, scoped);
|
|
129
134
|
const baseUrl = normalizeBaseUrl(parseConfig(config, existing).baseUrl);
|
|
130
|
-
const health = await fetchJson(
|
|
135
|
+
const health = await fetchJson(
|
|
136
|
+
`${baseUrl}/health`,
|
|
137
|
+
{ method: 'GET', signal },
|
|
138
|
+
{ serviceName: 'NeoRecall health check' },
|
|
139
|
+
);
|
|
131
140
|
if (text(health?.status) !== 'ok' || text(health?.process) !== 'http') {
|
|
132
141
|
throw new Error('The configured endpoint did not identify itself as a healthy NeoRecall HTTP service.');
|
|
133
142
|
}
|
|
134
|
-
const boot = await bootstrap(baseUrl, callbackUrl());
|
|
143
|
+
const boot = await bootstrap(baseUrl, callbackUrl(), { signal });
|
|
135
144
|
if (text(boot?.companion) !== 'neoagent' || !text(boot?.clientId)) {
|
|
136
145
|
throw new Error('The NeoRecall server does not expose the NeoAgent companion OAuth contract.');
|
|
137
146
|
}
|
|
@@ -187,7 +187,8 @@ function notionUrl(path, query) {
|
|
|
187
187
|
return url.toString();
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
async function notionRequest(
|
|
190
|
+
async function notionRequest(context, { method = 'GET', path, query, body }) {
|
|
191
|
+
const { credentials, signal } = context;
|
|
191
192
|
return fetchJson(
|
|
192
193
|
notionUrl(path, query),
|
|
193
194
|
{
|
|
@@ -197,16 +198,17 @@ async function notionRequest(credentials, { method = 'GET', path, query, body })
|
|
|
197
198
|
'Notion-Version': NOTION_VERSION,
|
|
198
199
|
},
|
|
199
200
|
...(body === undefined ? {} : { json: body }),
|
|
201
|
+
signal,
|
|
200
202
|
},
|
|
201
203
|
{ serviceName: 'Notion' },
|
|
202
204
|
);
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
async function executeNotionTool(toolName, args,
|
|
207
|
+
async function executeNotionTool(toolName, args, context) {
|
|
206
208
|
switch (toolName) {
|
|
207
209
|
case 'notion_search':
|
|
208
210
|
return {
|
|
209
|
-
result: await notionRequest(
|
|
211
|
+
result: await notionRequest(context, {
|
|
210
212
|
method: 'POST',
|
|
211
213
|
path: '/v1/search',
|
|
212
214
|
body: {
|
|
@@ -218,13 +220,13 @@ async function executeNotionTool(toolName, args, { credentials }) {
|
|
|
218
220
|
};
|
|
219
221
|
case 'notion_get_page':
|
|
220
222
|
return {
|
|
221
|
-
result: await notionRequest(
|
|
223
|
+
result: await notionRequest(context, {
|
|
222
224
|
path: `/v1/pages/${encodeURIComponent(requireText(args.page_id, 'page_id'))}`,
|
|
223
225
|
}),
|
|
224
226
|
};
|
|
225
227
|
case 'notion_create_page':
|
|
226
228
|
return {
|
|
227
|
-
result: await notionRequest(
|
|
229
|
+
result: await notionRequest(context, {
|
|
228
230
|
method: 'POST',
|
|
229
231
|
path: '/v1/pages',
|
|
230
232
|
body: {
|
|
@@ -236,7 +238,7 @@ async function executeNotionTool(toolName, args, { credentials }) {
|
|
|
236
238
|
};
|
|
237
239
|
case 'notion_update_page':
|
|
238
240
|
return {
|
|
239
|
-
result: await notionRequest(
|
|
241
|
+
result: await notionRequest(context, {
|
|
240
242
|
method: 'PATCH',
|
|
241
243
|
path: `/v1/pages/${encodeURIComponent(requireText(args.page_id, 'page_id'))}`,
|
|
242
244
|
body: {
|
|
@@ -249,7 +251,7 @@ async function executeNotionTool(toolName, args, { credentials }) {
|
|
|
249
251
|
};
|
|
250
252
|
case 'notion_query_database':
|
|
251
253
|
return {
|
|
252
|
-
result: await notionRequest(
|
|
254
|
+
result: await notionRequest(context, {
|
|
253
255
|
method: 'POST',
|
|
254
256
|
path: `/v1/databases/${encodeURIComponent(requireText(args.database_id, 'database_id'))}/query`,
|
|
255
257
|
body: {
|
|
@@ -261,14 +263,14 @@ async function executeNotionTool(toolName, args, { credentials }) {
|
|
|
261
263
|
};
|
|
262
264
|
case 'notion_get_block_children':
|
|
263
265
|
return {
|
|
264
|
-
result: await notionRequest(
|
|
266
|
+
result: await notionRequest(context, {
|
|
265
267
|
path: `/v1/blocks/${encodeURIComponent(requireText(args.block_id, 'block_id'))}/children`,
|
|
266
268
|
query: { page_size: Math.max(1, Math.min(Number(args.page_size) || 25, 100)) },
|
|
267
269
|
}),
|
|
268
270
|
};
|
|
269
271
|
case 'notion_append_block_children':
|
|
270
272
|
return {
|
|
271
|
-
result: await notionRequest(
|
|
273
|
+
result: await notionRequest(context, {
|
|
272
274
|
method: 'PATCH',
|
|
273
275
|
path: `/v1/blocks/${encodeURIComponent(requireText(args.block_id, 'block_id'))}/children`,
|
|
274
276
|
body: { children: Array.isArray(args.children) ? args.children : [] },
|
|
@@ -276,7 +278,7 @@ async function executeNotionTool(toolName, args, { credentials }) {
|
|
|
276
278
|
};
|
|
277
279
|
case 'notion_update_block':
|
|
278
280
|
return {
|
|
279
|
-
result: await notionRequest(
|
|
281
|
+
result: await notionRequest(context, {
|
|
280
282
|
method: 'PATCH',
|
|
281
283
|
path: `/v1/blocks/${encodeURIComponent(requireText(args.block_id, 'block_id'))}`,
|
|
282
284
|
body: args.body || {},
|
|
@@ -284,14 +286,14 @@ async function executeNotionTool(toolName, args, { credentials }) {
|
|
|
284
286
|
};
|
|
285
287
|
case 'notion_delete_block':
|
|
286
288
|
return {
|
|
287
|
-
result: await notionRequest(
|
|
289
|
+
result: await notionRequest(context, {
|
|
288
290
|
method: 'DELETE',
|
|
289
291
|
path: `/v1/blocks/${encodeURIComponent(requireText(args.block_id, 'block_id'))}`,
|
|
290
292
|
}),
|
|
291
293
|
};
|
|
292
294
|
case 'notion_api_request':
|
|
293
295
|
return {
|
|
294
|
-
result: await notionRequest(
|
|
296
|
+
result: await notionRequest(context, {
|
|
295
297
|
method: args.method,
|
|
296
298
|
path: requireText(args.path, 'path'),
|
|
297
299
|
query: args.query,
|
|
@@ -332,7 +334,7 @@ function createNotionProvider() {
|
|
|
332
334
|
appId: app.id,
|
|
333
335
|
};
|
|
334
336
|
},
|
|
335
|
-
async finishOAuth({ code, app }) {
|
|
337
|
+
async finishOAuth({ code, app, signal }) {
|
|
336
338
|
const config = resolveNotionOAuthConfig();
|
|
337
339
|
const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString(
|
|
338
340
|
'base64',
|
|
@@ -349,6 +351,7 @@ function createNotionProvider() {
|
|
|
349
351
|
code,
|
|
350
352
|
redirect_uri: config.redirectUri,
|
|
351
353
|
},
|
|
354
|
+
signal,
|
|
352
355
|
},
|
|
353
356
|
{ serviceName: 'Notion' },
|
|
354
357
|
);
|