@mindexec/cli 0.2.107 → 0.2.109

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.107",
3
+ "version": "0.2.109",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
package/remote-hub.js CHANGED
@@ -15,6 +15,11 @@ const MAX_AGENT_TASK_CHARS = 4000;
15
15
  const MAX_AGENT_TASK_RESULT_CHARS = 3000;
16
16
  const RECENT_TASK_LIMIT = 12;
17
17
  const RECENT_TASK_BATCH_LIMIT = 16;
18
+ const RECENT_FRAME_CACHE_TTL_MS = 5000;
19
+ const RECENT_THUMBNAIL_FRAME_CACHE_LIMIT = 4;
20
+ const RECENT_LIVE_FRAME_CACHE_LIMIT = 24;
21
+ const RECENT_THUMBNAIL_FRAME_CACHE_MAX_BYTES = 3 * 1024 * 1024;
22
+ const RECENT_LIVE_FRAME_CACHE_MAX_BYTES = 8 * 1024 * 1024;
18
23
  const REMOTE_PROTOCOL_VERSION = 1;
19
24
  const MAX_SYNTHETIC_DEVICES = 1000;
20
25
  const DEFAULT_HOST_TARGET_LEASE_MS = 30000;
@@ -374,6 +379,153 @@ function serializeRemoteFrame(frame, deviceId, frameKind, options = {}) {
374
379
  return serialized;
375
380
  }
376
381
 
