neoagent 2.3.1-beta.99 → 2.4.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/.env.example +6 -3
- package/LICENSE +619 -21
- package/README.md +1 -1
- package/extensions/chrome-browser/background.mjs +19 -7
- package/extensions/chrome-browser/icons/icon128.png +0 -0
- package/extensions/chrome-browser/icons/icon16.png +0 -0
- package/extensions/chrome-browser/icons/icon48.png +0 -0
- package/extensions/chrome-browser/icons/logo.svg +12 -0
- package/extensions/chrome-browser/manifest.json +13 -2
- package/extensions/chrome-browser/popup.css +5 -0
- package/extensions/chrome-browser/popup.html +7 -5
- package/extensions/chrome-browser/popup.js +16 -7
- package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +391 -0
- package/flutter_app/lib/features/onboarding/onboarding_shell.dart +6 -0
- package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +1 -1
- package/flutter_app/lib/main.dart +2 -0
- package/flutter_app/lib/main_controller.dart +156 -3
- package/flutter_app/lib/main_devices.dart +485 -119
- package/flutter_app/lib/main_integrations.dart +21 -2
- package/flutter_app/lib/main_models.dart +60 -0
- package/flutter_app/lib/main_settings.dart +289 -30
- package/flutter_app/lib/main_theme.dart +31 -2
- package/flutter_app/lib/src/backend_client.dart +89 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +144 -0
- package/flutter_app/lib/src/desktop_companion_io.dart +145 -4
- package/flutter_app/lib/src/desktop_native_bridge.dart +13 -0
- package/flutter_app/lib/src/stream_renderer.dart +286 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +67 -2
- package/flutter_app/macos/Runner/DebugProfile.entitlements +4 -0
- package/flutter_app/macos/Runner/Release.entitlements +4 -0
- package/flutter_app/pubspec.lock +5 -5
- package/lib/manager.js +164 -2
- package/package.json +2 -2
- package/server/db/database.js +85 -0
- package/server/guest_agent.js +19 -1
- package/server/http/routes.js +191 -0
- package/server/http/socket.js +1 -1
- package/server/index.js +4 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +971 -1066
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/shaders/ink_sparkle.frag +1 -1
- package/server/public/assets/shaders/stretch_effect.frag +1 -1
- package/server/public/canvaskit/canvaskit.js +2 -2
- package/server/public/canvaskit/canvaskit.js.symbols +11796 -11733
- package/server/public/canvaskit/canvaskit.wasm +0 -0
- package/server/public/canvaskit/chromium/canvaskit.js +2 -2
- package/server/public/canvaskit/chromium/canvaskit.js.symbols +10706 -10643
- package/server/public/canvaskit/chromium/canvaskit.wasm +0 -0
- package/server/public/canvaskit/experimental_webparagraph/canvaskit.js +171 -0
- package/server/public/canvaskit/experimental_webparagraph/canvaskit.js.symbols +9134 -0
- package/server/public/canvaskit/experimental_webparagraph/canvaskit.wasm +0 -0
- package/server/public/canvaskit/skwasm.js +14 -14
- package/server/public/canvaskit/skwasm.js.symbols +12787 -12676
- package/server/public/canvaskit/skwasm.wasm +0 -0
- package/server/public/canvaskit/skwasm_heavy.js +14 -14
- package/server/public/canvaskit/skwasm_heavy.js.symbols +14400 -14286
- package/server/public/canvaskit/skwasm_heavy.wasm +0 -0
- package/server/public/canvaskit/wimp.js +94 -95
- package/server/public/canvaskit/wimp.js.symbols +11325 -11177
- package/server/public/canvaskit/wimp.wasm +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +86174 -83144
- package/server/routes/browser.js +14 -0
- package/server/routes/browser_extension.js +21 -4
- package/server/routes/desktop.js +10 -0
- package/server/routes/integrations.js +2 -2
- package/server/routes/memory.js +73 -0
- package/server/routes/settings.js +4 -0
- package/server/routes/stream.js +187 -0
- package/server/services/ai/engine.js +65 -26
- package/server/services/ai/models.js +21 -0
- package/server/services/ai/preModelCompaction.js +191 -0
- package/server/services/ai/providers/claudeCode.js +273 -0
- package/server/services/ai/providers/openaiCodex.js +212 -40
- package/server/services/ai/settings.js +12 -2
- package/server/services/ai/tools.js +40 -29
- package/server/services/android/controller.js +41 -2
- package/server/services/browser/controller.js +34 -0
- package/server/services/browser/extension/manifest.js +33 -0
- package/server/services/browser/extension/provider.js +12 -6
- package/server/services/browser/extension/registry.js +188 -18
- package/server/services/desktop/gateway.js +28 -3
- package/server/services/desktop/protocol.js +34 -0
- package/server/services/desktop/provider.js +25 -0
- package/server/services/desktop/registry.js +92 -10
- package/server/services/integrations/google/provider.js +78 -0
- package/server/services/integrations/manager.js +29 -13
- package/server/services/manager.js +44 -2
- package/server/services/memory/ingestion.js +486 -0
- package/server/services/memory/manager.js +422 -0
- package/server/services/memory/openhuman_uplift.test.js +98 -0
- package/server/services/runtime/backends/local-vm.js +6 -0
- package/server/services/runtime/docker-vm-manager.js +26 -3
- package/server/services/runtime/manager.js +36 -5
- package/server/services/runtime/settings.js +17 -0
- package/server/services/streaming/android-stream.js +298 -0
- package/server/services/streaming/browser-stream.js +87 -0
- package/server/services/streaming/stream-hub.js +231 -0
- package/server/services/websocket.js +73 -0
- package/server/services/widgets/focus_widget.js +45 -4
package/server/db/database.js
CHANGED
|
@@ -614,6 +614,78 @@ db.exec(`
|
|
|
614
614
|
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
|
|
615
615
|
);
|
|
616
616
|
|
|
617
|
+
CREATE TABLE IF NOT EXISTS memory_ingestion_jobs (
|
|
618
|
+
id TEXT PRIMARY KEY,
|
|
619
|
+
user_id INTEGER NOT NULL,
|
|
620
|
+
agent_id TEXT,
|
|
621
|
+
source_type TEXT NOT NULL,
|
|
622
|
+
provider_key TEXT DEFAULT '',
|
|
623
|
+
connection_id INTEGER,
|
|
624
|
+
status TEXT DEFAULT 'pending',
|
|
625
|
+
freshness_policy_json TEXT DEFAULT '{}',
|
|
626
|
+
cursor_json TEXT DEFAULT '{}',
|
|
627
|
+
summary_json TEXT DEFAULT '{}',
|
|
628
|
+
metadata_json TEXT DEFAULT '{}',
|
|
629
|
+
document_count INTEGER DEFAULT 0,
|
|
630
|
+
error_text TEXT,
|
|
631
|
+
started_at TEXT DEFAULT (datetime('now')),
|
|
632
|
+
completed_at TEXT,
|
|
633
|
+
next_sync_at TEXT,
|
|
634
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
635
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
636
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
637
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL,
|
|
638
|
+
FOREIGN KEY (connection_id) REFERENCES integration_connections(id) ON DELETE SET NULL
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
CREATE TABLE IF NOT EXISTS memory_ingestion_documents (
|
|
642
|
+
id TEXT PRIMARY KEY,
|
|
643
|
+
user_id INTEGER NOT NULL,
|
|
644
|
+
agent_id TEXT,
|
|
645
|
+
source_type TEXT NOT NULL,
|
|
646
|
+
normalized_type TEXT NOT NULL,
|
|
647
|
+
provider_key TEXT DEFAULT '',
|
|
648
|
+
connection_id INTEGER NOT NULL DEFAULT 0,
|
|
649
|
+
external_object_id TEXT NOT NULL,
|
|
650
|
+
source_account TEXT,
|
|
651
|
+
title TEXT,
|
|
652
|
+
content TEXT NOT NULL,
|
|
653
|
+
summary TEXT,
|
|
654
|
+
salience INTEGER DEFAULT 5,
|
|
655
|
+
source_timestamp TEXT,
|
|
656
|
+
metadata_json TEXT DEFAULT '{}',
|
|
657
|
+
payload_json TEXT DEFAULT '{}',
|
|
658
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
659
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
660
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
661
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL,
|
|
662
|
+
UNIQUE(user_id, agent_id, source_type, provider_key, connection_id, external_object_id)
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
CREATE TABLE IF NOT EXISTS materialized_knowledge_views (
|
|
666
|
+
id TEXT PRIMARY KEY,
|
|
667
|
+
user_id INTEGER NOT NULL,
|
|
668
|
+
agent_id TEXT,
|
|
669
|
+
view_type TEXT NOT NULL,
|
|
670
|
+
subject_key TEXT NOT NULL,
|
|
671
|
+
title TEXT NOT NULL,
|
|
672
|
+
summary TEXT,
|
|
673
|
+
markdown_text TEXT,
|
|
674
|
+
source_memory_ids_json TEXT DEFAULT '[]',
|
|
675
|
+
source_document_ids_json TEXT DEFAULT '[]',
|
|
676
|
+
metadata_json TEXT DEFAULT '{}',
|
|
677
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
678
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
679
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
680
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL,
|
|
681
|
+
UNIQUE(user_id, agent_id, view_type, subject_key)
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
CREATE INDEX IF NOT EXISTS idx_memory_ingestion_jobs_user ON memory_ingestion_jobs(user_id, agent_id, source_type, updated_at DESC);
|
|
685
|
+
CREATE INDEX IF NOT EXISTS idx_memory_ingestion_documents_user ON memory_ingestion_documents(user_id, agent_id, source_type, updated_at DESC);
|
|
686
|
+
CREATE INDEX IF NOT EXISTS idx_memory_ingestion_documents_provider ON memory_ingestion_documents(user_id, agent_id, provider_key, connection_id, updated_at DESC);
|
|
687
|
+
CREATE INDEX IF NOT EXISTS idx_materialized_knowledge_views_user ON materialized_knowledge_views(user_id, agent_id, view_type, updated_at DESC);
|
|
688
|
+
|
|
617
689
|
CREATE TABLE IF NOT EXISTS agent_run_events (
|
|
618
690
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
619
691
|
run_id TEXT NOT NULL,
|
|
@@ -1698,6 +1770,19 @@ function migrateUsersDisplayName() {
|
|
|
1698
1770
|
}
|
|
1699
1771
|
migrateUsersDisplayName();
|
|
1700
1772
|
|
|
1773
|
+
function migrateIngestionDocumentsConnectionId() {
|
|
1774
|
+
// connection_id was initially nullable; NULL breaks the UNIQUE dedup constraint.
|
|
1775
|
+
// Coerce any existing NULLs to 0 so the unique index works as intended.
|
|
1776
|
+
try {
|
|
1777
|
+
db.prepare(
|
|
1778
|
+
`UPDATE memory_ingestion_documents SET connection_id = 0 WHERE connection_id IS NULL`
|
|
1779
|
+
).run();
|
|
1780
|
+
} catch {
|
|
1781
|
+
// Table may not exist yet on first boot; the schema DDL handles that case.
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
migrateIngestionDocumentsConnectionId();
|
|
1785
|
+
|
|
1701
1786
|
try {
|
|
1702
1787
|
db.exec(`
|
|
1703
1788
|
INSERT OR REPLACE INTO conversation_history_fts(rowid, content, role, user_id, agent_id, agent_run_id)
|
package/server/guest_agent.js
CHANGED
|
@@ -62,7 +62,18 @@ function isInsideAllowedRoots(targetPath) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function requireToken(req, res, next) {
|
|
65
|
-
|
|
65
|
+
if (!AUTH_TOKEN) {
|
|
66
|
+
// Token not configured in this environment — allow but unauthenticated.
|
|
67
|
+
// Pass NEOAGENT_VM_GUEST_TOKEN to the container to enforce auth.
|
|
68
|
+
return next();
|
|
69
|
+
}
|
|
70
|
+
const header = String(req.headers?.authorization || '').trim();
|
|
71
|
+
const prefix = 'Bearer ';
|
|
72
|
+
const provided = header.startsWith(prefix) ? header.slice(prefix.length).trim() : '';
|
|
73
|
+
if (!provided || provided !== AUTH_TOKEN) {
|
|
74
|
+
return res.status(401).json({ error: 'Unauthorized.' });
|
|
75
|
+
}
|
|
76
|
+
return next();
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
function sanitizeError(err) {
|
|
@@ -184,6 +195,13 @@ function requireCapability(controller, name) {
|
|
|
184
195
|
app.post('/browser/launch', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').launch(req.body || {})));
|
|
185
196
|
app.post('/browser/navigate', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').navigate(req.body?.url, req.body || {})));
|
|
186
197
|
app.post('/browser/screenshot', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').screenshot(req.body || {})));
|
|
198
|
+
app.post('/browser/screenshot-jpeg', async (req, res) => handle(res, async () => {
|
|
199
|
+
const jpeg = await requireCapability(browserController, 'browser').screenshotJpeg(req.body?.quality, req.body || {});
|
|
200
|
+
return {
|
|
201
|
+
contentType: 'image/jpeg',
|
|
202
|
+
contentBase64: Buffer.from(jpeg).toString('base64'),
|
|
203
|
+
};
|
|
204
|
+
}));
|
|
187
205
|
app.post('/browser/click', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').click(req.body?.selector, req.body?.text, req.body?.screenshot !== false)));
|
|
188
206
|
app.post('/browser/click-point', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').clickPoint(req.body?.x, req.body?.y, req.body?.screenshot !== false)));
|
|
189
207
|
app.post('/browser/fill', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').type(req.body?.selector, String(req.body?.value ?? req.body?.text ?? ''), req.body || {})));
|
package/server/http/routes.js
CHANGED
|
@@ -25,6 +25,7 @@ const routeRegistry = [
|
|
|
25
25
|
{ basePath: '/api/browser-extension', modulePath: '../routes/browser_extension' },
|
|
26
26
|
{ basePath: '/api/android', modulePath: '../routes/android' },
|
|
27
27
|
{ basePath: '/api/desktop', modulePath: '../routes/desktop' },
|
|
28
|
+
{ basePath: '/api/stream', modulePath: '../routes/stream' },
|
|
28
29
|
{ basePath: '/api/recordings', modulePath: '../routes/recordings' },
|
|
29
30
|
{ basePath: '/api/social-video', modulePath: '../routes/social_video' },
|
|
30
31
|
{ basePath: '/api/voice-assistant', modulePath: '../routes/voice_assistant' },
|
|
@@ -67,6 +68,196 @@ function registerApiRoutes(app) {
|
|
|
67
68
|
});
|
|
68
69
|
});
|
|
69
70
|
|
|
71
|
+
app.get('/api/system/health-check', requireAuth, async (req, res) => {
|
|
72
|
+
const userId = req.session?.userId;
|
|
73
|
+
const runtimeManager = req.app?.locals?.runtimeManager;
|
|
74
|
+
const desktopRegistry = req.app?.locals?.desktopCompanionRegistry;
|
|
75
|
+
const extensionRegistry = req.app?.locals?.browserExtensionRegistry;
|
|
76
|
+
const results = [];
|
|
77
|
+
|
|
78
|
+
// 1. Backend connectivity — trivially true if we got here.
|
|
79
|
+
results.push({ id: 'backend', label: 'Backend server', passed: true, detail: 'Reachable' });
|
|
80
|
+
|
|
81
|
+
// 2. Cloud VM runtime availability.
|
|
82
|
+
const runtimeValidation = getRuntimeValidation(runtimeManager);
|
|
83
|
+
const runtimeReady = Boolean(runtimeValidation?.ready);
|
|
84
|
+
results.push({
|
|
85
|
+
id: 'vm_runtime',
|
|
86
|
+
label: 'Cloud VM runtime',
|
|
87
|
+
passed: runtimeReady,
|
|
88
|
+
detail: runtimeReady ? 'Available' : String(runtimeValidation?.issues?.[0] || 'Not configured'),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// 3. Cloud VM CLI execution — actually run a command.
|
|
92
|
+
if (runtimeManager && typeof runtimeManager.executeCommand === 'function') {
|
|
93
|
+
try {
|
|
94
|
+
const cmdResult = await runtimeManager.executeCommand(userId, 'echo "health_check_ok"', { timeout: 15000 });
|
|
95
|
+
const exitOk = cmdResult?.exitCode === 0;
|
|
96
|
+
const outputOk = String(cmdResult?.stdout || '').includes('health_check_ok');
|
|
97
|
+
results.push({
|
|
98
|
+
id: 'vm_cli',
|
|
99
|
+
label: 'Cloud VM — command execution',
|
|
100
|
+
passed: exitOk && outputOk,
|
|
101
|
+
detail: exitOk && outputOk
|
|
102
|
+
? 'Commands running'
|
|
103
|
+
: `Exit ${cmdResult?.exitCode ?? '?'}: ${String(cmdResult?.stderr || cmdResult?.stdout || '').slice(0, 120)}`,
|
|
104
|
+
});
|
|
105
|
+
} catch (err) {
|
|
106
|
+
results.push({ id: 'vm_cli', label: 'Cloud VM — command execution', passed: false, detail: String(err?.message || err).slice(0, 120) });
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
results.push({ id: 'vm_cli', label: 'Cloud VM — command execution', passed: false, detail: 'VM runtime unavailable' });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 4. Desktop companion (macOS app / remote device) connectivity + permissions.
|
|
113
|
+
if (desktopRegistry) {
|
|
114
|
+
try {
|
|
115
|
+
const desktopStatus = desktopRegistry.getStatus(userId);
|
|
116
|
+
const connected = Boolean(desktopStatus?.connected);
|
|
117
|
+
results.push({
|
|
118
|
+
id: 'desktop_connected',
|
|
119
|
+
label: 'Desktop companion',
|
|
120
|
+
passed: connected,
|
|
121
|
+
detail: connected
|
|
122
|
+
? `${desktopStatus.onlineCount} device${desktopStatus.onlineCount !== 1 ? 's' : ''} connected`
|
|
123
|
+
: 'No device connected — open the desktop app',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (connected && Array.isArray(desktopStatus?.devices)) {
|
|
127
|
+
const onlineDevice = desktopStatus.devices.find((d) => d.online && !d.revokedAt);
|
|
128
|
+
const perms = onlineDevice?.permissions || {};
|
|
129
|
+
const screenOk = Boolean(perms.screenCapture || perms.screen_capture);
|
|
130
|
+
const inputOk = Boolean(perms.accessibility || perms.inputControl || perms.input_control);
|
|
131
|
+
results.push({
|
|
132
|
+
id: 'desktop_screen',
|
|
133
|
+
label: 'Desktop — screen capture',
|
|
134
|
+
passed: screenOk,
|
|
135
|
+
detail: screenOk ? 'Granted' : 'Not granted — open System Settings › Privacy › Screen Recording',
|
|
136
|
+
});
|
|
137
|
+
results.push({
|
|
138
|
+
id: 'desktop_input',
|
|
139
|
+
label: 'Desktop — input control',
|
|
140
|
+
passed: inputOk,
|
|
141
|
+
detail: inputOk ? 'Granted' : 'Not granted — open System Settings › Privacy › Accessibility',
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
results.push({ id: 'desktop_connected', label: 'Desktop companion', passed: false, detail: String(err?.message || err).slice(0, 120) });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 5. Chrome extension connectivity.
|
|
150
|
+
if (extensionRegistry) {
|
|
151
|
+
try {
|
|
152
|
+
const extStatus = extensionRegistry.getStatus(userId);
|
|
153
|
+
const extConnected = Boolean(extStatus?.connected);
|
|
154
|
+
results.push({
|
|
155
|
+
id: 'chrome_extension',
|
|
156
|
+
label: 'Chrome extension',
|
|
157
|
+
passed: extConnected,
|
|
158
|
+
detail: extConnected ? 'Connected' : 'Not connected — install the NeoAgent extension in Chrome',
|
|
159
|
+
});
|
|
160
|
+
} catch (err) {
|
|
161
|
+
results.push({ id: 'chrome_extension', label: 'Chrome extension', passed: false, detail: String(err?.message || err).slice(0, 120) });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const allPassed = results.every((r) => r.passed);
|
|
166
|
+
res.json({ passed: allPassed, results });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Targeted runtime self-tests — one check per endpoint so the UI can embed
|
|
170
|
+
// results inline next to the relevant settings control.
|
|
171
|
+
|
|
172
|
+
app.get('/api/system/test/cli', requireAuth, async (req, res) => {
|
|
173
|
+
const userId = req.session?.userId;
|
|
174
|
+
const runtimeManager = req.app?.locals?.runtimeManager;
|
|
175
|
+
if (!runtimeManager || typeof runtimeManager.executeCommand !== 'function') {
|
|
176
|
+
return res.json({ passed: false, backendUsed: 'vm', detail: 'Runtime not configured on this server.' });
|
|
177
|
+
}
|
|
178
|
+
// Note: executeCommand always routes through the VM backend regardless of
|
|
179
|
+
// the cli_backend setting — desktop CLI routing is not yet implemented.
|
|
180
|
+
try {
|
|
181
|
+
const result = await runtimeManager.executeCommand(userId, 'echo "cli_test_ok"', { timeout: 15000 });
|
|
182
|
+
const exitOk = result?.exitCode === 0;
|
|
183
|
+
const outputOk = String(result?.stdout || '').includes('cli_test_ok');
|
|
184
|
+
return res.json({
|
|
185
|
+
passed: exitOk && outputOk,
|
|
186
|
+
backendUsed: 'vm',
|
|
187
|
+
detail: exitOk && outputOk
|
|
188
|
+
? 'Command executed successfully'
|
|
189
|
+
: `Exit ${result?.exitCode ?? '?'}: ${String(result?.stderr || result?.stdout || '').slice(0, 120)}`,
|
|
190
|
+
});
|
|
191
|
+
} catch (err) {
|
|
192
|
+
return res.json({ passed: false, backendUsed: 'vm', detail: String(err?.message || err).slice(0, 120) });
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
app.get('/api/system/test/extension', requireAuth, (req, res) => {
|
|
197
|
+
const userId = req.session?.userId;
|
|
198
|
+
const extensionRegistry = req.app?.locals?.browserExtensionRegistry;
|
|
199
|
+
if (!extensionRegistry) {
|
|
200
|
+
return res.json({ passed: false, detail: 'Extension registry not available on this server.' });
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const status = extensionRegistry.getStatus(userId);
|
|
204
|
+
const connected = Boolean(status?.connected);
|
|
205
|
+
return res.json({
|
|
206
|
+
passed: connected,
|
|
207
|
+
detail: connected ? 'Extension is connected and live' : 'Extension is not connected',
|
|
208
|
+
tokenId: status?.activeTokenId || null,
|
|
209
|
+
meta: status?.connectedMeta || null,
|
|
210
|
+
});
|
|
211
|
+
} catch (err) {
|
|
212
|
+
return res.json({ passed: false, detail: String(err?.message || err).slice(0, 120) });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
app.get('/api/system/test/desktop', requireAuth, (req, res) => {
|
|
217
|
+
const userId = req.session?.userId;
|
|
218
|
+
const desktopRegistry = req.app?.locals?.desktopCompanionRegistry;
|
|
219
|
+
if (!desktopRegistry) {
|
|
220
|
+
return res.json({ passed: false, detail: 'Desktop registry not available on this server.' });
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
const status = desktopRegistry.getStatus(userId);
|
|
224
|
+
const connected = Boolean(status?.connected);
|
|
225
|
+
const devices = Array.isArray(status?.devices)
|
|
226
|
+
? status.devices.filter((d) => d.online && !d.revokedAt)
|
|
227
|
+
: [];
|
|
228
|
+
const selected = status?.selectedDeviceId || null;
|
|
229
|
+
const activeDevice = selected
|
|
230
|
+
? devices.find((d) => d.deviceId === selected)
|
|
231
|
+
: devices.length === 1 ? devices[0] : null;
|
|
232
|
+
const perms = activeDevice?.permissions || {};
|
|
233
|
+
const screenOk = Boolean(perms.screenCapture || perms.screen_capture);
|
|
234
|
+
const inputOk = Boolean(perms.accessibility || perms.inputControl || perms.input_control);
|
|
235
|
+
return res.json({
|
|
236
|
+
passed: connected,
|
|
237
|
+
connected,
|
|
238
|
+
onlineCount: devices.length,
|
|
239
|
+
selectedDeviceId: selected,
|
|
240
|
+
activeDevice: activeDevice ? {
|
|
241
|
+
deviceId: activeDevice.deviceId,
|
|
242
|
+
label: activeDevice.label || activeDevice.hostname || activeDevice.deviceId,
|
|
243
|
+
platform: activeDevice.platform || null,
|
|
244
|
+
paused: activeDevice.paused || false,
|
|
245
|
+
permissions: { screenCapture: screenOk, inputControl: inputOk },
|
|
246
|
+
} : null,
|
|
247
|
+
multipleOnline: devices.length > 1 && !activeDevice,
|
|
248
|
+
detail: !connected
|
|
249
|
+
? 'No device connected'
|
|
250
|
+
: devices.length > 1 && !activeDevice
|
|
251
|
+
? `${devices.length} devices online — select one in Desktop settings`
|
|
252
|
+
: activeDevice?.paused
|
|
253
|
+
? `${activeDevice.label || 'Device'} is paused`
|
|
254
|
+
: `${activeDevice?.label || 'Device'} connected`,
|
|
255
|
+
});
|
|
256
|
+
} catch (err) {
|
|
257
|
+
return res.json({ passed: false, detail: String(err?.message || err).slice(0, 120) });
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
70
261
|
app.get('/api/version', requireAuth, (req, res) => {
|
|
71
262
|
res.json(getVersionInfo());
|
|
72
263
|
});
|
package/server/http/socket.js
CHANGED
|
@@ -8,7 +8,7 @@ function createSocketServer(httpServer, { validateOrigin }) {
|
|
|
8
8
|
pingInterval: Number(process.env.NEOAGENT_SOCKET_PING_INTERVAL_MS || 25000),
|
|
9
9
|
pingTimeout: Number(process.env.NEOAGENT_SOCKET_PING_TIMEOUT_MS || 20000),
|
|
10
10
|
connectTimeout: Number(process.env.NEOAGENT_SOCKET_CONNECT_TIMEOUT_MS || 10000),
|
|
11
|
-
maxHttpBufferSize: Number(process.env.NEOAGENT_SOCKET_MAX_HTTP_BUFFER_BYTES ||
|
|
11
|
+
maxHttpBufferSize: Number(process.env.NEOAGENT_SOCKET_MAX_HTTP_BUFFER_BYTES || 8 * 1024 * 1024),
|
|
12
12
|
cors: {
|
|
13
13
|
origin(origin, callback) {
|
|
14
14
|
return validateOrigin(origin, callback, { allowMissingOrigin: true });
|
package/server/index.js
CHANGED
|
@@ -34,6 +34,7 @@ const { startServices, stopServices } = require('./services/manager');
|
|
|
34
34
|
const { bindBrowserExtensionGateway } = require('./services/browser/extension/gateway');
|
|
35
35
|
const { bindDesktopCompanionGateway } = require('./services/desktop/gateway');
|
|
36
36
|
const { bindWearableGateway } = require('./services/wearable/gateway');
|
|
37
|
+
const { StreamHub } = require('./services/streaming/stream-hub');
|
|
37
38
|
|
|
38
39
|
function parseBooleanFlag(value, fallback = false) {
|
|
39
40
|
const normalized = String(value || '').trim().toLowerCase();
|
|
@@ -89,6 +90,8 @@ const app = express();
|
|
|
89
90
|
app.disable('x-powered-by');
|
|
90
91
|
const httpServer = createServer(app);
|
|
91
92
|
const io = createSocketServer(httpServer, { validateOrigin });
|
|
93
|
+
const streamHub = new StreamHub(io);
|
|
94
|
+
app.locals.streamHub = streamHub;
|
|
92
95
|
app.locals.httpRuntimeConfig = {
|
|
93
96
|
secureCookies: SECURE_COOKIES,
|
|
94
97
|
trustProxy: TRUST_PROXY,
|
|
@@ -112,7 +115,7 @@ registerApiRoutes(app);
|
|
|
112
115
|
registerStaticRoutes(app);
|
|
113
116
|
registerErrorHandler(app);
|
|
114
117
|
bindBrowserExtensionGateway(httpServer, app);
|
|
115
|
-
bindDesktopCompanionGateway(httpServer, app, sessionMiddleware);
|
|
118
|
+
bindDesktopCompanionGateway(httpServer, app, sessionMiddleware, streamHub);
|
|
116
119
|
bindWearableGateway(httpServer, app, sessionMiddleware);
|
|
117
120
|
|
|
118
121
|
let shuttingDown = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
523e49e35d448c4b94454f78c7beebb8
|