neoagent 3.2.1-beta.0 → 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/docs/agent-run-lifecycle.md +10 -0
- 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 +145 -13
- package/package.json +30 -15
- package/runtime/paths.js +49 -5
- package/server/db/database.js +4 -4
- 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/agents.js +35 -2
- 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 +496 -166
- 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 +620 -340
- package/server/services/ai/loop/lifecycle.js +108 -0
- 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 +48 -56
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loop/tool_dispatch.js +28 -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 +4 -4
- 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 +6 -5
- package/server/services/ai/providers/grokOauth.js +19 -27
- package/server/services/ai/providers/nvidia.js +12 -7
- package/server/services/ai/providers/ollama.js +114 -86
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +41 -7
- package/server/services/ai/providers/openaiCodex.js +13 -4
- package/server/services/ai/providers/openrouter.js +31 -9
- 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) {
|
|
@@ -633,6 +654,111 @@ function migrateBilling(db) {
|
|
|
633
654
|
`);
|
|
634
655
|
}
|
|
635
656
|
|
|
657
|
+
function migrateAgentRunLifecycle(db) {
|
|
658
|
+
db.exec(`
|
|
659
|
+
CREATE TABLE IF NOT EXISTS agent_run_controls (
|
|
660
|
+
run_id TEXT PRIMARY KEY,
|
|
661
|
+
user_id INTEGER NOT NULL,
|
|
662
|
+
action TEXT NOT NULL CHECK(action IN ('pause', 'stop', 'interrupt')),
|
|
663
|
+
reason TEXT DEFAULT '',
|
|
664
|
+
requested_at TEXT DEFAULT (datetime('now')),
|
|
665
|
+
consumed_at TEXT,
|
|
666
|
+
FOREIGN KEY (run_id) REFERENCES agent_runs(id) ON DELETE CASCADE,
|
|
667
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
CREATE TABLE IF NOT EXISTS agent_run_checkpoints (
|
|
671
|
+
run_id TEXT PRIMARY KEY,
|
|
672
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
673
|
+
phase TEXT NOT NULL,
|
|
674
|
+
state_json TEXT NOT NULL DEFAULT '{}',
|
|
675
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
676
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
677
|
+
FOREIGN KEY (run_id) REFERENCES agent_runs(id) ON DELETE CASCADE
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
CREATE INDEX IF NOT EXISTS idx_agent_run_controls_pending
|
|
681
|
+
ON agent_run_controls(user_id, consumed_at, requested_at);
|
|
682
|
+
`);
|
|
683
|
+
}
|
|
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
|
+
|
|
636
762
|
function runSchemaMigrations(db) {
|
|
637
763
|
removeRetiredCaptureData(db);
|
|
638
764
|
migrateMemoryEmbeddingIndex(db);
|
|
@@ -647,6 +773,9 @@ function runSchemaMigrations(db) {
|
|
|
647
773
|
migrateApprovalPersistence(db);
|
|
648
774
|
migrateToolPoliciesAllowAlways(db);
|
|
649
775
|
migrateBilling(db);
|
|
776
|
+
migrateAgentRunLifecycle(db);
|
|
777
|
+
migrateMessagingInboundJobs(db);
|
|
778
|
+
migrateCredentialBroker(db);
|
|
650
779
|
}
|
|
651
780
|
|
|
652
781
|
module.exports = {
|
|
@@ -664,5 +793,8 @@ module.exports = {
|
|
|
664
793
|
migrateApprovalPersistence,
|
|
665
794
|
migrateToolPoliciesAllowAlways,
|
|
666
795
|
migrateBilling,
|
|
796
|
+
migrateAgentRunLifecycle,
|
|
797
|
+
migrateMessagingInboundJobs,
|
|
798
|
+
migrateCredentialBroker,
|
|
667
799
|
runSchemaMigrations,
|
|
668
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
|
@@ -1021,7 +1021,7 @@ function interruptStaleAgentRuns(reason = STALE_RUN_INTERRUPTED_ERROR) {
|
|
|
1021
1021
|
const staleRunIds = db.prepare(
|
|
1022
1022
|
`SELECT id
|
|
1023
1023
|
FROM agent_runs
|
|
1024
|
-
WHERE status
|
|
1024
|
+
WHERE status IN ('running', 'pausing', 'paused', 'resuming')`
|
|
1025
1025
|
).all().map((row) => row.id);
|
|
1026
1026
|
const runsResult = db.prepare(
|
|
1027
1027
|
`UPDATE agent_runs
|
|
@@ -1029,7 +1029,7 @@ function interruptStaleAgentRuns(reason = STALE_RUN_INTERRUPTED_ERROR) {
|
|
|
1029
1029
|
error = COALESCE(NULLIF(error, ''), ?),
|
|
1030
1030
|
updated_at = datetime('now'),
|
|
1031
1031
|
completed_at = COALESCE(completed_at, datetime('now'))
|
|
1032
|
-
WHERE status
|
|
1032
|
+
WHERE status IN ('running', 'pausing', 'paused', 'resuming')`
|
|
1033
1033
|
).run(normalizedReason);
|
|
1034
1034
|
|
|
1035
1035
|
db.prepare(
|
|
@@ -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
|
|