382
+ function getRecentFrameCache(device, frameKind) {
383
+ if (!device) {
384
+ return null;
385
+ }
386
+
387
+ if (!device.recentFramePayloads) {
388
+ device.recentFramePayloads = {
389
+ thumbnail: [],
390
+ live: []
391
+ };
392
+ }
393
+
394
+ const kind = frameKind === 'thumbnail' ? 'thumbnail' : 'live';
395
+ if (!Array.isArray(device.recentFramePayloads[kind])) {
396
+ device.recentFramePayloads[kind] = [];
397
+ }
398
+
399
+ return device.recentFramePayloads[kind];
400
+ }
401
+
402
+ function getRecentFrameCacheLimit(frameKind) {
403
+ return frameKind === 'thumbnail'
404
+ ? RECENT_THUMBNAIL_FRAME_CACHE_LIMIT
405
+ : RECENT_LIVE_FRAME_CACHE_LIMIT;
406
+ }
407
+
408
+ function getRecentFrameCacheMaxBytes(frameKind) {
409
+ return frameKind === 'thumbnail'
410
+ ? RECENT_THUMBNAIL_FRAME_CACHE_MAX_BYTES
411
+ : RECENT_LIVE_FRAME_CACHE_MAX_BYTES;
412
+ }
413
+
414
+ function pruneRecentFrameCache(cache, frameKind, nowMs = Date.now()) {
415
+ if (!Array.isArray(cache)) {
416
+ return;
417
+ }
418
+
419
+ const limit = getRecentFrameCacheLimit(frameKind);
420
+ const maxBytes = getRecentFrameCacheMaxBytes(frameKind);
421
+ let byteTotal = 0;
422
+ for (let index = cache.length - 1; index >= 0; index -= 1) {
423
+ const entry = cache[index];
424
+ const frame = entry?.frame;
425
+ if (!entry
426
+ || entry.expiresAt <= nowMs
427
+ || !frame
428
+ || !Buffer.isBuffer(frame.payload)
429
+ || !Number.isFinite(Number(frame.frameSeq))
430
+ || !safeString(frame.accessToken, 128)) {
431
+ cache.splice(index, 1);
432
+ }
433
+ }
434
+
435
+ for (let index = 0; index < cache.length; index += 1) {
436
+ const entry = cache[index];
437
+ const byteLength = Number(entry?.byteLength || entry?.frame?.payload?.length || 0) || 0;
438
+ byteTotal += byteLength;
439
+ if (index >= limit || byteTotal > maxBytes) {
440
+ cache.splice(index);
441
+ break;
442
+ }
443
+ }
444
+ }
445
+
446
+ function rememberRecentFramePayload(device, frameKind, frame) {
447
+ if (!device || !frame || !Buffer.isBuffer(frame.payload)) {
448
+ return;
449
+ }
450
+
451
+ const frameSeq = Number(frame.frameSeq);
452
+ const accessToken = safeString(frame.accessToken, 128);
453
+ if (!Number.isFinite(frameSeq) || !accessToken) {
454
+ return;
455
+ }
456
+
457
+ const kind = frameKind === 'thumbnail' ? 'thumbnail' : 'live';
458
+ const cache = getRecentFrameCache(device, kind);
459
+ if (!cache) {
460
+ return;
461
+ }
462
+
463
+ const nowMs = Date.now();
464
+ const normalizedSeq = Math.floor(frameSeq);
465
+ for (let index = cache.length - 1; index >= 0; index -= 1) {
466
+ const existing = cache[index]?.frame;
467
+ if (Number(existing?.frameSeq) === normalizedSeq
468
+ || safeString(existing?.accessToken, 128) === accessToken) {
469
+ cache.splice(index, 1);
470
+ }
471
+ }
472
+
473
+ cache.unshift({
474
+ frame,
475
+ byteLength: frame.payload.length,
476
+ cachedAt: nowMs,
477
+ expiresAt: nowMs + RECENT_FRAME_CACHE_TTL_MS
478
+ });
479
+ pruneRecentFrameCache(cache, kind, nowMs);
480
+ }
481
+
482
+ function isFramePayloadRequestMatch(frame, options = {}) {
483
+ if (!frame || !Buffer.isBuffer(frame.payload)) {
484
+ return false;
485
+ }
486
+
487
+ const requestedSeq = Number(options.frameSeq);
488
+ if (Number.isFinite(requestedSeq) && Math.floor(requestedSeq) !== Number(frame.frameSeq)) {
489
+ return false;
490
+ }
491
+
492
+ const token = safeString(options.token, 128);
493
+ if (token && !timingSafeStringEqual(token, frame.accessToken)) {
494
+ return false;
495
+ }
496
+
497
+ if (options.requireToken === true && !token) {
498
+ return false;
499
+ }
500
+
501
+ return true;
502
+ }
503
+
504
+ function findRecentFramePayload(device, frameKind, options = {}) {
505
+ const kind = frameKind === 'thumbnail' ? 'thumbnail' : 'live';
506
+ const latest = kind === 'thumbnail'
507
+ ? device?.latestThumbnail
508
+ : device?.latestLiveFrame;
509
+ if (isFramePayloadRequestMatch(latest, options)) {
510
+ return latest;
511
+ }
512
+
513
+ const token = safeString(options.token, 128);
514
+ const requestedSeq = Number(options.frameSeq);
515
+ if (!token && !Number.isFinite(requestedSeq)) {
516
+ return null;
517
+ }
518
+
519
+ const cache = getRecentFrameCache(device, kind);
520
+ if (!cache) {
521
+ return null;
522
+ }
523
+
524
+ pruneRecentFrameCache(cache, kind);
525
+ const entry = cache.find(item => isFramePayloadRequestMatch(item?.frame, options));
526
+ return entry?.frame || null;
527
+ }
528
+
377
529
  function serializeDevice(device, options = {}) {
378
530
  if (!device) {
379
531
  return null;
@@ -809,6 +961,7 @@ export function createRemoteHub(options = {}) {
809
961
  delete frame.mode;
810
962
  delete frame.fps;
811
963
  device.latestThumbnail = frame;
964
+ rememberRecentFramePayload(device, 'thumbnail', frame);
812
965
  device.lastSeenAt = frame.receivedAt;
813
966
  device.counters.thumbnailFramesReceived += 1;
814
967
  emitRemoteEvent('RemoteFrameReceived', device, {
@@ -831,6 +984,7 @@ export function createRemoteHub(options = {}) {
831
984
  fps: options.fps || device.activeLiveStream.fps
832
985
  });
833
986
  device.latestLiveFrame = frame;
987
+ rememberRecentFramePayload(device, 'live', frame);
834
988
  device.activeLiveStream.lastFrameAt = frame.receivedAt;
835
989
  device.activeLiveStream.lastFrameSeq = frame.frameSeq;
836
990
  device.activeLiveStream.framesReceived = (device.activeLiveStream.framesReceived || 0) + 1;
@@ -968,6 +1122,10 @@ export function createRemoteHub(options = {}) {
968
1122
  },
969
1123
  latestThumbnail: null,
970
1124
  latestLiveFrame: null,
1125
+ recentFramePayloads: {
1126
+ thumbnail: [],
1127
+ live: []
1128
+ },
971
1129
  activeLiveStream: null,
972
1130
  latestTask,
973
1131
  recentTasks: latestTask ? [latestTask] : [],
@@ -992,6 +1150,7 @@ export function createRemoteHub(options = {}) {
992
1150
  frame.receivedAt = seenAt;
993
1151
  frame.capturedAt = seenAt;
994
1152
  device.latestThumbnail = frame;
1153
+ rememberRecentFramePayload(device, 'thumbnail', frame);
995
1154
  device.counters.thumbnailFramesReceived = 1;
996
1155
  }
997
1156
 
@@ -1074,6 +1233,10 @@ export function createRemoteHub(options = {}) {
1074
1233
  status: {},
1075
1234
  latestThumbnail: null,
1076
1235
  latestLiveFrame: null,
1236
+ recentFramePayloads: {
1237
+ thumbnail: [],
1238
+ live: []
1239
+ },
1077
1240
  activeLiveStream: null,
1078
1241
  latestTask: null,
1079
1242
  recentTasks: [],
@@ -1545,6 +1708,7 @@ export function createRemoteHub(options = {}) {
1545
1708
  payload,
1546
1709
  accessToken: createFrameAccessToken()
1547
1710
  };
1711
+ rememberRecentFramePayload(device, 'thumbnail', device.latestThumbnail);
1548
1712
  device.counters.thumbnailFramesReceived += 1;
1549
1713
  emitRemoteEvent('RemoteFrameReceived', device, {
1550
1714
  streamId: device.latestThumbnail.streamId,
@@ -1615,6 +1779,7 @@ export function createRemoteHub(options = {}) {
1615
1779
  payload,
1616
1780
  accessToken: createFrameAccessToken()
1617
1781
  };
1782
+ rememberRecentFramePayload(device, 'live', device.latestLiveFrame);
1618
1783
  device.activeLiveStream.lastFrameAt = device.lastSeenAt;
1619
1784
  device.activeLiveStream.lastFrameSeq = frameSeq;
1620
1785
  device.activeLiveStream.framesReceived = (device.activeLiveStream.framesReceived || 0) + 1;
@@ -2376,29 +2541,18 @@ export function createRemoteHub(options = {}) {
2376
2541
 
2377
2542
  function getFramePayload(deviceId, frameKind, options = {}) {
2378
2543
  const device = devices.get(String(deviceId || ''));
2379
- const frame = frameKind === 'thumbnail'
2380
- ? device?.latestThumbnail
2381
- : device?.latestLiveFrame;
2382
- if (!frame || !Buffer.isBuffer(frame.payload)) {
2383
- return null;
2384
- }
2385
-
2386
- const requestedSeq = Number(options.frameSeq);
2387
- if (Number.isFinite(requestedSeq) && Math.floor(requestedSeq) !== frame.frameSeq) {
2388
- return null;
2389
- }
2390
-
2391
- const token = safeString(options.token, 128);
2392
- if (token && !timingSafeStringEqual(token, frame.accessToken)) {
2544
+ if (!device) {
2393
2545
  return null;
2394
2546
  }
2395
2547
 
2396
- if (options.requireToken === true && !token) {
2548
+ const kind = frameKind === 'thumbnail' ? 'thumbnail' : 'live';
2549
+ const frame = findRecentFramePayload(device, kind, options);
2550
+ if (!frame) {
2397
2551
  return null;
2398
2552
  }
2399
2553
 
2400
2554
  return {
2401
- frame: serializeRemoteFrame(frame, device.deviceId, frameKind, { includeDataUrl: false }),
2555
+ frame: serializeRemoteFrame(frame, device.deviceId, kind, { includeDataUrl: false }),
2402
2556
  payload: frame.payload,
2403
2557
  mimeType: safeString(frame.mimeType || frame.format || 'application/octet-stream', 120) || 'application/octet-stream',
2404
2558
  byteLength: frame.payload.length
@@ -796,6 +796,9 @@ try {
796
796
  assert.ok(setHostCall);
797
797
  assert.equal(setHostCall.args[0], 'remote-fleet-render-smoke');
798
798
  assert.equal(setHostCall.args[1], true);
799
+ const hostFeedback = bodyView.querySelector('[data-remote-fleet-task-feedback="true"]');
800
+ assert.equal(hostFeedback?.style.display, 'none');
801
+ assert.equal(hostFeedback?.textContent, '');
799
802
 
800
803
  hub.setHostTarget({
801
804
  nodeId: 'remote-fleet-render-smoke',
@@ -143,6 +143,8 @@ try {
143
143
  });
144
144
  assert.equal(thumbnailDevice.latestThumbnail.streamId, 'smoke-thumb');
145
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');
146
148
 
147
149
  const binaryThumbnailCommand = hub.requestThumbnail('smoke-device', {
148
150
  streamId: 'smoke-thumb-binary',
@@ -190,6 +192,14 @@ try {
190
192
  assert.equal(thumbnailPayload?.mimeType, 'image/png');
191
193
  assert.equal(thumbnailPayload?.byteLength, smokePngFrame.length);
192
194
  assert.equal(Buffer.compare(thumbnailPayload.payload, smokePngFrame), 0);
195
+ const retainedThumbnailPayload = hub.getFramePayload('smoke-device', 'thumbnail', {
196
+ token: serializedFirstThumbnailUrl.searchParams.get('token'),
197
+ frameSeq: serializedFirstThumbnailUrl.searchParams.get('seq'),
198
+ requireToken: true
199
+ });
200
+ assert.equal(retainedThumbnailPayload?.frame?.frameSeq, 1);
201
+ assert.equal(retainedThumbnailPayload?.byteLength, smokePngFrame.length);
202
+ assert.equal(Buffer.compare(retainedThumbnailPayload.payload, smokePngFrame), 0);
193
203
  assert.equal(hub.getFramePayload('smoke-device', 'thumbnail', {
194
204
  token: 'wrong-token',
195
205
  frameSeq: 4,
@@ -225,6 +235,8 @@ try {
225
235
  assert.equal(liveDevice.activeLiveStream.active, true);
226
236
  assert.equal(liveDevice.latestLiveFrame.mode, 'remote-fast');
227
237
  assert.equal(liveDevice.counters.liveFramesReceived, 1);
238
+ const serializedFirstLiveFrame = hub.getDeviceLiveFrame('smoke-device', { includeDataUrl: false });
239
+ const serializedFirstLiveUrl = new URL(serializedFirstLiveFrame.framePath, 'http://127.0.0.1');
228
240
 
229
241
  writeBinaryFrame(socket, {
230
242
  frameKind: 'stream',
@@ -267,6 +279,14 @@ try {
267
279
  assert.equal(livePayload?.mimeType, 'image/png');
268
280
  assert.equal(livePayload?.byteLength, smokePngFrame.length);
269
281
  assert.equal(Buffer.compare(livePayload.payload, smokePngFrame), 0);
282
+ const retainedLivePayload = hub.getFramePayload('smoke-device', 'live', {
283
+ token: serializedFirstLiveUrl.searchParams.get('token'),
284
+ frameSeq: serializedFirstLiveUrl.searchParams.get('seq'),
285
+ requireToken: true
286
+ });
287
+ assert.equal(retainedLivePayload?.frame?.frameSeq, 2);
288
+ assert.equal(retainedLivePayload?.byteLength, smokePngFrame.length);
289
+ assert.equal(Buffer.compare(retainedLivePayload.payload, smokePngFrame), 0);
270
290
  assert.equal(hub.getFramePayload('smoke-device', 'live', {
271
291
  token: serializedLiveUrl.searchParams.get('token'),
272
292
  frameSeq: 4,
@@ -5,7 +5,7 @@
5
5
  const DEBUG = false;
6
6
  const FPS_DEBUG = false;
7
7
  const FRAME_PERF_DEBUG = false;
8
- const MINDMAP_CORE_BUILD_ID = '20260616-mdm-simple-monitor-border-v567';
8
+ const MINDMAP_CORE_BUILD_ID = '20260616-mdm-host-feedback-hidden-v568';
9
9
  const CanvasPhase = Object.freeze({
10
10
  Booting: 'booting',
11
11
  BoardFileLoading: 'board-file-loading',
@@ -13145,45 +13145,11 @@
13145
13145
  };
13146
13146
  }
13147
13147
 
13148
- function getRemoteFleetResultRegistryStatus(result) {
13149
- return String(result?.registryStatus ?? result?.RegistryStatus ?? '').trim().toLowerCase();
13150
- }
13151
-
13152
- function getRemoteFleetResultRegistryReason(result) {
13153
- return String(result?.registryReason ?? result?.RegistryReason ?? result?.error ?? result?.Error ?? '').trim();
13154
- }
13155
-
13156
13148
  function isRemoteFleetRegistryMigrationReason(reason) {
13157
13149
  return /registry-table-unavailable|PGRST20[25]|remote_host_targets|set_remote_host_target|clear_remote_host_target/i
13158
13150
  .test(String(reason || ''));
13159
13151
  }
13160
13152
 
13161
- function getRemoteFleetHostFeedbackText(enabled, result) {
13162
- if (enabled !== true) {
13163
- return 'Host target stopped.';
13164
- }
13165
-
13166
- const status = getRemoteFleetResultRegistryStatus(result);
13167
- const reason = getRemoteFleetResultRegistryReason(result);
13168
- if (status === 'account') {
13169
- return 'Host target set.';
13170
- }
13171
-
13172
- if (status === 'local') {
13173
- return 'Host set locally. Account route pending.';
13174
- }
13175
-
13176
- if (status === 'blocked') {
13177
- if (isRemoteFleetRegistryMigrationReason(reason)) {
13178
- return 'Host set locally. Account route needs setup.';
13179
- }
13180
-
13181
- return 'Host route blocked.';
13182
- }
13183
-
13184
- return 'Host target set.';
13185
- }
13186
-
13187
13153
  function createRemoteFleetButton(label, title, action) {
13188
13154
  const button = document.createElement('button');
13189
13155
  button.type = 'button';
@@ -16777,9 +16743,6 @@
16777
16743
  activeButton.disabled = true;
16778
16744
  }
16779
16745
 
16780
- if (!quiet) {
16781
- setTaskFeedback(enabled ? 'Setting host target...' : 'Stopping host target...');
16782
- }
16783
16746
  try {
16784
16747
  const result = await invokeDotNetAsync('SetRemoteFleetHostFromJs', nodeId, enabled === true, renew === true);
16785
16748
  window.RuntimeTrace?.emit?.('remote.hostTarget.result', {
@@ -16804,7 +16767,7 @@
16804
16767
  stopRemoteFleetHostLeaseTimer(nodeId);
16805
16768
  }
16806
16769
  if (isRemoteFleetResultSuccess(result)) {
16807
- setTaskFeedback(getRemoteFleetHostFeedbackText(enabled, result), 'success');
16770
+ setTaskFeedback('');
16808
16771
  } else {
16809
16772
  setTaskFeedback(result?.error || result?.Error || 'Host target update failed.', 'error');
16810
16773
  }
@@ -7,8 +7,8 @@
7
7
  <title>MindExec | Run your ideas as AI task graphs</title>
8
8
  <meta name="description" content="MindExec is an AI execution canvas for solo builders, researchers, developers, and creators. Start with free browser tools, then move serious work into saved MindCanvas projects." />
9
9
  <base href="/" />
10
- <link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260616-mdm-simple-monitor-border-v567" />
11
- <link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-mdm-simple-monitor-border-v567" />
10
+ <link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260616-mdm-host-feedback-hidden-v568" />
11
+ <link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-mdm-host-feedback-hidden-v568" />
12
12
  <!-- ?쇄뼹??Font Awesome (local) ?쇄뼹??-->
13
13
  <link rel="stylesheet" href="_content/MindExecution.Shared/lib/font-awesome/css/all.min.css" />
14
14
  <!-- ?꿎뼯??-->
@@ -579,7 +579,7 @@
579
579
  }
580
580
 
581
581
  const base = '_content/MindExecution.Shared/js/';
582
- const scriptVersion = '20260616-mdm-simple-monitor-border-v567';
582
+ const scriptVersion = '20260616-mdm-host-feedback-hidden-v568';
583
583
  const scriptUrl = (script) => `${base}${script}?v=${scriptVersion}`;
584
584
  console.log(`[Script Loader] Shared JS version: ${scriptVersion}`);
585
585
  const criticalScripts = [
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "lm4IaYy/",
2
+ "version": "jNuNaLgh",
3
3
  "assets": [
4
4
  {
5
5
  "hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
@@ -78,7 +78,7 @@
78
78
  "url": "_content/MindExecution.Shared/js/marked.min.js"
79
79
  },
80
80
  {
81
- "hash": "sha256-xoC/V6bym09C2/PSsXrMNa7JA6xWQTFq5RNRA+nojVs=",
81
+ "hash": "sha256-ob+BxinSzTsfU3sdt2hwn0eM8o3+BREF5TOv7dhc4iY=",
82
82
  "url": "_content/MindExecution.Shared/js/mind-map-core.js"
83
83
  },
84
84
  {
@@ -86,7 +86,7 @@
86
86
  "url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
87
87
  },
88
88
  {
89
- "hash": "sha256-3PsDD8j+YZE2SU7MQscT9DHl+zbP9r2NCCW6p8DzvOA=",
89
+ "hash": "sha256-/o9b8Gda8DwgDGaq9RBwBXb8C8uSvhppHN1cNPpUwO0=",
90
90
  "url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
91
91
  },
92
92
  {
@@ -834,7 +834,7 @@
834
834
  "url": "image-manifest.json"
835
835
  },
836
836
  {
837
- "hash": "sha256-dsplZ/RaSr9+idAqCQA85yTQKyHFVw9MkMndOJpXrAQ=",
837
+ "hash": "sha256-KRqSnu89eeP7M2si+TB94dARTN4vTM73icumlqupLKE=",
838
838
  "url": "index.html"
839
839
  },
840
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: lm4IaYy/ */
1
+ /* Manifest version: jNuNaLgh */
2
2
  // Hosted deployments should prefer the network over stale offline caches.
3
3
  // This service worker immediately clears old Blazor offline caches and unregisters itself.
4
4