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,631 @@
1
+ #!/usr/bin/env node
2
+
3
+ import net from 'net';
4
+ import assert from 'assert/strict';
5
+ import { createRemoteHub } from '../remote-hub.js';
6
+
7
+ function writeJsonLine(socket, payload) {
8
+ socket.write(`${JSON.stringify(payload)}\n`);
9
+ }
10
+
11
+ function writeBinaryFrame(socket, header, payload) {
12
+ const framePayload = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
13
+ socket.write(`${JSON.stringify({
14
+ ...header,
15
+ type: 'frame.binary',
16
+ byteLength: framePayload.length
17
+ })}\n`);
18
+ socket.write(framePayload);
19
+ }
20
+
21
+ function wait(ms) {
22
+ return new Promise(resolve => setTimeout(resolve, ms));
23
+ }
24
+
25
+ async function waitFor(predicate, timeoutMs = 3000) {
26
+ const startedAt = Date.now();
27
+ while (Date.now() - startedAt < timeoutMs) {
28
+ const value = predicate();
29
+ if (value) {
30
+ return value;
31
+ }
32
+ await wait(25);
33
+ }
34
+
35
+ throw new Error('Timed out waiting for RemoteHub smoke condition.');
36
+ }
37
+
38
+ const hub = createRemoteHub({
39
+ env: {
40
+ MINDEXEC_REMOTE_HUB: '1',
41
+ REMOTE_HUB_HOST: '127.0.0.1',
42
+ REMOTE_HUB_PORT: '0',
43
+ REMOTE_HUB_PAIR_TOKEN: 'smoke-token',
44
+ REMOTE_HUB_TASK_TIMEOUT_MS: '120'
45
+ }
46
+ });
47
+ const smokePngFrame = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC', 'base64');
48
+
49
+ try {
50
+ await hub.start();
51
+ const status = hub.getStatus({ includeSecrets: true });
52
+ assert.equal(status.started, true);
53
+ assert.equal(status.canvasPagination, 'none');
54
+
55
+ const socket = net.createConnection({ host: '127.0.0.1', port: status.port });
56
+ socket.setEncoding('utf8');
57
+ await new Promise((resolve, reject) => {
58
+ socket.once('connect', resolve);
59
+ socket.once('error', reject);
60
+ });
61
+ socket.on('error', () => { });
62
+
63
+ writeJsonLine(socket, {
64
+ type: 'hello',
65
+ pairToken: 'smoke-token',
66
+ deviceId: 'smoke-device',
67
+ deviceName: 'Smoke Device',
68
+ hostname: 'smoke-host',
69
+ platform: process.platform,
70
+ arch: process.arch,
71
+ pid: process.pid,
72
+ agentVersion: '0.0.0-smoke',
73
+ capabilities: {
74
+ status: true,
75
+ thumbnail: false,
76
+ control: true,
77
+ liveStream: true,
78
+ computerAgent: true,
79
+ taskDispatch: true,
80
+ aiAssist: true,
81
+ aiModel: 'fake-ai'
82
+ }
83
+ });
84
+ writeJsonLine(socket, {
85
+ type: 'status',
86
+ status: {
87
+ uptimeSec: 1,
88
+ totalMem: 2,
89
+ freeMem: 1
90
+ }
91
+ });
92
+
93
+ const devices = await waitFor(() => {
94
+ const current = hub.listDevices();
95
+ return current.length === 1 && current[0].status?.uptimeSec === 1 ? current : null;
96
+ });
97
+
98
+ assert.equal(devices[0].deviceId, 'smoke-device');
99
+ assert.equal(devices[0].connected, true);
100
+ assert.equal(hub.getStatus().deviceCount, 1);
101
+
102
+ const inputCommand = hub.sendInputControl('smoke-device', {
103
+ type: 'noop'
104
+ });
105
+ assert.equal(inputCommand.ok, true);
106
+ writeJsonLine(socket, {
107
+ type: 'command.result',
108
+ commandId: inputCommand.commandId,
109
+ result: {
110
+ input: true,
111
+ handled: true
112
+ }
113
+ });
114
+ const inputDevice = await waitFor(() => {
115
+ const current = hub.listDevices();
116
+ return current[0]?.counters?.commandResultsReceived === 1 ? current[0] : null;
117
+ });
118
+ assert.equal(inputDevice.counters.commandsSent, 1);
119
+
120
+ const thumbnailCommand = hub.requestThumbnail('smoke-device', {
121
+ streamId: 'smoke-thumb',
122
+ maxWidth: 320,
123
+ maxHeight: 180,
124
+ quality: 50
125
+ });
126
+ assert.equal(thumbnailCommand.ok, true);
127
+
128
+ writeJsonLine(socket, {
129
+ type: 'thumbnail.frame',
130
+ commandId: thumbnailCommand.commandId,
131
+ streamId: 'smoke-thumb',
132
+ frameSeq: 1,
133
+ width: 2,
134
+ height: 1,
135
+ mimeType: 'image/png',
136
+ capturedAt: new Date().toISOString(),
137
+ data: 'iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC'
138
+ });
139
+
140
+ const thumbnailDevice = await waitFor(() => {
141
+ const current = hub.listDevices();
142
+ return current[0]?.latestThumbnail?.frameSeq === 1 ? current[0] : null;
143
+ });
144
+ assert.equal(thumbnailDevice.latestThumbnail.streamId, 'smoke-thumb');
145
+ assert.equal(thumbnailDevice.counters.thumbnailFramesReceived, 1);
146
+ const serializedFirstThumbnail = hub.getDeviceThumbnail('smoke-device', { includeDataUrl: false });
147
+ const serializedFirstThumbnailUrl = new URL(serializedFirstThumbnail.framePath, 'http://127.0.0.1');
148
+
149
+ const binaryThumbnailCommand = hub.requestThumbnail('smoke-device', {
150
+ streamId: 'smoke-thumb-binary',
151
+ maxWidth: 320,
152
+ maxHeight: 180,
153
+ quality: 50
154
+ });
155
+ assert.equal(binaryThumbnailCommand.ok, true);
156
+ writeBinaryFrame(socket, {
157
+ frameKind: 'thumbnail',
158
+ commandId: binaryThumbnailCommand.commandId,
159
+ streamId: 'smoke-thumb-binary',
160
+ frameSeq: 4,
161
+ width: 2,
162
+ height: 1,
163
+ mimeType: 'image/png',
164
+ capturedAt: new Date().toISOString(),
165
+ droppedByAgent: 7,
166
+ dirtyRectCount: 18,
167
+ dirtyPixelRatio: 72,
168
+ codec: 'png-delta',
169
+ deltaEncoded: true
170
+ }, smokePngFrame);
171
+
172
+ const binaryThumbnailDevice = await waitFor(() => {
173
+ const current = hub.listDevices();
174
+ return current[0]?.latestThumbnail?.frameSeq === 4 ? current[0] : null;
175
+ });
176
+ assert.equal(binaryThumbnailDevice.latestThumbnail.streamId, 'smoke-thumb-binary');
177
+ assert.equal(binaryThumbnailDevice.latestThumbnail.transport, 'binary');
178
+ assert.equal(binaryThumbnailDevice.latestThumbnail.byteLength, smokePngFrame.length);
179
+ assert.equal(typeof binaryThumbnailDevice.latestThumbnail.contentHash, 'string');
180
+ assert.equal(binaryThumbnailDevice.latestThumbnail.contentHash.length, 16);
181
+ assert.equal(binaryThumbnailDevice.latestThumbnail.sameContentStreak, 1);
182
+ assert.equal(binaryThumbnailDevice.latestThumbnail.droppedByAgent, 7);
183
+ assert.equal(binaryThumbnailDevice.latestThumbnail.dirtyRectCount, 18);
184
+ assert.equal(binaryThumbnailDevice.latestThumbnail.dirtyPixelRatio, 0.72);
185
+ assert.equal(binaryThumbnailDevice.latestThumbnail.codec, 'png-delta');
186
+ assert.equal(binaryThumbnailDevice.latestThumbnail.deltaEncoded, true);
187
+ assert.equal(binaryThumbnailDevice.counters.thumbnailFramesReceived, 2);
188
+ const serializedBinaryThumbnail = hub.getDeviceThumbnail('smoke-device', { includeDataUrl: false });
189
+ assert.equal(serializedBinaryThumbnail.streamId, 'smoke-thumb-binary');
190
+ assert.equal(serializedBinaryThumbnail.frameSeq, 4);
191
+ assert.equal(serializedBinaryThumbnail.contentHash, binaryThumbnailDevice.latestThumbnail.contentHash);
192
+ assert.equal(serializedBinaryThumbnail.droppedByAgent, 7);
193
+ assert.equal(serializedBinaryThumbnail.dirtyRectCount, 18);
194
+ assert.equal(serializedBinaryThumbnail.dirtyPixelRatio, 0.72);
195
+ assert.equal(serializedBinaryThumbnail.codec, 'png-delta');
196
+ assert.equal(serializedBinaryThumbnail.deltaEncoded, true);
197
+ assert.ok(serializedBinaryThumbnail.framePath.includes('/api/remote/devices/smoke-device/thumbnail?'));
198
+ assert.ok(!('dataUrl' in serializedBinaryThumbnail));
199
+ assert.ok(!('payload' in serializedBinaryThumbnail));
200
+ assert.ok(!('accessToken' in serializedBinaryThumbnail));
201
+ const serializedThumbnailUrl = new URL(serializedBinaryThumbnail.framePath, 'http://127.0.0.1');
202
+ const thumbnailPayload = hub.getFramePayload('smoke-device', 'thumbnail', {
203
+ token: serializedThumbnailUrl.searchParams.get('token'),
204
+ frameSeq: serializedThumbnailUrl.searchParams.get('seq'),
205
+ requireToken: true
206
+ });
207
+ assert.equal(thumbnailPayload?.mimeType, 'image/png');
208
+ assert.equal(thumbnailPayload?.byteLength, smokePngFrame.length);
209
+ assert.equal(Buffer.compare(thumbnailPayload.payload, smokePngFrame), 0);
210
+ const retainedThumbnailPayload = hub.getFramePayload('smoke-device', 'thumbnail', {
211
+ token: serializedFirstThumbnailUrl.searchParams.get('token'),
212
+ frameSeq: serializedFirstThumbnailUrl.searchParams.get('seq'),
213
+ requireToken: true
214
+ });
215
+ assert.equal(retainedThumbnailPayload?.frame?.frameSeq, 1);
216
+ assert.equal(retainedThumbnailPayload?.byteLength, smokePngFrame.length);
217
+ assert.equal(Buffer.compare(retainedThumbnailPayload.payload, smokePngFrame), 0);
218
+ assert.equal(hub.getFramePayload('smoke-device', 'thumbnail', {
219
+ token: 'wrong-token',
220
+ frameSeq: 4,
221
+ requireToken: true
222
+ }), null);
223
+
224
+ const liveCommand = hub.startLiveStream('smoke-device', {
225
+ streamId: 'smoke-live',
226
+ fps: 5,
227
+ maxWidth: 320,
228
+ maxHeight: 180,
229
+ quality: 50
230
+ });
231
+ assert.equal(liveCommand.ok, true);
232
+ writeJsonLine(socket, {
233
+ type: 'stream.frame',
234
+ commandId: liveCommand.commandId,
235
+ streamId: 'smoke-live',
236
+ frameSeq: 2,
237
+ width: 2,
238
+ height: 1,
239
+ mimeType: 'image/png',
240
+ mode: 'remote-fast',
241
+ fps: 5,
242
+ capturedAt: new Date().toISOString(),
243
+ data: 'iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC'
244
+ });
245
+
246
+ const liveDevice = await waitFor(() => {
247
+ const current = hub.listDevices();
248
+ return current[0]?.latestLiveFrame?.streamId === 'smoke-live' ? current[0] : null;
249
+ });
250
+ assert.equal(liveDevice.activeLiveStream.active, true);
251
+ assert.equal(liveDevice.latestLiveFrame.mode, 'remote-fast');
252
+ assert.equal(liveDevice.counters.liveFramesReceived, 1);
253
+ const serializedFirstLiveFrame = hub.getDeviceLiveFrame('smoke-device', { includeDataUrl: false });
254
+ const serializedFirstLiveUrl = new URL(serializedFirstLiveFrame.framePath, 'http://127.0.0.1');
255
+
256
+ writeBinaryFrame(socket, {
257
+ frameKind: 'stream',
258
+ commandId: liveCommand.commandId,
259
+ streamId: 'smoke-live',
260
+ frameSeq: 5,
261
+ width: 2,
262
+ height: 1,
263
+ mimeType: 'image/png',
264
+ mode: 'remote-fast',
265
+ fps: 5,
266
+ capturedAt: new Date().toISOString(),
267
+ droppedByAgent: 3,
268
+ dirtyRegionCount: 22,
269
+ dirtyAreaRatio: 0.74,
270
+ encoder: 'png-live-delta',
271
+ deltaFrame: 'true'
272
+ }, smokePngFrame);
273
+
274
+ const binaryLiveDevice = await waitFor(() => {
275
+ const current = hub.listDevices();
276
+ return current[0]?.latestLiveFrame?.frameSeq === 5 ? current[0] : null;
277
+ });
278
+ assert.equal(binaryLiveDevice.latestLiveFrame.streamId, 'smoke-live');
279
+ assert.equal(binaryLiveDevice.latestLiveFrame.transport, 'binary');
280
+ assert.equal(binaryLiveDevice.latestLiveFrame.byteLength, smokePngFrame.length);
281
+ assert.equal(typeof binaryLiveDevice.latestLiveFrame.contentHash, 'string');
282
+ assert.equal(binaryLiveDevice.latestLiveFrame.contentHash.length, 16);
283
+ assert.equal(binaryLiveDevice.latestLiveFrame.sameContentStreak, 1);
284
+ assert.equal(binaryLiveDevice.latestLiveFrame.droppedByAgent, 3);
285
+ assert.equal(binaryLiveDevice.latestLiveFrame.dirtyRectCount, 22);
286
+ assert.equal(binaryLiveDevice.latestLiveFrame.dirtyPixelRatio, 0.74);
287
+ assert.equal(binaryLiveDevice.latestLiveFrame.codec, 'png-live-delta');
288
+ assert.equal(binaryLiveDevice.latestLiveFrame.deltaEncoded, true);
289
+ assert.equal(binaryLiveDevice.counters.liveFramesReceived, 2);
290
+ const serializedBinaryLiveFrame = hub.getDeviceLiveFrame('smoke-device', { includeDataUrl: false });
291
+ assert.equal(serializedBinaryLiveFrame.streamId, 'smoke-live');
292
+ assert.equal(serializedBinaryLiveFrame.frameSeq, 5);
293
+ assert.equal(serializedBinaryLiveFrame.contentHash, binaryLiveDevice.latestLiveFrame.contentHash);
294
+ assert.equal(serializedBinaryLiveFrame.droppedByAgent, 3);
295
+ assert.equal(serializedBinaryLiveFrame.dirtyRectCount, 22);
296
+ assert.equal(serializedBinaryLiveFrame.dirtyPixelRatio, 0.74);
297
+ assert.equal(serializedBinaryLiveFrame.codec, 'png-live-delta');
298
+ assert.equal(serializedBinaryLiveFrame.deltaEncoded, true);
299
+ assert.ok(serializedBinaryLiveFrame.framePath.includes('/api/remote/devices/smoke-device/live/frame?'));
300
+ assert.ok(!('dataUrl' in serializedBinaryLiveFrame));
301
+ assert.ok(!('payload' in serializedBinaryLiveFrame));
302
+ assert.ok(!('accessToken' in serializedBinaryLiveFrame));
303
+ const serializedLiveUrl = new URL(serializedBinaryLiveFrame.framePath, 'http://127.0.0.1');
304
+ const livePayload = hub.getFramePayload('smoke-device', 'live', {
305
+ token: serializedLiveUrl.searchParams.get('token'),
306
+ frameSeq: serializedLiveUrl.searchParams.get('seq'),
307
+ requireToken: true
308
+ });
309
+ assert.equal(livePayload?.mimeType, 'image/png');
310
+ assert.equal(livePayload?.byteLength, smokePngFrame.length);
311
+ assert.equal(Buffer.compare(livePayload.payload, smokePngFrame), 0);
312
+ const retainedLivePayload = hub.getFramePayload('smoke-device', 'live', {
313
+ token: serializedFirstLiveUrl.searchParams.get('token'),
314
+ frameSeq: serializedFirstLiveUrl.searchParams.get('seq'),
315
+ requireToken: true
316
+ });
317
+ assert.equal(retainedLivePayload?.frame?.frameSeq, 2);
318
+ assert.equal(retainedLivePayload?.byteLength, smokePngFrame.length);
319
+ assert.equal(Buffer.compare(retainedLivePayload.payload, smokePngFrame), 0);
320
+ assert.equal(hub.getFramePayload('smoke-device', 'live', {
321
+ token: serializedLiveUrl.searchParams.get('token'),
322
+ frameSeq: 4,
323
+ requireToken: true
324
+ }), null);
325
+
326
+ const staleFrameBefore = liveDevice.counters.liveFramesDropped;
327
+ writeJsonLine(socket, {
328
+ type: 'stream.frame',
329
+ streamId: 'stale-live',
330
+ frameSeq: 3,
331
+ width: 2,
332
+ height: 1,
333
+ mimeType: 'image/png',
334
+ capturedAt: new Date().toISOString(),
335
+ data: 'iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC'
336
+ });
337
+ const staleDropDevice = await waitFor(() => {
338
+ const current = hub.listDevices();
339
+ return current[0]?.counters?.liveFramesDropped > staleFrameBefore ? current[0] : null;
340
+ });
341
+ assert.equal(staleDropDevice.latestLiveFrame.streamId, 'smoke-live');
342
+
343
+ const stopLiveCommand = hub.stopLiveStream('smoke-device', {
344
+ streamId: 'smoke-live'
345
+ });
346
+ assert.equal(stopLiveCommand.ok, true);
347
+ assert.equal(hub.listDevices()[0].activeLiveStream.active, false);
348
+
349
+ const taskCommand = hub.requestAgentTask('smoke-device', {
350
+ instruction: 'Summarize current desktop status for the manager.',
351
+ title: 'Smoke task'
352
+ });
353
+ assert.equal(taskCommand.ok, true);
354
+ writeJsonLine(socket, {
355
+ type: 'command.result',
356
+ commandId: taskCommand.commandId,
357
+ result: {
358
+ kind: 'agent.task',
359
+ taskId: taskCommand.taskId,
360
+ status: 'completed',
361
+ summary: 'Smoke task accepted.',
362
+ completedAt: new Date().toISOString()
363
+ }
364
+ });
365
+
366
+ const taskDevice = await waitFor(() => {
367
+ const current = hub.listDevices();
368
+ return current[0]?.latestTask?.taskId === taskCommand.taskId
369
+ && current[0]?.latestTask?.status === 'completed'
370
+ ? current[0]
371
+ : null;
372
+ });
373
+ assert.equal(taskDevice.latestTask.resultSummary, 'Smoke task accepted.');
374
+ assert.equal(taskDevice.counters.tasksQueued, 1);
375
+ assert.equal(taskDevice.counters.taskResultsReceived, 1);
376
+
377
+ const aiTaskCommand = hub.requestAgentTask('smoke-device', {
378
+ instruction: 'Use AI assist to summarize the smoke test state.',
379
+ title: 'Smoke AI task',
380
+ approvalLevel: 'ai-assist'
381
+ });
382
+ assert.equal(aiTaskCommand.ok, true);
383
+ assert.equal(aiTaskCommand.approvalLevel, 'ai-assist');
384
+ writeJsonLine(socket, {
385
+ type: 'command.result',
386
+ commandId: aiTaskCommand.commandId,
387
+ result: {
388
+ kind: 'agent.task',
389
+ mode: 'ai-assist',
390
+ taskId: aiTaskCommand.taskId,
391
+ status: 'completed',
392
+ summary: 'Smoke AI task accepted.',
393
+ model: 'smoke-ai-model',
394
+ responseId: 'smoke-ai-response',
395
+ completedAt: new Date().toISOString()
396
+ }
397
+ });
398
+
399
+ const aiTaskDevice = await waitFor(() => {
400
+ const current = hub.listDevices();
401
+ return current[0]?.latestTask?.taskId === aiTaskCommand.taskId
402
+ && current[0]?.latestTask?.approvalLevel === 'ai-assist'
403
+ && current[0]?.latestTask?.status === 'completed'
404
+ ? current[0]
405
+ : null;
406
+ });
407
+ assert.equal(aiTaskDevice.latestTask.resultSummary, 'Smoke AI task accepted.');
408
+ assert.equal(aiTaskDevice.latestTask.resultModel, 'smoke-ai-model');
409
+ assert.equal(aiTaskDevice.latestTask.resultResponseId, 'smoke-ai-response');
410
+ assert.equal(aiTaskDevice.counters.tasksQueued, 2);
411
+ assert.equal(aiTaskDevice.counters.taskResultsReceived, 2);
412
+
413
+ const batchTask = hub.requestAgentTaskBatch(['smoke-device', 'missing-smoke-device'], {
414
+ instruction: 'Dispatch a smoke batch to one connected and one missing device.',
415
+ title: 'Smoke batch'
416
+ });
417
+ assert.equal(batchTask.ok, true);
418
+ assert.equal(batchTask.total, 2);
419
+ assert.equal(batchTask.queued, 1);
420
+ assert.ok(batchTask.batchId);
421
+ assert.equal(batchTask.batch?.pending, 1);
422
+ assert.equal(batchTask.batch?.failed, 1);
423
+ assert.equal(batchTask.batch?.status, 'running');
424
+ const acceptedBatchItem = batchTask.results.find(result => result.deviceId === 'smoke-device');
425
+ assert.ok(acceptedBatchItem?.commandId);
426
+ writeJsonLine(socket, {
427
+ type: 'command.result',
428
+ commandId: acceptedBatchItem.commandId,
429
+ result: {
430
+ kind: 'agent.task',
431
+ taskId: acceptedBatchItem.taskId,
432
+ status: 'completed',
433
+ summary: 'Smoke batch item accepted.',
434
+ completedAt: new Date().toISOString()
435
+ }
436
+ });
437
+
438
+ const completedBatch = await waitFor(() => {
439
+ const latest = hub.getLatestTaskBatch();
440
+ return latest?.batchId === batchTask.batchId
441
+ && latest.completed === 1
442
+ && latest.failed === 1
443
+ ? latest
444
+ : null;
445
+ });
446
+ assert.equal(completedBatch.pending, 0);
447
+ assert.equal(completedBatch.status, 'completed-with-failures');
448
+ assert.equal(completedBatch.results.length, 2);
449
+ assert.equal(completedBatch.results.find(result => result.deviceId === 'missing-smoke-device')?.error, 'device-not-found');
450
+
451
+ const timeoutTaskCommand = hub.requestAgentTask('smoke-device', {
452
+ instruction: 'This task intentionally receives no agent response.',
453
+ title: 'Timeout task'
454
+ });
455
+ assert.equal(timeoutTaskCommand.ok, true);
456
+ const timeoutTaskDevice = await waitFor(() => {
457
+ const current = hub.listDevices();
458
+ return current[0]?.latestTask?.taskId === timeoutTaskCommand.taskId
459
+ && current[0]?.latestTask?.status === 'failed'
460
+ ? current[0]
461
+ : null;
462
+ }, 1000);
463
+ assert.equal(timeoutTaskDevice.latestTask.error, 'task-timeout');
464
+ assert.equal(timeoutTaskDevice.latestTask.resultSummary, 'task-timeout');
465
+ assert.equal(timeoutTaskDevice.counters.tasksQueued, 4);
466
+ assert.equal(timeoutTaskDevice.counters.taskResultsReceived, 4);
467
+ assert.equal(timeoutTaskDevice.counters.taskResultsFailed, 1);
468
+ assert.equal(timeoutTaskDevice.counters.taskResultsTimedOut, 1);
469
+ writeJsonLine(socket, {
470
+ type: 'command.result',
471
+ commandId: timeoutTaskCommand.commandId,
472
+ result: {
473
+ kind: 'agent.task',
474
+ taskId: timeoutTaskCommand.taskId,
475
+ status: 'completed',
476
+ summary: 'This late timeout result must be ignored.',
477
+ completedAt: new Date().toISOString()
478
+ }
479
+ });
480
+ await wait(100);
481
+ const lateTimeoutDevice = hub.listDevices()[0];
482
+ assert.equal(lateTimeoutDevice.latestTask.taskId, timeoutTaskCommand.taskId);
483
+ assert.equal(lateTimeoutDevice.latestTask.status, 'failed');
484
+ assert.equal(lateTimeoutDevice.latestTask.error, 'task-timeout');
485
+ assert.equal(lateTimeoutDevice.latestTask.resultSummary, 'task-timeout');
486
+ assert.equal(lateTimeoutDevice.counters.taskResultsReceived, 4);
487
+
488
+ const timeoutBatch = hub.requestAgentTaskBatch(['smoke-device'], {
489
+ instruction: 'This batch item intentionally times out before a late result arrives.',
490
+ title: 'Timeout batch'
491
+ });
492
+ assert.equal(timeoutBatch.ok, true);
493
+ assert.equal(timeoutBatch.total, 1);
494
+ assert.equal(timeoutBatch.queued, 1);
495
+ assert.equal(timeoutBatch.batch?.pending, 1);
496
+ const timeoutBatchItem = timeoutBatch.results.find(result => result.deviceId === 'smoke-device');
497
+ assert.ok(timeoutBatchItem?.commandId);
498
+ assert.ok(timeoutBatchItem?.taskId);
499
+
500
+ const timedOutBatch = await waitFor(() => {
501
+ const latest = hub.getLatestTaskBatch();
502
+ return latest?.batchId === timeoutBatch.batchId
503
+ && latest.timedOut === 1
504
+ && latest.pending === 0
505
+ ? latest
506
+ : null;
507
+ }, 1000);
508
+ assert.equal(timedOutBatch.status, 'completed-with-failures');
509
+ assert.equal(timedOutBatch.failed, 1);
510
+ assert.equal(timedOutBatch.results[0]?.status, 'failed');
511
+ assert.equal(timedOutBatch.results[0]?.error, 'task-timeout');
512
+
513
+ writeJsonLine(socket, {
514
+ type: 'command.result',
515
+ commandId: timeoutBatchItem.commandId,
516
+ result: {
517
+ kind: 'agent.task',
518
+ taskId: timeoutBatchItem.taskId,
519
+ status: 'completed',
520
+ summary: 'This late batch timeout result must be ignored.',
521
+ completedAt: new Date().toISOString()
522
+ }
523
+ });
524
+ await wait(100);
525
+ const lateTimedOutBatch = hub.getLatestTaskBatch();
526
+ const lateTimedOutDevice = hub.listDevices()[0];
527
+ assert.equal(lateTimedOutBatch.batchId, timeoutBatch.batchId);
528
+ assert.equal(lateTimedOutBatch.status, 'completed-with-failures');
529
+ assert.equal(lateTimedOutBatch.completed, 0);
530
+ assert.equal(lateTimedOutBatch.failed, 1);
531
+ assert.equal(lateTimedOutBatch.timedOut, 1);
532
+ assert.equal(lateTimedOutBatch.results[0]?.status, 'failed');
533
+ assert.equal(lateTimedOutBatch.results[0]?.error, 'task-timeout');
534
+ assert.equal(lateTimedOutDevice.latestTask.taskId, timeoutBatchItem.taskId);
535
+ assert.equal(lateTimedOutDevice.latestTask.status, 'failed');
536
+ assert.equal(lateTimedOutDevice.latestTask.error, 'task-timeout');
537
+ assert.equal(lateTimedOutDevice.latestTask.resultSummary, 'task-timeout');
538
+ assert.equal(lateTimedOutDevice.counters.taskResultsReceived, 5);
539
+ assert.equal(lateTimedOutDevice.counters.taskResultsFailed, 2);
540
+ assert.equal(lateTimedOutDevice.counters.taskResultsTimedOut, 2);
541
+
542
+ const staleSessionTask = hub.requestAgentTask('smoke-device', {
543
+ instruction: 'This old session task must not survive reconnect.',
544
+ title: 'Reconnect stale task'
545
+ });
546
+ assert.equal(staleSessionTask.ok, true);
547
+ const oldSessionId = lateTimedOutDevice.sessionId;
548
+ const replacementSocket = net.createConnection({ host: '127.0.0.1', port: status.port });
549
+ replacementSocket.setEncoding('utf8');
550
+ await new Promise((resolve, reject) => {
551
+ replacementSocket.once('connect', resolve);
552
+ replacementSocket.once('error', reject);
553
+ });
554
+ replacementSocket.on('error', () => { });
555
+ writeJsonLine(replacementSocket, {
556
+ type: 'hello',
557
+ pairToken: 'smoke-token',
558
+ deviceId: 'smoke-device',
559
+ deviceName: 'Smoke Device Reconnected',
560
+ hostname: 'smoke-host-reconnected',
561
+ platform: process.platform,
562
+ arch: process.arch,
563
+ pid: process.pid,
564
+ agentVersion: '0.0.0-smoke-reconnected',
565
+ capabilities: {
566
+ status: true,
567
+ thumbnail: false,
568
+ control: false,
569
+ liveStream: true,
570
+ computerAgent: true,
571
+ taskDispatch: true,
572
+ aiAssist: true,
573
+ aiModel: 'fake-ai'
574
+ }
575
+ });
576
+ writeJsonLine(replacementSocket, {
577
+ type: 'status',
578
+ status: {
579
+ uptimeSec: 2,
580
+ totalMem: 4,
581
+ freeMem: 3
582
+ }
583
+ });
584
+
585
+ const reconnectedDevice = await waitFor(() => {
586
+ const current = hub.listDevices();
587
+ return current.length === 1
588
+ && current[0]?.deviceName === 'Smoke Device Reconnected'
589
+ && current[0]?.status?.uptimeSec === 2
590
+ ? current[0]
591
+ : null;
592
+ });
593
+ assert.notEqual(reconnectedDevice.sessionId, oldSessionId);
594
+ assert.equal(reconnectedDevice.connected, true);
595
+ assert.equal(reconnectedDevice.latestTask, null);
596
+ assert.equal(reconnectedDevice.counters.taskResultsReceived, 0);
597
+
598
+ writeJsonLine(socket, {
599
+ type: 'status',
600
+ status: {
601
+ uptimeSec: 999,
602
+ totalMem: 999,
603
+ freeMem: 0
604
+ }
605
+ });
606
+ writeJsonLine(socket, {
607
+ type: 'command.result',
608
+ commandId: staleSessionTask.commandId,
609
+ result: {
610
+ kind: 'agent.task',
611
+ taskId: staleSessionTask.taskId,
612
+ status: 'completed',
613
+ summary: 'This old-session result must not mutate the reconnected device.',
614
+ completedAt: new Date().toISOString()
615
+ }
616
+ });
617
+ await wait(100);
618
+ const staleSessionIgnoredDevice = hub.listDevices()[0];
619
+ assert.equal(staleSessionIgnoredDevice.sessionId, reconnectedDevice.sessionId);
620
+ assert.equal(staleSessionIgnoredDevice.deviceName, 'Smoke Device Reconnected');
621
+ assert.equal(staleSessionIgnoredDevice.status.uptimeSec, 2);
622
+ assert.equal(staleSessionIgnoredDevice.latestTask, null);
623
+ assert.equal(staleSessionIgnoredDevice.counters.taskResultsReceived, 0);
624
+
625
+ socket.destroy();
626
+ replacementSocket.destroy();
627
+ await waitFor(() => hub.listDevices()[0]?.connected === false);
628
+ console.log('RemoteHub smoke OK');
629
+ } finally {
630
+ await hub.close();
631
+ }