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
package/lib/schema_migrations.js
CHANGED
|
@@ -30,20 +30,41 @@ function migrateMemoryEmbeddingIndex(db) {
|
|
|
30
30
|
.map((column) => column.name),
|
|
31
31
|
);
|
|
32
32
|
if (!columns.has('index_version')) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
user_id,
|
|
40
|
-
agent_id,
|
|
41
|
-
dimension,
|
|
42
|
-
index_version,
|
|
43
|
-
band_index,
|
|
44
|
-
band_value
|
|
33
|
+
try {
|
|
34
|
+
db.exec('ALTER TABLE memory_embedding_bands ADD COLUMN index_version INTEGER');
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const refreshedColumns = new Set(
|
|
37
|
+
db.prepare('PRAGMA table_info(memory_embedding_bands)').all()
|
|
38
|
+
.map((column) => column.name),
|
|
45
39
|
);
|
|
46
|
-
|
|
40
|
+
if (!refreshedColumns.has('index_version')) throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const expectedIndexColumns = [
|
|
44
|
+
'user_id',
|
|
45
|
+
'agent_id',
|
|
46
|
+
'dimension',
|
|
47
|
+
'index_version',
|
|
48
|
+
'band_index',
|
|
49
|
+
'band_value',
|
|
50
|
+
];
|
|
51
|
+
const indexColumns = db.prepare(
|
|
52
|
+
'PRAGMA index_info(idx_memory_embedding_bands_lookup)',
|
|
53
|
+
).all().map((column) => column.name);
|
|
54
|
+
if (indexColumns.join('\0') !== expectedIndexColumns.join('\0')) {
|
|
55
|
+
db.exec(`
|
|
56
|
+
DROP INDEX IF EXISTS idx_memory_embedding_bands_lookup;
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_memory_embedding_bands_lookup
|
|
58
|
+
ON memory_embedding_bands(
|
|
59
|
+
user_id,
|
|
60
|
+
agent_id,
|
|
61
|
+
dimension,
|
|
62
|
+
index_version,
|
|
63
|
+
band_index,
|
|
64
|
+
band_value
|
|
65
|
+
);
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
47
68
|
}
|
|
48
69
|
|
|
49
70
|
function removeRetiredCaptureData(db) {
|
|
@@ -661,6 +682,83 @@ function migrateAgentRunLifecycle(db) {
|
|
|
661
682
|
`);
|
|
662
683
|
}
|
|
663
684
|
|
|
685
|
+
function migrateMessagingInboundJobs(db) {
|
|
686
|
+
db.exec(`
|
|
687
|
+
CREATE TABLE IF NOT EXISTS messaging_inbound_jobs (
|
|
688
|
+
id TEXT PRIMARY KEY,
|
|
689
|
+
message_id INTEGER NOT NULL UNIQUE,
|
|
690
|
+
user_id INTEGER NOT NULL,
|
|
691
|
+
agent_id TEXT,
|
|
692
|
+
platform TEXT NOT NULL,
|
|
693
|
+
platform_msg_id TEXT,
|
|
694
|
+
platform_chat_id TEXT,
|
|
695
|
+
payload_json TEXT NOT NULL,
|
|
696
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
697
|
+
CHECK(status IN ('pending', 'processing', 'completed', 'failed')),
|
|
698
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
699
|
+
run_id TEXT,
|
|
700
|
+
last_error TEXT,
|
|
701
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
702
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
703
|
+
completed_at TEXT,
|
|
704
|
+
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE,
|
|
705
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
706
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
CREATE INDEX IF NOT EXISTS idx_messaging_inbound_jobs_pending
|
|
710
|
+
ON messaging_inbound_jobs(status, platform, user_id, agent_id, created_at);
|
|
711
|
+
CREATE INDEX IF NOT EXISTS idx_messaging_inbound_jobs_run
|
|
712
|
+
ON messaging_inbound_jobs(run_id);
|
|
713
|
+
`);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function migrateCredentialBroker(db) {
|
|
717
|
+
db.exec(`
|
|
718
|
+
CREATE TABLE IF NOT EXISTS credential_bindings (
|
|
719
|
+
id TEXT PRIMARY KEY,
|
|
720
|
+
user_id INTEGER NOT NULL,
|
|
721
|
+
agent_id TEXT NOT NULL,
|
|
722
|
+
provider_key TEXT NOT NULL,
|
|
723
|
+
connection_id INTEGER NOT NULL,
|
|
724
|
+
alias TEXT NOT NULL,
|
|
725
|
+
usage_type TEXT NOT NULL CHECK(usage_type IN ('browser', 'http')),
|
|
726
|
+
item_ref_encrypted TEXT NOT NULL,
|
|
727
|
+
field_config_encrypted TEXT NOT NULL,
|
|
728
|
+
target_config_json TEXT NOT NULL DEFAULT '{}',
|
|
729
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
730
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
731
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
732
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,
|
|
733
|
+
FOREIGN KEY (connection_id) REFERENCES integration_connections(id) ON DELETE CASCADE,
|
|
734
|
+
UNIQUE(user_id, agent_id, alias)
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
CREATE INDEX IF NOT EXISTS idx_credential_bindings_scope
|
|
738
|
+
ON credential_bindings(user_id, agent_id, provider_key, usage_type);
|
|
739
|
+
|
|
740
|
+
CREATE TABLE IF NOT EXISTS credential_usage_audit (
|
|
741
|
+
id TEXT PRIMARY KEY,
|
|
742
|
+
user_id INTEGER NOT NULL,
|
|
743
|
+
agent_id TEXT NOT NULL,
|
|
744
|
+
run_id TEXT,
|
|
745
|
+
binding_id TEXT,
|
|
746
|
+
operation TEXT NOT NULL,
|
|
747
|
+
target TEXT,
|
|
748
|
+
outcome TEXT NOT NULL,
|
|
749
|
+
error_code TEXT,
|
|
750
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
751
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
752
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,
|
|
753
|
+
FOREIGN KEY (run_id) REFERENCES agent_runs(id) ON DELETE SET NULL,
|
|
754
|
+
FOREIGN KEY (binding_id) REFERENCES credential_bindings(id) ON DELETE SET NULL
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
CREATE INDEX IF NOT EXISTS idx_credential_usage_audit_scope
|
|
758
|
+
ON credential_usage_audit(user_id, agent_id, created_at DESC);
|
|
759
|
+
`);
|
|
760
|
+
}
|
|
761
|
+
|
|
664
762
|
function runSchemaMigrations(db) {
|
|
665
763
|
removeRetiredCaptureData(db);
|
|
666
764
|
migrateMemoryEmbeddingIndex(db);
|
|
@@ -676,6 +774,8 @@ function runSchemaMigrations(db) {
|
|
|
676
774
|
migrateToolPoliciesAllowAlways(db);
|
|
677
775
|
migrateBilling(db);
|
|
678
776
|
migrateAgentRunLifecycle(db);
|
|
777
|
+
migrateMessagingInboundJobs(db);
|
|
778
|
+
migrateCredentialBroker(db);
|
|
679
779
|
}
|
|
680
780
|
|
|
681
781
|
module.exports = {
|
|
@@ -694,5 +794,7 @@ module.exports = {
|
|
|
694
794
|
migrateToolPoliciesAllowAlways,
|
|
695
795
|
migrateBilling,
|
|
696
796
|
migrateAgentRunLifecycle,
|
|
797
|
+
migrateMessagingInboundJobs,
|
|
798
|
+
migrateCredentialBroker,
|
|
697
799
|
runSchemaMigrations,
|
|
698
800
|
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neoagent",
|
|
3
|
-
"version": "3.2.1-beta.
|
|
3
|
+
"version": "3.2.1-beta.10",
|
|
4
4
|
"description": "Self-hosted AI agent for long-running tasks, automation, messaging, device control, and local memory",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"main": "server/index.js",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": ">=
|
|
8
|
+
"node": ">=20.19.0"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
11
|
"neoagent": "bin/neoagent.js"
|
|
@@ -67,10 +67,11 @@
|
|
|
67
67
|
"homepage": "https://github.com/NeoLabs-Systems/NeoAgent#readme",
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
70
|
-
"@
|
|
71
|
-
"@
|
|
70
|
+
"@bitwarden/cli": "^2026.6.0",
|
|
71
|
+
"@google/genai": "^2.13.0",
|
|
72
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
72
73
|
"@remotion/cli": "^4.0.459",
|
|
73
|
-
"@slidev/cli": "^52.
|
|
74
|
+
"@slidev/cli": "^52.18.0",
|
|
74
75
|
"@slidev/theme-default": "^0.25.0",
|
|
75
76
|
"baileys": "^6.7.21",
|
|
76
77
|
"bcrypt": "^6.0.0",
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
"better-sqlite3-session-store": "^0.1.0",
|
|
79
80
|
"cheerio": "^1.0.0-rc.12",
|
|
80
81
|
"cors": "^2.8.5",
|
|
81
|
-
"discord.js": "^14.
|
|
82
|
+
"discord.js": "^14.27.0",
|
|
82
83
|
"dotenv": "^16.4.7",
|
|
83
84
|
"express": "^4.21.2",
|
|
84
85
|
"express-rate-limit": "^7.5.0",
|
|
@@ -86,10 +87,10 @@
|
|
|
86
87
|
"geoip-lite": "^1.4.10",
|
|
87
88
|
"googleapis": "^150.0.1",
|
|
88
89
|
"helmet": "^8.0.0",
|
|
89
|
-
"multer": "^
|
|
90
|
-
"node-cron": "^
|
|
90
|
+
"multer": "^2.2.0",
|
|
91
|
+
"node-cron": "^4.6.0",
|
|
91
92
|
"node-pty": "^1.0.0",
|
|
92
|
-
"nodemailer": "^
|
|
93
|
+
"nodemailer": "^9.0.3",
|
|
93
94
|
"openai": "^4.85.4",
|
|
94
95
|
"otplib": "^13.4.0",
|
|
95
96
|
"playwright-chromium": "^1.59.1",
|
|
@@ -99,7 +100,7 @@
|
|
|
99
100
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
100
101
|
"qrcode": "^1.5.4",
|
|
101
102
|
"remotion": "^4.0.459",
|
|
102
|
-
"sharp": "^0.
|
|
103
|
+
"sharp": "^0.35.3",
|
|
103
104
|
"socket.io": "^4.8.1",
|
|
104
105
|
"stripe": "^22.2.1",
|
|
105
106
|
"telegraf": "^4.16.3",
|
|
@@ -109,19 +110,33 @@
|
|
|
109
110
|
"ws": "^8.21.0"
|
|
110
111
|
},
|
|
111
112
|
"overrides": {
|
|
113
|
+
"@bitwarden/cli": {
|
|
114
|
+
"form-data": "4.0.6",
|
|
115
|
+
"inquirer": "8.2.7",
|
|
116
|
+
"multer": "2.2.0"
|
|
117
|
+
},
|
|
118
|
+
"@hono/node-server": "^2.0.11",
|
|
112
119
|
"axios": "^1.15.2",
|
|
113
120
|
"basic-ftp": "^5.3.0",
|
|
121
|
+
"dompurify": "^3.4.12",
|
|
122
|
+
"fast-uri": "^3.1.4",
|
|
123
|
+
"form-data": "4.0.6",
|
|
114
124
|
"follow-redirects": "^1.16.0",
|
|
115
|
-
"hono": "^4.12.
|
|
116
|
-
"
|
|
125
|
+
"hono": "^4.12.31",
|
|
126
|
+
"ip-address": "^10.2.0",
|
|
127
|
+
"linkify-it": "^5.0.2",
|
|
128
|
+
"protobufjs": "^7.6.5",
|
|
117
129
|
"serialize-javascript": "^7.0.5",
|
|
118
|
-
"
|
|
130
|
+
"tmp": "0.2.7",
|
|
131
|
+
"undici": "^6.27.0",
|
|
132
|
+
"uuid": "$uuid",
|
|
119
133
|
"webpackbar": "^7.0.0",
|
|
120
134
|
"ws": "^8.21.0"
|
|
121
135
|
},
|
|
122
136
|
"devDependencies": {
|
|
123
|
-
"@docusaurus/core": "3.10.
|
|
124
|
-
"@docusaurus/preset-classic": "3.10.
|
|
137
|
+
"@docusaurus/core": "3.10.2",
|
|
138
|
+
"@docusaurus/preset-classic": "3.10.2",
|
|
139
|
+
"@types/node": "^20.19.0",
|
|
125
140
|
"autocannon": "^7.15.0",
|
|
126
141
|
"react": "18.3.1",
|
|
127
142
|
"react-dom": "18.3.1",
|
package/runtime/paths.js
CHANGED
|
@@ -109,26 +109,69 @@ function readEnvFileRaw(envFile = ENV_FILE) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
function writeEnvFileAtomic(envFile, content) {
|
|
113
|
+
const resolved = path.resolve(envFile);
|
|
114
|
+
const directory = path.dirname(resolved);
|
|
115
|
+
const temporary = path.join(
|
|
116
|
+
directory,
|
|
117
|
+
`.${path.basename(resolved)}.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`,
|
|
118
|
+
);
|
|
119
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
120
|
+
let descriptor = null;
|
|
121
|
+
try {
|
|
122
|
+
descriptor = fs.openSync(temporary, 'wx', 0o600);
|
|
123
|
+
fs.writeFileSync(descriptor, content, 'utf8');
|
|
124
|
+
fs.fsyncSync(descriptor);
|
|
125
|
+
fs.closeSync(descriptor);
|
|
126
|
+
descriptor = null;
|
|
127
|
+
fs.renameSync(temporary, resolved);
|
|
128
|
+
} finally {
|
|
129
|
+
if (descriptor !== null) {
|
|
130
|
+
try { fs.closeSync(descriptor); } catch {}
|
|
131
|
+
}
|
|
132
|
+
try { fs.rmSync(temporary, { force: true }); } catch {}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function normalizeEnvKey(key) {
|
|
137
|
+
const normalized = String(key).replace(/[\r\n]/g, '');
|
|
138
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(normalized)) {
|
|
139
|
+
throw new Error(`Invalid environment variable name: ${normalized || '(empty)'}`);
|
|
140
|
+
}
|
|
141
|
+
return normalized;
|
|
142
|
+
}
|
|
143
|
+
|
|
112
144
|
function upsertEnvValue(envFile, key, value) {
|
|
145
|
+
const safeKey = normalizeEnvKey(key);
|
|
146
|
+
const safeValue = String(value).replace(/[\r\n]/g, '');
|
|
113
147
|
const raw = readEnvFileRaw(envFile);
|
|
114
148
|
const lines = raw ? raw.split('\n') : [];
|
|
115
149
|
let replaced = false;
|
|
116
150
|
|
|
117
151
|
for (let i = 0; i < lines.length; i++) {
|
|
118
|
-
if (lines[i].startsWith(`${
|
|
119
|
-
lines[i] = `${
|
|
152
|
+
if (lines[i].startsWith(`${safeKey}=`)) {
|
|
153
|
+
lines[i] = `${safeKey}=${safeValue}`;
|
|
120
154
|
replaced = true;
|
|
121
155
|
break;
|
|
122
156
|
}
|
|
123
157
|
}
|
|
124
158
|
|
|
125
159
|
if (!replaced) {
|
|
126
|
-
lines.push(`${
|
|
160
|
+
lines.push(`${safeKey}=${safeValue}`);
|
|
127
161
|
}
|
|
128
162
|
|
|
129
163
|
const output = lines.filter((line, idx, arr) => idx !== arr.length - 1 || line !== '').join('\n') + '\n';
|
|
130
|
-
|
|
131
|
-
|
|
164
|
+
writeEnvFileAtomic(envFile, output);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function removeEnvValue(envFile, key) {
|
|
168
|
+
const safeKey = normalizeEnvKey(key);
|
|
169
|
+
const raw = readEnvFileRaw(envFile);
|
|
170
|
+
if (!raw) return false;
|
|
171
|
+
const lines = raw.split('\n').filter((line) => !line.startsWith(`${safeKey}=`));
|
|
172
|
+
const output = lines.filter((line, idx, arr) => idx !== arr.length - 1 || line !== '').join('\n') + '\n';
|
|
173
|
+
writeEnvFileAtomic(envFile, output);
|
|
174
|
+
return true;
|
|
132
175
|
}
|
|
133
176
|
|
|
134
177
|
function generateSecret(bytes = 32) {
|
|
@@ -247,6 +290,7 @@ module.exports = {
|
|
|
247
290
|
ensureSecureRuntimeEnv,
|
|
248
291
|
getDefaultVmBaseImageUrl,
|
|
249
292
|
migrateLegacyRuntime,
|
|
293
|
+
removeEnvValue,
|
|
250
294
|
upsertEnvValue,
|
|
251
295
|
readEnvFileRaw,
|
|
252
296
|
};
|
package/server/db/database.js
CHANGED
|
@@ -1311,8 +1311,8 @@ function getMainAgentId(userId) {
|
|
|
1311
1311
|
id, user_id, slug, display_name, description, responsibilities, instructions,
|
|
1312
1312
|
is_default, can_delegate, can_be_delegated_to, delegate_targets_json
|
|
1313
1313
|
)
|
|
1314
|
-
VALUES (?, ?, 'main', 'Main', 'Default personal
|
|
1315
|
-
'Handle general requests and delegate to specialist agents only when there is a clear match.',
|
|
1314
|
+
VALUES (?, ?, 'main', 'Main', 'Default personal AI contact and fallback agent.',
|
|
1315
|
+
'Handle general requests like a proactive favorite contact and delegate to specialist agents only when there is a clear match.',
|
|
1316
1316
|
'', 1, 1, 0, '[]'
|
|
1317
1317
|
)`
|
|
1318
1318
|
).run(id, userId);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "neoagent-guest-agent-cli",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Guest runtime for isolated NeoAgent CLI services",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"express": "^4.21.2",
|
|
11
|
+
"proper-lockfile": "^4.1.2"
|
|
12
|
+
}
|
|
13
|
+
}
|
package/server/guest_agent.js
CHANGED
|
@@ -4,7 +4,6 @@ const express = require('express');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { CLIExecutor } = require('./services/cli/executor');
|
|
8
7
|
const { RUNTIME_HOME, DATA_DIR } = require('../runtime/paths');
|
|
9
8
|
|
|
10
9
|
const PORT = Number(process.env.NEOAGENT_GUEST_AGENT_PORT || 8421);
|
|
@@ -21,8 +20,9 @@ function resolveGuestToken() {
|
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
const AUTH_TOKEN = resolveGuestToken();
|
|
24
|
-
const
|
|
25
|
-
|
|
23
|
+
const RAW_GUEST_PROFILE = String(process.env.NEOAGENT_GUEST_PROFILE || 'browser_cli').trim();
|
|
24
|
+
const GUEST_PROFILE = ['android', 'browser', 'cli', 'browser_cli'].includes(RAW_GUEST_PROFILE)
|
|
25
|
+
? RAW_GUEST_PROFILE
|
|
26
26
|
: 'browser_cli';
|
|
27
27
|
const FILE_ROOT = path.join(RUNTIME_HOME, 'guest-agent-files');
|
|
28
28
|
const MAX_APK_STREAM_BYTES = Number(process.env.NEOAGENT_GUEST_MAX_APK_STREAM_BYTES || 512 * 1024 * 1024);
|
|
@@ -32,8 +32,10 @@ fs.mkdirSync(FILE_ROOT, { recursive: true });
|
|
|
32
32
|
const app = express();
|
|
33
33
|
app.use(express.json({ limit: '100mb' }));
|
|
34
34
|
|
|
35
|
-
const cliExecutor =
|
|
36
|
-
|
|
35
|
+
const cliExecutor = ['cli', 'browser_cli', 'android'].includes(GUEST_PROFILE)
|
|
36
|
+
? new (require('./services/cli/executor').CLIExecutor)()
|
|
37
|
+
: null;
|
|
38
|
+
const browserController = ['browser', 'browser_cli'].includes(GUEST_PROFILE)
|
|
37
39
|
? new (require('./services/browser/controller').BrowserController)({ runtimeBackend: 'vm' })
|
|
38
40
|
: null;
|
|
39
41
|
const androidController = GUEST_PROFILE === 'android'
|
|
@@ -105,6 +107,26 @@ async function handle(res, work) {
|
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
async function handleRequest(req, res, work) {
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
const abort = () => {
|
|
113
|
+
if (!res.writableEnded) controller.abort('Guest runtime request disconnected.');
|
|
114
|
+
};
|
|
115
|
+
req.once('aborted', abort);
|
|
116
|
+
res.once('close', abort);
|
|
117
|
+
try {
|
|
118
|
+
const result = await work(controller.signal);
|
|
119
|
+
if (!res.headersSent && !res.writableEnded) res.json(result);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (!res.headersSent && !res.writableEnded) {
|
|
122
|
+
res.status(controller.signal.aborted ? 499 : 500).json({ error: sanitizeError(err) });
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
req.removeListener('aborted', abort);
|
|
126
|
+
res.removeListener('close', abort);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
108
130
|
app.use(requireToken);
|
|
109
131
|
|
|
110
132
|
app.get('/health', (_req, res) => {
|
|
@@ -118,36 +140,40 @@ app.get('/health', (_req, res) => {
|
|
|
118
140
|
});
|
|
119
141
|
|
|
120
142
|
app.post('/exec', async (req, res) => {
|
|
121
|
-
await
|
|
143
|
+
await handleRequest(req, res, async (signal) => {
|
|
144
|
+
const executor = requireCapability(cliExecutor, 'cli');
|
|
122
145
|
const command = String(req.body?.command || '').trim();
|
|
123
146
|
if (!command) {
|
|
124
147
|
return { error: 'command is required' };
|
|
125
148
|
}
|
|
126
149
|
if (req.body?.pty) {
|
|
127
|
-
return
|
|
150
|
+
return executor.executeInteractive(command, req.body?.inputs || [], {
|
|
128
151
|
cwd: req.body?.cwd,
|
|
129
152
|
timeout: req.body?.timeout,
|
|
153
|
+
signal,
|
|
130
154
|
});
|
|
131
155
|
}
|
|
132
|
-
return
|
|
156
|
+
return executor.execute(command, {
|
|
133
157
|
cwd: req.body?.cwd,
|
|
134
158
|
timeout: req.body?.timeout,
|
|
135
159
|
stdinInput: req.body?.stdin_input,
|
|
160
|
+
signal,
|
|
136
161
|
});
|
|
137
162
|
});
|
|
138
163
|
});
|
|
139
164
|
|
|
140
165
|
app.post('/exec/kill', async (req, res) => {
|
|
141
166
|
await handle(res, async () => {
|
|
167
|
+
const executor = requireCapability(cliExecutor, 'cli');
|
|
142
168
|
const pid = Number(req.body?.pid);
|
|
143
169
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
144
170
|
return { error: 'pid is required' };
|
|
145
171
|
}
|
|
146
172
|
const reason = String(req.body?.reason || 'aborted').trim();
|
|
147
|
-
if (typeof
|
|
173
|
+
if (typeof executor.isManaged === 'function' && !executor.isManaged(pid)) {
|
|
148
174
|
return { error: 'pid not managed' };
|
|
149
175
|
}
|
|
150
|
-
const killed =
|
|
176
|
+
const killed = executor.kill(pid, reason || 'aborted');
|
|
151
177
|
return { success: killed, pid };
|
|
152
178
|
});
|
|
153
179
|
});
|
|
@@ -185,6 +211,22 @@ app.get('/browser/status', async (_req, res) => {
|
|
|
185
211
|
};
|
|
186
212
|
});
|
|
187
213
|
});
|
|
214
|
+
|
|
215
|
+
app.use('/browser', (req, res, next) => {
|
|
216
|
+
if (!browserController?.hasProtectedCredentialFill?.()) return next();
|
|
217
|
+
const allowed = new Set([
|
|
218
|
+
'/status',
|
|
219
|
+
'/credential-submit',
|
|
220
|
+
'/credential-cancel',
|
|
221
|
+
'/close',
|
|
222
|
+
]);
|
|
223
|
+
if (allowed.has(req.path)) return next();
|
|
224
|
+
return res.status(423).json({
|
|
225
|
+
error: 'Browser control is paused while a protected credential fill is active. Submit or cancel it first.',
|
|
226
|
+
code: 'PROTECTED_CREDENTIAL_FILL_ACTIVE',
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
188
230
|
function requireCapability(controller, name) {
|
|
189
231
|
if (!controller) {
|
|
190
232
|
throw new Error(`${name} runtime is unavailable in this guest profile.`);
|
|
@@ -192,43 +234,46 @@ function requireCapability(controller, name) {
|
|
|
192
234
|
return controller;
|
|
193
235
|
}
|
|
194
236
|
|
|
195
|
-
app.post('/browser/launch', async (req, res) =>
|
|
196
|
-
app.post('/browser/navigate', async (req, res) =>
|
|
197
|
-
app.post('/browser/screenshot', async (req, res) =>
|
|
198
|
-
app.post('/browser/screenshot-jpeg', async (req, res) =>
|
|
199
|
-
const jpeg = await requireCapability(browserController, 'browser').screenshotJpeg(req.body?.quality, req.body || {});
|
|
237
|
+
app.post('/browser/launch', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').launch({ ...(req.body || {}), signal })));
|
|
238
|
+
app.post('/browser/navigate', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').navigate(req.body?.url, { ...(req.body || {}), signal })));
|
|
239
|
+
app.post('/browser/screenshot', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').screenshot({ ...(req.body || {}), signal })));
|
|
240
|
+
app.post('/browser/screenshot-jpeg', async (req, res) => handleRequest(req, res, async (signal) => {
|
|
241
|
+
const jpeg = await requireCapability(browserController, 'browser').screenshotJpeg(req.body?.quality, { ...(req.body || {}), signal });
|
|
200
242
|
return {
|
|
201
243
|
contentType: 'image/jpeg',
|
|
202
244
|
contentBase64: Buffer.from(jpeg).toString('base64'),
|
|
203
245
|
};
|
|
204
246
|
}));
|
|
205
|
-
app.post('/browser/click', async (req, res) =>
|
|
206
|
-
app.post('/browser/click-point', async (req, res) =>
|
|
207
|
-
app.post('/browser/fill', async (req, res) =>
|
|
208
|
-
app.post('/browser/
|
|
209
|
-
app.post('/browser/
|
|
210
|
-
app.post('/browser/
|
|
211
|
-
app.post('/browser/
|
|
212
|
-
app.post('/browser/
|
|
247
|
+
app.post('/browser/click', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').click(req.body?.selector, req.body?.text, req.body?.screenshot !== false, { signal })));
|
|
248
|
+
app.post('/browser/click-point', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').clickPoint(req.body?.x, req.body?.y, req.body?.screenshot !== false, { signal })));
|
|
249
|
+
app.post('/browser/fill', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').type(req.body?.selector, String(req.body?.value ?? req.body?.text ?? ''), { ...(req.body || {}), signal })));
|
|
250
|
+
app.post('/browser/credential-fill', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').fillCredential(req.body || {}, { signal })));
|
|
251
|
+
app.post('/browser/credential-submit', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').submitProtectedCredential(req.body?.protectedFillId, { signal })));
|
|
252
|
+
app.post('/browser/credential-cancel', async (req, res) => handleRequest(req, res, () => requireCapability(browserController, 'browser').cancelProtectedCredential(req.body?.protectedFillId)));
|
|
253
|
+
app.post('/browser/type-text', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').typeText(String(req.body?.text || ''), { ...(req.body || {}), signal })));
|
|
254
|
+
app.post('/browser/press-key', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').pressKey(req.body?.key, req.body?.screenshot !== false, { signal })));
|
|
255
|
+
app.post('/browser/scroll', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').scroll(req.body?.deltaX ?? 0, req.body?.deltaY ?? 0, req.body?.screenshot !== false, { signal })));
|
|
256
|
+
app.post('/browser/extract', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').extractContent({ ...(req.body || {}), signal })));
|
|
257
|
+
app.post('/browser/execute', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').executeJS(req.body?.code, { signal })));
|
|
213
258
|
app.post('/browser/close', async (_req, res) => handle(res, () => requireCapability(browserController, 'browser').closeBrowser().then(() => ({ success: true }))));
|
|
214
259
|
|
|
215
260
|
app.get('/android/status', async (_req, res) => handle(res, () => requireCapability(androidController, 'android').getStatus()));
|
|
216
|
-
app.post('/android/start', async (req, res) =>
|
|
217
|
-
app.post('/android/stop', async (
|
|
218
|
-
app.get('/android/devices', async (
|
|
219
|
-
app.post('/android/screenshot', async (req, res) =>
|
|
220
|
-
app.post('/android/observe', async (req, res) =>
|
|
221
|
-
app.post('/android/ui-dump', async (req, res) =>
|
|
222
|
-
app.get('/android/apps', async (req, res) =>
|
|
223
|
-
app.post('/android/open-app', async (req, res) =>
|
|
224
|
-
app.post('/android/open-intent', async (req, res) =>
|
|
225
|
-
app.post('/android/tap', async (req, res) =>
|
|
226
|
-
app.post('/android/long-press', async (req, res) =>
|
|
227
|
-
app.post('/android/type', async (req, res) =>
|
|
228
|
-
app.post('/android/swipe', async (req, res) =>
|
|
229
|
-
app.post('/android/press-key', async (req, res) =>
|
|
230
|
-
app.post('/android/wait-for', async (req, res) =>
|
|
231
|
-
app.post('/android/shell', async (req, res) =>
|
|
261
|
+
app.post('/android/start', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').requestStartEmulator({ ...(req.body || {}), signal })));
|
|
262
|
+
app.post('/android/stop', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').stopEmulator({ signal })));
|
|
263
|
+
app.get('/android/devices', async (req, res) => handleRequest(req, res, async (signal) => ({ devices: await requireCapability(androidController, 'android').listDevices({ signal }) })));
|
|
264
|
+
app.post('/android/screenshot', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').screenshot({ ...(req.body || {}), signal })));
|
|
265
|
+
app.post('/android/observe', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').observe({ ...(req.body || {}), signal })));
|
|
266
|
+
app.post('/android/ui-dump', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').dumpUi({ ...(req.body || {}), signal })));
|
|
267
|
+
app.get('/android/apps', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').listApps({ includeSystem: req.query.includeSystem === 'true', signal })));
|
|
268
|
+
app.post('/android/open-app', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').openApp({ ...(req.body || {}), signal })));
|
|
269
|
+
app.post('/android/open-intent', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').openIntent({ ...(req.body || {}), signal })));
|
|
270
|
+
app.post('/android/tap', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').tap({ ...(req.body || {}), signal })));
|
|
271
|
+
app.post('/android/long-press', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').longPress({ ...(req.body || {}), signal })));
|
|
272
|
+
app.post('/android/type', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').type({ ...(req.body || {}), signal })));
|
|
273
|
+
app.post('/android/swipe', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').swipe({ ...(req.body || {}), signal })));
|
|
274
|
+
app.post('/android/press-key', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').pressKey({ ...(req.body || {}), signal })));
|
|
275
|
+
app.post('/android/wait-for', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').waitFor({ ...(req.body || {}), signal })));
|
|
276
|
+
app.post('/android/shell', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').shell({ ...(req.body || {}), signal })));
|
|
232
277
|
app.post('/android/install-apk', async (req, res) => {
|
|
233
278
|
await handle(res, async () => {
|
|
234
279
|
requireCapability(androidController, 'android');
|
|
@@ -325,7 +370,7 @@ async function shutdown() {
|
|
|
325
370
|
} catch (err) {
|
|
326
371
|
console.warn('[GuestAgent] Failed to close android controller:', err?.message);
|
|
327
372
|
}
|
|
328
|
-
cliExecutor
|
|
373
|
+
cliExecutor?.killAll?.('shutdown');
|
|
329
374
|
server.close(() => process.exit(0));
|
|
330
375
|
}
|
|
331
376
|
|
|
@@ -145,6 +145,28 @@ function createSessionMiddleware({ secureCookies, trustProxy }) {
|
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
function attachRequestSignal(req, res, next) {
|
|
149
|
+
const controller = new AbortController();
|
|
150
|
+
const abort = () => {
|
|
151
|
+
if (!res.writableEnded && !controller.signal.aborted) {
|
|
152
|
+
const error = new Error('HTTP client disconnected.');
|
|
153
|
+
error.name = 'AbortError';
|
|
154
|
+
error.code = 'ABORT_ERR';
|
|
155
|
+
controller.abort(error);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const cleanup = () => {
|
|
159
|
+
req.removeListener('aborted', abort);
|
|
160
|
+
res.removeListener('close', abort);
|
|
161
|
+
res.removeListener('finish', cleanup);
|
|
162
|
+
};
|
|
163
|
+
req.signal = controller.signal;
|
|
164
|
+
req.once('aborted', abort);
|
|
165
|
+
res.once('close', abort);
|
|
166
|
+
res.once('finish', cleanup);
|
|
167
|
+
next();
|
|
168
|
+
}
|
|
169
|
+
|
|
148
170
|
function applyHttpMiddleware(app, { secureCookies, trustProxy, sessionMiddleware, validateOrigin }) {
|
|
149
171
|
const jsonBody = require('express').json({
|
|
150
172
|
limit: '10mb',
|
|
@@ -179,6 +201,7 @@ function applyHttpMiddleware(app, { secureCookies, trustProxy, sessionMiddleware
|
|
|
179
201
|
}
|
|
180
202
|
|
|
181
203
|
app.use(helmet(buildHelmetOptions({ secureCookies })));
|
|
204
|
+
app.use(attachRequestSignal);
|
|
182
205
|
app.use(
|
|
183
206
|
cors((req, callback) => {
|
|
184
207
|
const requestPath = `${req.originalUrl || req.url || req.path || ''}`.split('?')[0];
|
|
@@ -255,6 +278,7 @@ function applyHttpMiddleware(app, { secureCookies, trustProxy, sessionMiddleware
|
|
|
255
278
|
}
|
|
256
279
|
|
|
257
280
|
module.exports = {
|
|
281
|
+
attachRequestSignal,
|
|
258
282
|
applyHttpMiddleware,
|
|
259
283
|
createSessionMiddleware
|
|
260
284
|
};
|