@mindexec/cli 0.2.152 → 0.2.154

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/server.js CHANGED
@@ -2125,10 +2125,13 @@ app.get('/api/local-files/:id', async (req, res) => {
2125
2125
  const httpServer = createServer(app);
2126
2126
  const wss = new WebSocketServer({ noServer: true });
2127
2127
  const remoteFrameWss = new WebSocketServer({ noServer: true });
2128
+ const remoteInputWss = new WebSocketServer({ noServer: true });
2128
2129
  const wsClients = new Set();
2129
2130
  const remoteFrameClients = new Set();
2131
+ const remoteInputClients = new Set();
2130
2132
  const shellJobs = new Map();
2131
2133
  let remoteFrameClientSeq = 0;
2134
+ let remoteInputClientSeq = 0;
2132
2135
  const remoteFrameWsDiagnostics = {
2133
2136
  opened: 0,
2134
2137
  closed: 0,
@@ -2157,6 +2160,25 @@ const remoteFrameWsDiagnostics = {
2157
2160
  lastAutoStartStarted: 0,
2158
2161
  lastAutoStartSkipped: 0
2159
2162
  };
2163
+ const remoteInputWsDiagnostics = {
2164
+ opened: 0,
2165
+ closed: 0,
2166
+ errors: 0,
2167
+ messagesReceived: 0,
2168
+ messagesInvalid: 0,
2169
+ inputsQueued: 0,
2170
+ inputsFailed: 0,
2171
+ pointerMovesQueued: 0,
2172
+ keyEventsQueued: 0,
2173
+ lastOpenAt: '',
2174
+ lastCloseAt: '',
2175
+ lastErrorAt: '',
2176
+ lastInputAt: '',
2177
+ lastDeviceId: '',
2178
+ lastInputType: '',
2179
+ lastCommandId: '',
2180
+ lastError: ''
2181
+ };
2160
2182
  const REMOTE_FRAME_WS_AUTO_START_LIMIT = 120;
2161
2183
  const REMOTE_FRAME_WS_DEFAULT_FPS = 12;
2162
2184
  const REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH = 960;
@@ -2183,6 +2205,14 @@ httpServer.on('upgrade', (req, socket, head) => {
2183
2205
  return;
2184
2206
  }
2185
2207
 
2208
+ if (parsed.pathname === '/api/remote/input/ws'
2209
+ && (!bridgeAuthRequired || token === bridgeToken)) {
2210
+ remoteInputWss.handleUpgrade(req, socket, head, (ws) => {
2211
+ remoteInputWss.emit('connection', ws, req);
2212
+ });
2213
+ return;
2214
+ }
2215
+
2186
2216
  {
2187
2217
  socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
2188
2218
  socket.destroy();
@@ -2552,6 +2582,195 @@ remoteFrameWss.on('connection', (ws, req) => {
2552
2582
  });
2553
2583
  });
2554
2584
 
2585
+ function sendRemoteInputClientJson(ws, payload) {
2586
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
2587
+ return false;
2588
+ }
2589
+
2590
+ try {
2591
+ ws.send(JSON.stringify(payload));
2592
+ return true;
2593
+ } catch {
2594
+ return false;
2595
+ }
2596
+ }
2597
+
2598
+ function normalizeRemoteInputWsPayload(payload = {}) {
2599
+ const outer = payload && typeof payload === 'object' ? payload : {};
2600
+ const inputSource = outer.input && typeof outer.input === 'object'
2601
+ ? outer.input
2602
+ : outer.Input && typeof outer.Input === 'object'
2603
+ ? outer.Input
2604
+ : outer;
2605
+ const input = { ...inputSource };
2606
+ input.type = String(input.type ?? input.Type ?? '').trim();
2607
+ const deviceId = String(
2608
+ outer.deviceId
2609
+ ?? outer.DeviceId
2610
+ ?? input.deviceId
2611
+ ?? input.DeviceId
2612
+ ?? ''
2613
+ ).trim();
2614
+ delete input.deviceId;
2615
+ delete input.DeviceId;
2616
+
2617
+ return {
2618
+ deviceId,
2619
+ input,
2620
+ requestId: String(outer.requestId ?? outer.RequestId ?? '').trim(),
2621
+ ack: outer.ack === true || outer.Ack === true
2622
+ };
2623
+ }
2624
+
2625
+ function serializeRemoteInputWsDiagnostics() {
2626
+ const clients = [];
2627
+ for (const client of remoteInputClients) {
2628
+ clients.push({
2629
+ id: client.remoteInputClientId || '',
2630
+ readyState: client.readyState,
2631
+ connectedAt: client.remoteInputConnectedAt || '',
2632
+ received: Number(client.remoteInputReceivedCount || 0),
2633
+ queued: Number(client.remoteInputQueuedCount || 0),
2634
+ failed: Number(client.remoteInputFailedCount || 0),
2635
+ bufferedAmount: Number(client.bufferedAmount || 0),
2636
+ lastInputAt: client.remoteInputLastInputAt || '',
2637
+ lastDeviceId: client.remoteInputLastDeviceId || '',
2638
+ lastInputType: client.remoteInputLastInputType || ''
2639
+ });
2640
+ }
2641
+
2642
+ return {
2643
+ protocol: 'mindexec.remote.input.json.v1',
2644
+ path: '/api/remote/input/ws',
2645
+ clientCount: remoteInputClients.size,
2646
+ ...remoteInputWsDiagnostics,
2647
+ clients
2648
+ };
2649
+ }
2650
+
2651
+ remoteInputWss.on('connection', (ws) => {
2652
+ remoteInputClients.add(ws);
2653
+ ws.remoteInputClientId = `riws-${++remoteInputClientSeq}`;
2654
+ ws.remoteInputConnectedAt = new Date().toISOString();
2655
+ ws.remoteInputReceivedCount = 0;
2656
+ ws.remoteInputQueuedCount = 0;
2657
+ ws.remoteInputFailedCount = 0;
2658
+ remoteInputWsDiagnostics.opened += 1;
2659
+ remoteInputWsDiagnostics.lastOpenAt = ws.remoteInputConnectedAt;
2660
+
2661
+ ws.on('message', data => {
2662
+ const receivedAt = new Date().toISOString();
2663
+ remoteInputWsDiagnostics.messagesReceived += 1;
2664
+ ws.remoteInputReceivedCount = (ws.remoteInputReceivedCount || 0) + 1;
2665
+
2666
+ let payload = null;
2667
+ try {
2668
+ const text = Buffer.isBuffer(data) ? data.toString('utf8') : String(data || '');
2669
+ payload = JSON.parse(text);
2670
+ } catch {
2671
+ remoteInputWsDiagnostics.messagesInvalid += 1;
2672
+ sendRemoteInputClientJson(ws, {
2673
+ type: 'RemoteInputError',
2674
+ timestamp: receivedAt,
2675
+ error: 'invalid-json'
2676
+ });
2677
+ return;
2678
+ }
2679
+
2680
+ const messageType = String(payload?.type || payload?.Type || '').trim().toLowerCase();
2681
+ if (messageType === 'ping') {
2682
+ sendRemoteInputClientJson(ws, {
2683
+ type: 'RemoteInputPong',
2684
+ timestamp: receivedAt
2685
+ });
2686
+ return;
2687
+ }
2688
+
2689
+ if (messageType !== 'input' && messageType !== 'remote.input') {
2690
+ remoteInputWsDiagnostics.messagesInvalid += 1;
2691
+ sendRemoteInputClientJson(ws, {
2692
+ type: 'RemoteInputError',
2693
+ timestamp: receivedAt,
2694
+ error: 'unsupported-message'
2695
+ });
2696
+ return;
2697
+ }
2698
+
2699
+ const normalized = normalizeRemoteInputWsPayload(payload);
2700
+ if (!normalized.deviceId || !normalized.input.type) {
2701
+ remoteInputWsDiagnostics.messagesInvalid += 1;
2702
+ sendRemoteInputClientJson(ws, {
2703
+ type: 'RemoteInputResult',
2704
+ timestamp: receivedAt,
2705
+ ok: false,
2706
+ requestId: normalized.requestId,
2707
+ deviceId: normalized.deviceId,
2708
+ inputType: normalized.input.type,
2709
+ error: !normalized.deviceId ? 'missing-device-id' : 'missing-input-type'
2710
+ });
2711
+ return;
2712
+ }
2713
+
2714
+ const result = remoteHub.sendInputControl(normalized.deviceId, normalized.input);
2715
+ const ok = result?.ok === true;
2716
+ const inputType = String(normalized.input.type || '').trim();
2717
+ remoteInputWsDiagnostics.lastInputAt = receivedAt;
2718
+ remoteInputWsDiagnostics.lastDeviceId = normalized.deviceId;
2719
+ remoteInputWsDiagnostics.lastInputType = inputType;
2720
+ remoteInputWsDiagnostics.lastCommandId = String(result?.commandId || '');
2721
+ ws.remoteInputLastInputAt = receivedAt;
2722
+ ws.remoteInputLastDeviceId = normalized.deviceId;
2723
+ ws.remoteInputLastInputType = inputType;
2724
+
2725
+ if (ok) {
2726
+ remoteInputWsDiagnostics.inputsQueued += 1;
2727
+ ws.remoteInputQueuedCount = (ws.remoteInputQueuedCount || 0) + 1;
2728
+ if (/^pointermove$/i.test(inputType)) {
2729
+ remoteInputWsDiagnostics.pointerMovesQueued += 1;
2730
+ }
2731
+ if (/^key(up|down)$/i.test(inputType)) {
2732
+ remoteInputWsDiagnostics.keyEventsQueued += 1;
2733
+ }
2734
+ } else {
2735
+ remoteInputWsDiagnostics.inputsFailed += 1;
2736
+ remoteInputWsDiagnostics.lastError = String(result?.error || 'input-failed');
2737
+ ws.remoteInputFailedCount = (ws.remoteInputFailedCount || 0) + 1;
2738
+ }
2739
+
2740
+ const shouldAck = normalized.ack === true || !ok || !/^pointermove$/i.test(inputType);
2741
+ if (shouldAck) {
2742
+ sendRemoteInputClientJson(ws, {
2743
+ type: 'RemoteInputResult',
2744
+ timestamp: receivedAt,
2745
+ ok,
2746
+ requestId: normalized.requestId,
2747
+ deviceId: normalized.deviceId,
2748
+ inputType,
2749
+ commandId: result?.commandId || '',
2750
+ error: result?.error || ''
2751
+ });
2752
+ }
2753
+ });
2754
+
2755
+ ws.on('close', () => {
2756
+ remoteInputClients.delete(ws);
2757
+ remoteInputWsDiagnostics.closed += 1;
2758
+ remoteInputWsDiagnostics.lastCloseAt = new Date().toISOString();
2759
+ });
2760
+ ws.on('error', error => {
2761
+ remoteInputClients.delete(ws);
2762
+ remoteInputWsDiagnostics.errors += 1;
2763
+ remoteInputWsDiagnostics.lastErrorAt = new Date().toISOString();
2764
+ remoteInputWsDiagnostics.lastError = String(error?.message || error || 'input-ws-error');
2765
+ });
2766
+ sendRemoteInputClientJson(ws, {
2767
+ type: 'RemoteInputSocketReady',
2768
+ timestamp: new Date().toISOString(),
2769
+ protocol: 'mindexec.remote.input.json.v1',
2770
+ clientId: ws.remoteInputClientId
2771
+ });
2772
+ });
2773
+
2555
2774
  // Helper: Validate path is within workspace
2556
2775
  function validatePath(requestedPath) {
2557
2776
  const fullPath = resolveWorkspaceBoundPath(requestedPath);
@@ -10279,11 +10498,13 @@ app.get('/api/status', async (req, res) => {
10279
10498
  wsToken,
10280
10499
  wsPath: '/events',
10281
10500
  remoteFrameWsPath: '/api/remote/frames/ws',
10501
+ remoteInputWsPath: '/api/remote/input/ws',
10282
10502
  bridgeToken,
10283
10503
  bridgeTokenHeader,
10284
10504
  bridgeAuthRequired,
10285
10505
  remoteHub: remoteHub.getStatus({ includeSecrets: false }),
10286
10506
  remoteFrameWs: serializeRemoteFrameWsDiagnostics(),
10507
+ remoteInputWs: serializeRemoteInputWsDiagnostics(),
10287
10508
  remoteAgent: serializeRemoteAgentState(),
10288
10509
  remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
10289
10510
  remoteRegistryRealtime: serializeRemoteRegistryRealtimeState(),
@@ -12127,6 +12348,34 @@ async function shutdownBridge(signal) {
12127
12348
  // Ignore if already closed
12128
12349
  }
12129
12350
 
12351
+ for (const client of remoteFrameClients) {
12352
+ try {
12353
+ client.close();
12354
+ } catch {
12355
+ // Ignore close race
12356
+ }
12357
+ }
12358
+
12359
+ for (const client of remoteInputClients) {
12360
+ try {
12361
+ client.close();
12362
+ } catch {
12363
+ // Ignore close race
12364
+ }
12365
+ }
12366
+
12367
+ try {
12368
+ remoteFrameWss.close();
12369
+ } catch {
12370
+ // Ignore if already closed
12371
+ }
12372
+
12373
+ try {
12374
+ remoteInputWss.close();
12375
+ } catch {
12376
+ // Ignore if already closed
12377
+ }
12378
+
12130
12379
  try {
12131
12380
  stopRemoteRegistryFollower();
12132
12381
  } catch {