mindexec-ai 0.2.385

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.
Files changed (99) hide show
  1. package/README.md +354 -0
  2. package/codex-runtime.js +1339 -0
  3. package/launch-bridge.cjs +236 -0
  4. package/package.json +77 -0
  5. package/port-guard.cjs +232 -0
  6. package/remote-fast/osx-arm64/mindexec-remote-fast +0 -0
  7. package/remote-fast/osx-arm64/mindexec-remote-fast.deps.json +24 -0
  8. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  9. package/remote-fast/osx-arm64/mindexec-remote-fast.runtimeconfig.json +13 -0
  10. package/remote-fast/osx-x64/mindexec-remote-fast +0 -0
  11. package/remote-fast/osx-x64/mindexec-remote-fast.deps.json +24 -0
  12. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  13. package/remote-fast/osx-x64/mindexec-remote-fast.runtimeconfig.json +13 -0
  14. package/remote-fast/win-x64/mindexec-remote-fast.deps.json +24 -0
  15. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  16. package/remote-fast/win-x64/mindexec-remote-fast.exe +0 -0
  17. package/remote-fast/win-x64/mindexec-remote-fast.runtimeconfig.json +20 -0
  18. package/remote-hub.js +3106 -0
  19. package/scripts/auth-session-smoke.mjs +262 -0
  20. package/scripts/remote-agent-managed-smoke.mjs +291 -0
  21. package/scripts/remote-agent-package-smoke.mjs +64 -0
  22. package/scripts/remote-agent-ws-smoke.mjs +202 -0
  23. package/scripts/remote-fast-live-rate-smoke.mjs +355 -0
  24. package/scripts/remote-fast-mdm-browser-smoke.mjs +476 -0
  25. package/scripts/remote-fleet-render-smoke.mjs +1491 -0
  26. package/scripts/remote-frame-ws-smoke.mjs +234 -0
  27. package/scripts/remote-http-smoke.mjs +592 -0
  28. package/scripts/remote-hub-identity-smoke.mjs +146 -0
  29. package/scripts/remote-hub-scale-smoke.mjs +124 -0
  30. package/scripts/remote-hub-smoke.mjs +631 -0
  31. package/scripts/remote-input-ws-smoke.mjs +263 -0
  32. package/scripts/remote-registry-follower-smoke.mjs +752 -0
  33. package/scripts/setup-tree-sitter-grammars.mjs +80 -0
  34. package/server.js +15709 -0
  35. package/start-bridge.bat +32 -0
  36. package/start-bridge.sh +81 -0
  37. package/tree-sitter-grammars/README.md +18 -0
  38. package/tree-sitter-grammars/tree-sitter-c_sharp.wasm +0 -0
  39. package/tree-sitter-grammars/tree-sitter-go.wasm +0 -0
  40. package/tree-sitter-grammars/tree-sitter-java.wasm +0 -0
  41. package/tree-sitter-grammars/tree-sitter-javascript.wasm +0 -0
  42. package/tree-sitter-grammars/tree-sitter-python.wasm +0 -0
  43. package/tree-sitter-grammars/tree-sitter-rust.wasm +0 -0
  44. package/tree-sitter-grammars/tree-sitter-tsx.wasm +0 -0
  45. package/tree-sitter-grammars/tree-sitter-typescript.wasm +0 -0
  46. package/wwwroot/_headers +73 -0
  47. package/wwwroot/_redirects +1 -0
  48. package/wwwroot/appsettings.json +83 -0
  49. package/wwwroot/assets/AdminDashboardPage-B2vz2Px9.css +1 -0
  50. package/wwwroot/assets/AdminDashboardPage-DnuCHywn.js +1 -0
  51. package/wwwroot/assets/AppSidebar-DU2OgSiv.js +2 -0
  52. package/wwwroot/assets/AuthPages-BrH6kRcv.css +1 -0
  53. package/wwwroot/assets/AuthPages-Dgezl7Vj.js +1 -0
  54. package/wwwroot/assets/CodePage-7kgZlB3O.js +87 -0
  55. package/wwwroot/assets/CodePage-Bncc352E.css +1 -0
  56. package/wwwroot/assets/CompanyCorePage-ChBnq1ve.css +1 -0
  57. package/wwwroot/assets/CompanyCorePage-CzIZIIU_.js +13 -0
  58. package/wwwroot/assets/ExecutionModePage-B-etp_mc.js +18 -0
  59. package/wwwroot/assets/ExecutionModePage-TLuld9l3.css +1 -0
  60. package/wwwroot/assets/LaunchLeadCapture-Bx9LM0IX.js +1 -0
  61. package/wwwroot/assets/LaunchLeadCapture-CiRI1shz.css +1 -0
  62. package/wwwroot/assets/MarketingHome-BsyerRpe.js +1 -0
  63. package/wwwroot/assets/MarketingHome-DPzaYzA_.css +1 -0
  64. package/wwwroot/assets/MindCanvas-DtqOZnoW.css +1 -0
  65. package/wwwroot/assets/MindCanvas-zEDXzaxW.js +49 -0
  66. package/wwwroot/assets/PlanMasterPage-CJ36rep-.css +1 -0
  67. package/wwwroot/assets/PlanMasterPage-NZ_mPvaE.js +4 -0
  68. package/wwwroot/assets/PricingPage-Cg_0i_ZR.css +1 -0
  69. package/wwwroot/assets/PricingPage-Ylrn8l2g.js +1 -0
  70. package/wwwroot/assets/ToolPages-3M2KqA9k.js +28 -0
  71. package/wwwroot/assets/ToolPages-DIB187pZ.css +1 -0
  72. package/wwwroot/assets/YouTubeSearchPage-COv1oAA7.js +4 -0
  73. package/wwwroot/assets/YouTubeSearchPage-IPPa_BIH.css +1 -0
  74. package/wwwroot/assets/app-runtime-xD2Z3NdN.js +1 -0
  75. package/wwwroot/assets/canvas-runtime-BbicBcOj.js +44 -0
  76. package/wwwroot/assets/code-agent-runtime-B5PPZd1t.js +74 -0
  77. package/wwwroot/assets/executionModeSettings-NJqurj-o.js +1 -0
  78. package/wwwroot/assets/index-CQMKCp-t.js +2 -0
  79. package/wwwroot/assets/index-yNpEK-gp.css +1 -0
  80. package/wwwroot/assets/marketingTools-DN_rnHeB.js +4 -0
  81. package/wwwroot/assets/mindCanvasSearchWorker-BzPMsHOB.js +1 -0
  82. package/wwwroot/assets/mindexecution-mindcanvas.png +0 -0
  83. package/wwwroot/assets/mindexecution-prod-home-current.png +0 -0
  84. package/wwwroot/assets/mindexecution-prod-pricing-current.png +0 -0
  85. package/wwwroot/assets/pricingCheckoutShell-O-DnwmbU.js +1 -0
  86. package/wwwroot/assets/productionAdapterConfig-C5jfk6oG.js +1 -0
  87. package/wwwroot/assets/runtimeSettingsPersistenceProjection-BoNWmYjU.js +1 -0
  88. package/wwwroot/assets/storage-TM3YrWaj.js +1 -0
  89. package/wwwroot/assets/supabaseAuthAdapter-DA43DeSY.js +44 -0
  90. package/wwwroot/assets/toolHandoff-D5e5f7t5.js +4 -0
  91. package/wwwroot/assets/vendor-icons-DE3gIReG.js +681 -0
  92. package/wwwroot/assets/vendor-msgpack-BE8aAsr3.js +1 -0
  93. package/wwwroot/assets/vendor-react-BXzpOyCS.js +40 -0
  94. package/wwwroot/favicon.svg +7 -0
  95. package/wwwroot/index.html +22 -0
  96. package/wwwroot/manifest.webmanifest +19 -0
  97. package/wwwroot/robots.txt +4 -0
  98. package/wwwroot/service-worker.js +7 -0
  99. package/wwwroot/sitemap.xml +39 -0
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+
3
+ import assert from 'node:assert/strict';
4
+ import { WebSocket } from 'ws';
5
+ import { createRemoteHub } from '../remote-hub.js';
6
+
7
+ const PAIR_TOKEN = 'remote-agent-ws-smoke-token';
8
+ const DEVICE_ID = 'remote-agent-ws-smoke-device';
9
+ const SMOKE_PNG = Buffer.from(
10
+ 'iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC',
11
+ 'base64');
12
+
13
+ function wait(ms) {
14
+ return new Promise(resolve => setTimeout(resolve, ms));
15
+ }
16
+
17
+ async function waitFor(predicate, timeoutMs = 5000, label = 'condition') {
18
+ const startedAt = Date.now();
19
+ while (Date.now() - startedAt < timeoutMs) {
20
+ const value = predicate();
21
+ if (value) {
22
+ return value;
23
+ }
24
+ await wait(25);
25
+ }
26
+
27
+ throw new Error(`Timed out waiting for ${label}.`);
28
+ }
29
+
30
+ function writeAgentBinaryFrame(ws, header, payload) {
31
+ const framePayload = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
32
+ const metadata = Buffer.from(JSON.stringify({
33
+ ...header,
34
+ type: 'frame.binary',
35
+ byteLength: framePayload.length
36
+ }), 'utf8');
37
+ const packet = Buffer.allocUnsafe(4 + metadata.length + framePayload.length);
38
+ packet.writeUInt32BE(metadata.length, 0);
39
+ metadata.copy(packet, 4);
40
+ framePayload.copy(packet, 4 + metadata.length);
41
+ ws.send(packet);
42
+ }
43
+
44
+ const hub = createRemoteHub({
45
+ env: {
46
+ MINDEXEC_REMOTE_HUB: '1',
47
+ REMOTE_HUB_HOST: '127.0.0.1',
48
+ REMOTE_HUB_PORT: '0',
49
+ REMOTE_HUB_PAIR_TOKEN: PAIR_TOKEN
50
+ }
51
+ });
52
+
53
+ let ws = null;
54
+
55
+ try {
56
+ await hub.start();
57
+ const status = hub.getStatus({ includeSecrets: true });
58
+ assert.equal(status.started, true);
59
+
60
+ ws = new WebSocket(`ws://127.0.0.1:${status.port}/remote-agent`);
61
+ const received = [];
62
+ ws.on('message', data => {
63
+ if (typeof data !== 'string' && !Buffer.isBuffer(data)) {
64
+ return;
65
+ }
66
+
67
+ const text = Buffer.isBuffer(data) ? data.toString('utf8') : data;
68
+ if (!text.trim().startsWith('{')) {
69
+ return;
70
+ }
71
+
72
+ received.push(JSON.parse(text));
73
+ });
74
+
75
+ await new Promise((resolve, reject) => {
76
+ ws.once('open', resolve);
77
+ ws.once('error', reject);
78
+ });
79
+
80
+ ws.send(JSON.stringify({
81
+ type: 'hello',
82
+ pairToken: PAIR_TOKEN,
83
+ deviceId: DEVICE_ID,
84
+ deviceName: 'Remote Agent WS Smoke',
85
+ hostname: 'remote-agent-ws-smoke',
86
+ platform: process.platform,
87
+ arch: process.arch,
88
+ pid: process.pid,
89
+ agentVersion: '0.0.0-ws-smoke',
90
+ runtime: 'node-smoke',
91
+ capabilities: {
92
+ status: true,
93
+ thumbnail: true,
94
+ liveStream: true,
95
+ binaryFrames: true,
96
+ control: true,
97
+ computerAgent: true,
98
+ taskDispatch: true,
99
+ aiAssist: false
100
+ }
101
+ }));
102
+ ws.send(JSON.stringify({
103
+ type: 'status',
104
+ status: {
105
+ uptimeSec: 1,
106
+ totalMem: 2,
107
+ freeMem: 1,
108
+ timestamp: new Date().toISOString()
109
+ }
110
+ }));
111
+
112
+ await waitFor(() => received.find(item => item.type === 'welcome'), 5000, 'welcome');
113
+ const device = await waitFor(() => {
114
+ const current = hub.listDevices();
115
+ return current.length === 1 && current[0].deviceId === DEVICE_ID ? current[0] : null;
116
+ }, 5000, 'device registration');
117
+ assert.equal(device.connected, true);
118
+ assert.equal(device.capabilities.binaryFrames, true);
119
+
120
+ const inputCommand = hub.sendInputControl(DEVICE_ID, { type: 'noop' });
121
+ assert.equal(inputCommand.ok, true);
122
+ const inputMessage = await waitFor(
123
+ () => received.find(item => item.commandId === inputCommand.commandId),
124
+ 5000,
125
+ 'input command');
126
+ assert.equal(inputMessage.command, 'input.control');
127
+ ws.send(JSON.stringify({
128
+ type: 'command.result',
129
+ commandId: inputCommand.commandId,
130
+ result: {
131
+ input: true,
132
+ handled: true
133
+ }
134
+ }));
135
+ await waitFor(() => hub.listDevices()[0]?.counters?.commandResultsReceived === 1, 5000, 'input result');
136
+
137
+ const thumbnailCommand = hub.requestThumbnail(DEVICE_ID, {
138
+ streamId: 'remote-agent-ws-thumb',
139
+ maxWidth: 320,
140
+ maxHeight: 180,
141
+ quality: 50
142
+ });
143
+ assert.equal(thumbnailCommand.ok, true);
144
+ await waitFor(
145
+ () => received.find(item => item.commandId === thumbnailCommand.commandId),
146
+ 5000,
147
+ 'thumbnail command');
148
+ writeAgentBinaryFrame(ws, {
149
+ frameKind: 'thumbnail',
150
+ commandId: thumbnailCommand.commandId,
151
+ streamId: 'remote-agent-ws-thumb',
152
+ frameSeq: 11,
153
+ width: 2,
154
+ height: 1,
155
+ mimeType: 'image/png',
156
+ capturedAt: new Date().toISOString()
157
+ }, SMOKE_PNG);
158
+ const thumbnailDevice = await waitFor(() => {
159
+ const current = hub.listDevices()[0];
160
+ return current?.latestThumbnail?.frameSeq === 11 ? current : null;
161
+ }, 5000, 'websocket binary thumbnail');
162
+ assert.equal(thumbnailDevice.latestThumbnail.transport, 'binary');
163
+ assert.equal(thumbnailDevice.latestThumbnail.byteLength, SMOKE_PNG.length);
164
+
165
+ const liveCommand = hub.startLiveStream(DEVICE_ID, {
166
+ streamId: 'remote-agent-ws-live',
167
+ fps: 10,
168
+ maxWidth: 320,
169
+ maxHeight: 180,
170
+ quality: 50
171
+ });
172
+ assert.equal(liveCommand.ok, true);
173
+ await waitFor(
174
+ () => received.find(item => item.commandId === liveCommand.commandId),
175
+ 5000,
176
+ 'live command');
177
+ writeAgentBinaryFrame(ws, {
178
+ frameKind: 'stream',
179
+ commandId: liveCommand.commandId,
180
+ streamId: 'remote-agent-ws-live',
181
+ frameSeq: 12,
182
+ width: 2,
183
+ height: 1,
184
+ mimeType: 'image/png',
185
+ fps: 10,
186
+ capturedAt: new Date().toISOString()
187
+ }, SMOKE_PNG);
188
+ const liveDevice = await waitFor(() => {
189
+ const current = hub.listDevices()[0];
190
+ return current?.latestLiveFrame?.frameSeq === 12 ? current : null;
191
+ }, 5000, 'websocket binary live frame');
192
+ assert.equal(liveDevice.latestLiveFrame.transport, 'binary');
193
+ assert.equal(liveDevice.latestLiveFrame.streamId, 'remote-agent-ws-live');
194
+
195
+ ws.close();
196
+ console.log('RemoteAgent WebSocket smoke OK');
197
+ } finally {
198
+ if (ws && ws.readyState === WebSocket.OPEN) {
199
+ ws.close();
200
+ }
201
+ await hub.close();
202
+ }
@@ -0,0 +1,355 @@
1
+ #!/usr/bin/env node
2
+
3
+ import assert from 'node:assert/strict';
4
+ import { spawn } from 'node:child_process';
5
+ import { mkdtemp, rm } from 'node:fs/promises';
6
+ import net from 'node:net';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { WebSocket } from 'ws';
11
+
12
+ const BRIDGE_TOKEN = 'remote-fast-live-rate-smoke-token';
13
+ const PAIR_TOKEN = 'remote-fast-live-rate-pair-token';
14
+ const LEASE_ID = 'remote-fast-live-rate-lease';
15
+ const LOCAL_BRIDGE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
16
+ const REQUESTED_FPS = Number(process.env.MINDEXEC_REMOTE_LIVE_RATE_REQUEST_FPS || 12);
17
+ const SAMPLE_MS = Number(process.env.MINDEXEC_REMOTE_LIVE_RATE_SAMPLE_MS || 3500);
18
+ const MIN_EFFECTIVE_FPS = Number(process.env.MINDEXEC_REMOTE_LIVE_RATE_MIN_FPS || 10);
19
+ const MAX_DROP_RATIO = Number(process.env.MINDEXEC_REMOTE_LIVE_RATE_MAX_DROP_RATIO || 0.2);
20
+ const MAX_SEQ_GAP = Number(process.env.MINDEXEC_REMOTE_LIVE_RATE_MAX_SEQ_GAP || 4);
21
+
22
+ function wait(ms) {
23
+ return new Promise(resolve => setTimeout(resolve, ms));
24
+ }
25
+
26
+ async function findFreePort() {
27
+ return await new Promise((resolve, reject) => {
28
+ const server = net.createServer();
29
+ server.unref();
30
+ server.once('error', reject);
31
+ server.listen(0, '127.0.0.1', () => {
32
+ const address = server.address();
33
+ const port = typeof address === 'object' && address ? address.port : 0;
34
+ server.close(() => resolve(port));
35
+ });
36
+ });
37
+ }
38
+
39
+ async function fetchJson(url, options = {}) {
40
+ const response = await fetch(url, {
41
+ ...options,
42
+ headers: {
43
+ ...(options.body ? { 'Content-Type': 'application/json' } : {}),
44
+ 'X-Bridge-Token': BRIDGE_TOKEN,
45
+ ...(options.headers || {})
46
+ }
47
+ });
48
+
49
+ let payload = null;
50
+ try {
51
+ payload = await response.json();
52
+ } catch {
53
+ // Some failure responses may be empty.
54
+ }
55
+
56
+ return { status: response.status, ok: response.ok, payload };
57
+ }
58
+
59
+ async function waitFor(predicate, timeoutMs = 30000, label = 'condition') {
60
+ const startedAt = Date.now();
61
+ let lastError = null;
62
+ while (Date.now() - startedAt < timeoutMs) {
63
+ try {
64
+ const value = await predicate();
65
+ if (value) {
66
+ return value;
67
+ }
68
+ } catch (error) {
69
+ lastError = error;
70
+ }
71
+
72
+ await wait(100);
73
+ }
74
+
75
+ throw new Error(`Timed out waiting for ${label}${lastError ? `: ${lastError.message}` : ''}.`);
76
+ }
77
+
78
+ function spawnBridge({ bridgePort, remoteHubPort, workspacePath, label, traceFrames = false }) {
79
+ const child = spawn(process.execPath, ['server.js'], {
80
+ cwd: LOCAL_BRIDGE_DIR,
81
+ stdio: ['ignore', 'pipe', 'pipe'],
82
+ windowsHide: true,
83
+ env: {
84
+ ...process.env,
85
+ BRIDGE_PORT: String(bridgePort),
86
+ BRIDGE_TOKEN,
87
+ BRIDGE_REQUIRE_TOKEN: '1',
88
+ MINDEXEC_REMOTE_HUB: '1',
89
+ MINDEXEC_REMOTE_TRACE_FRAMES: traceFrames ? '1' : '',
90
+ REMOTE_HUB_HOST: '127.0.0.1',
91
+ REMOTE_HUB_PORT: String(remoteHubPort),
92
+ REMOTE_HUB_PAIR_TOKEN: PAIR_TOKEN,
93
+ WORKSPACE_PATH: workspacePath,
94
+ NO_COLOR: '1'
95
+ }
96
+ });
97
+
98
+ let stdout = '';
99
+ let stderr = '';
100
+ child.stdout.on('data', chunk => {
101
+ stdout += chunk.toString();
102
+ });
103
+ child.stderr.on('data', chunk => {
104
+ stderr += chunk.toString();
105
+ });
106
+
107
+ const exitPromise = new Promise(resolve => child.once('exit', resolve));
108
+ const details = () => `${label} stdout=${stdout}\n${label} stderr=${stderr}`;
109
+ const stop = async () => {
110
+ if (child.exitCode === null && !child.killed) {
111
+ child.kill('SIGTERM');
112
+ await Promise.race([exitPromise, wait(5000)]);
113
+ if (child.exitCode === null && !child.killed) {
114
+ child.kill('SIGKILL');
115
+ }
116
+ }
117
+ };
118
+
119
+ return {
120
+ baseUrl: `http://127.0.0.1:${bridgePort}`,
121
+ child,
122
+ details,
123
+ stop
124
+ };
125
+ }
126
+
127
+ async function waitForBridge(bridge) {
128
+ return await waitFor(async () => {
129
+ const result = await fetchJson(`${bridge.baseUrl}/api/status`);
130
+ return result.ok && result.payload?.status === 'ok' ? result.payload : null;
131
+ }, 30000, `bridge startup\n${bridge.details()}`);
132
+ }
133
+
134
+ function parseFramePacket(data) {
135
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
136
+ if (buffer.length <= 8 || buffer[0] === 0x7b || buffer[0] === 0x5b) {
137
+ return null;
138
+ }
139
+
140
+ const metaLength = buffer.readUInt32BE(0);
141
+ if (metaLength <= 0 || metaLength > 64 * 1024 || 4 + metaLength >= buffer.length) {
142
+ return null;
143
+ }
144
+
145
+ const metadata = JSON.parse(buffer.subarray(4, 4 + metaLength).toString('utf8'));
146
+ const payload = buffer.subarray(4 + metaLength);
147
+ if (String(metadata?.type || '').toLowerCase() !== 'remote.frame.binary') {
148
+ return null;
149
+ }
150
+
151
+ return { metadata, payload, receivedAtMs: Date.now() };
152
+ }
153
+
154
+ async function openFrameSocket(baseUrl, deviceId) {
155
+ const ws = new WebSocket(`${baseUrl.replace(/^http:/, 'ws:')}/api/remote/frames/ws?token=${encodeURIComponent(BRIDGE_TOKEN)}`);
156
+ await new Promise((resolve, reject) => {
157
+ ws.once('open', resolve);
158
+ ws.once('error', reject);
159
+ });
160
+ ws.send(JSON.stringify({ type: 'subscribe', deviceIds: [deviceId] }));
161
+ return ws;
162
+ }
163
+
164
+ async function collectLiveFrames(ws, deviceId, sampleMs) {
165
+ const frames = [];
166
+ let firstFrameAt = 0;
167
+ let settle = null;
168
+ let settled = false;
169
+
170
+ const finish = () => {
171
+ if (settled) {
172
+ return;
173
+ }
174
+ settled = true;
175
+ settle?.();
176
+ };
177
+
178
+ const done = new Promise((resolve, reject) => {
179
+ settle = resolve;
180
+ const timeout = setTimeout(() => reject(new Error('timed out waiting for RemoteFast binary frames')), 25000);
181
+ ws.on('message', data => {
182
+ const frame = parseFramePacket(data);
183
+ if (!frame
184
+ || String(frame.metadata.kind || '').toLowerCase() !== 'live'
185
+ || String(frame.metadata.deviceId || '') !== deviceId) {
186
+ return;
187
+ }
188
+
189
+ frames.push(frame);
190
+ if (!firstFrameAt) {
191
+ firstFrameAt = frame.receivedAtMs;
192
+ }
193
+ if (frame.receivedAtMs - firstFrameAt >= sampleMs) {
194
+ clearTimeout(timeout);
195
+ finish();
196
+ }
197
+ });
198
+ ws.once('close', () => {
199
+ clearTimeout(timeout);
200
+ if (!settled) {
201
+ reject(new Error('frame websocket closed during live-rate sample'));
202
+ }
203
+ });
204
+ ws.once('error', error => {
205
+ clearTimeout(timeout);
206
+ reject(error);
207
+ });
208
+ });
209
+
210
+ await done;
211
+ return frames;
212
+ }
213
+
214
+ function summarizeFrames(frames) {
215
+ const first = frames[0];
216
+ const last = frames[frames.length - 1];
217
+ const durationMs = Math.max(1, Number(last?.receivedAtMs || 0) - Number(first?.receivedAtMs || 0));
218
+ const fps = frames.length > 1 ? ((frames.length - 1) * 1000) / durationMs : 0;
219
+ const seqs = frames.map(frame => Number(frame.metadata.frameSeq || 0)).filter(Number.isFinite);
220
+ const uniqueSeqs = new Set(seqs);
221
+ const firstSeq = seqs[0] || 0;
222
+ const lastSeq = seqs[seqs.length - 1] || 0;
223
+ const expectedSeqs = lastSeq >= firstSeq ? lastSeq - firstSeq + 1 : uniqueSeqs.size;
224
+ const droppedSeqs = Math.max(0, expectedSeqs - uniqueSeqs.size);
225
+ let maxSeqGap = 0;
226
+ for (let index = 1; index < seqs.length; index += 1) {
227
+ maxSeqGap = Math.max(maxSeqGap, Math.max(0, seqs[index] - seqs[index - 1] - 1));
228
+ }
229
+ const byteLengths = frames.map(frame => Number(frame.payload?.length || 0)).filter(value => value > 0);
230
+ return {
231
+ frames: frames.length,
232
+ uniqueSeqs: uniqueSeqs.size,
233
+ expectedSeqs,
234
+ droppedSeqs,
235
+ dropRatio: expectedSeqs > 0 ? droppedSeqs / expectedSeqs : 0,
236
+ maxSeqGap,
237
+ durationMs,
238
+ fps,
239
+ firstSeq,
240
+ lastSeq,
241
+ minBytes: byteLengths.length > 0 ? Math.min(...byteLengths) : 0,
242
+ maxBytes: byteLengths.length > 0 ? Math.max(...byteLengths) : 0,
243
+ metadataFps: Number(last?.metadata?.fps || 0) || 0,
244
+ mode: String(last?.metadata?.mode || '')
245
+ };
246
+ }
247
+
248
+ async function main() {
249
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'mindexec-remote-fast-live-rate-'));
250
+ const hostWorkspace = path.join(tempRoot, 'host');
251
+ const clientWorkspace = path.join(tempRoot, 'client');
252
+ const hostBridgePort = await findFreePort();
253
+ const hostRemotePort = await findFreePort();
254
+ const clientBridgePort = await findFreePort();
255
+ const clientRemotePort = await findFreePort();
256
+ const managerEndpoint = `127.0.0.1:${hostRemotePort}`;
257
+ let hostBridge = null;
258
+ let clientBridge = null;
259
+ let ws = null;
260
+
261
+ try {
262
+ hostBridge = spawnBridge({
263
+ bridgePort: hostBridgePort,
264
+ remoteHubPort: hostRemotePort,
265
+ workspacePath: hostWorkspace,
266
+ label: 'host'
267
+ });
268
+ await waitForBridge(hostBridge);
269
+
270
+ clientBridge = spawnBridge({
271
+ bridgePort: clientBridgePort,
272
+ remoteHubPort: clientRemotePort,
273
+ workspacePath: clientWorkspace,
274
+ label: 'client',
275
+ traceFrames: true
276
+ });
277
+ await waitForBridge(clientBridge);
278
+
279
+ const connectResult = await fetchJson(`${clientBridge.baseUrl}/api/remote/agent/connect`, {
280
+ method: 'POST',
281
+ body: JSON.stringify({
282
+ managerCandidates: [managerEndpoint],
283
+ pairToken: PAIR_TOKEN,
284
+ leaseId: LEASE_ID,
285
+ nodeId: 'remote-fast-live-rate-node',
286
+ engine: 'auto',
287
+ source: 'live-rate-smoke'
288
+ })
289
+ });
290
+ assert.equal(connectResult.ok, true, JSON.stringify(connectResult.payload));
291
+ assert.equal(connectResult.payload?.ok, true, JSON.stringify(connectResult.payload));
292
+ assert.equal(connectResult.payload?.agent?.usingNpx, false, JSON.stringify(connectResult.payload?.agent));
293
+ assert.match(String(connectResult.payload?.agent?.launcher || ''), /mindexec-remote-fast/i);
294
+
295
+ const device = await waitFor(async () => {
296
+ const result = await fetchJson(`${hostBridge.baseUrl}/api/remote/devices`);
297
+ return result.payload?.devices?.find(item =>
298
+ item.connected === true
299
+ && item.capabilities?.liveStream === true
300
+ && item.capabilities?.binaryFrames === true) || null;
301
+ }, 15000, `RemoteFast device registration\n${hostBridge.details()}\n${clientBridge.details()}`);
302
+
303
+ ws = await openFrameSocket(hostBridge.baseUrl, device.deviceId);
304
+ const live = await fetchJson(`${hostBridge.baseUrl}/api/remote/devices/${encodeURIComponent(device.deviceId)}/live/start`, {
305
+ method: 'POST',
306
+ body: JSON.stringify({
307
+ fps: REQUESTED_FPS,
308
+ maxWidth: 960,
309
+ maxHeight: 540,
310
+ quality: 60
311
+ })
312
+ });
313
+ assert.equal(live.ok, true, JSON.stringify(live.payload));
314
+ assert.equal(live.payload?.ok, true, JSON.stringify(live.payload));
315
+
316
+ let frames = [];
317
+ try {
318
+ frames = await collectLiveFrames(ws, device.deviceId, SAMPLE_MS);
319
+ } catch (error) {
320
+ const hostStatus = await fetchJson(`${hostBridge.baseUrl}/api/remote/status`).catch(() => null);
321
+ const hostDevices = await fetchJson(`${hostBridge.baseUrl}/api/remote/devices`).catch(() => null);
322
+ const clientAgent = await fetchJson(`${clientBridge.baseUrl}/api/remote/agent/status`).catch(() => null);
323
+ error.message = `${error.message}\n` +
324
+ `hostStatus=${JSON.stringify(hostStatus?.payload || null)}\n` +
325
+ `hostDevices=${JSON.stringify(hostDevices?.payload || null)}\n` +
326
+ `clientAgent=${JSON.stringify(clientAgent?.payload || null)}\n` +
327
+ `${hostBridge.details()}\n${clientBridge.details()}`;
328
+ throw error;
329
+ }
330
+ const summary = summarizeFrames(frames);
331
+ assert.ok(summary.frames >= 2, JSON.stringify(summary));
332
+ assert.equal(summary.uniqueSeqs, summary.frames, `frameSeq should advance for each binary frame: ${JSON.stringify(summary)}`);
333
+ assert.ok(summary.minBytes > 0, `binary payload should be non-empty: ${JSON.stringify(summary)}`);
334
+ assert.equal(summary.metadataFps, REQUESTED_FPS, JSON.stringify(summary));
335
+ assert.equal(summary.mode, 'remote-fast', JSON.stringify(summary));
336
+ assert.ok(summary.fps >= MIN_EFFECTIVE_FPS, `RemoteFast live-rate too low: ${JSON.stringify(summary)}`);
337
+ assert.ok(summary.dropRatio <= MAX_DROP_RATIO, `RemoteFast live-rate dropped too many frame sequences: ${JSON.stringify(summary)}`);
338
+ assert.ok(summary.maxSeqGap <= MAX_SEQ_GAP, `RemoteFast live-rate sequence gap too large: ${JSON.stringify(summary)}`);
339
+
340
+ console.log(`RemoteFast live-rate smoke OK (${summary.frames} frames, ${summary.fps.toFixed(1)} fps, drop ${(summary.dropRatio * 100).toFixed(1)}%, max gap ${summary.maxSeqGap}, bytes ${summary.minBytes}-${summary.maxBytes})`);
341
+ } finally {
342
+ if (ws && ws.readyState === WebSocket.OPEN) {
343
+ ws.close();
344
+ }
345
+ if (clientBridge) {
346
+ await clientBridge.stop();
347
+ }
348
+ if (hostBridge) {
349
+ await hostBridge.stop();
350
+ }
351
+ await rm(tempRoot, { recursive: true, force: true });
352
+ }
353
+ }
354
+
355
+ await main();