@mindexec/cli 0.2.15 → 0.2.17
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 +1 -1
- package/remote-hub.js +350 -0
- package/scripts/remote-fleet-render-smoke.mjs +35 -2
- package/scripts/remote-hub-scale-smoke.mjs +14 -7
- package/scripts/remote-hub-smoke.mjs +59 -1
- package/server.js +13 -14
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +93 -2
- package/wwwroot/_framework/MindExecution.Core.ckgjbdvvxl.dll +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.lqglgq2jmo.dll → MindExecution.Kernel.lm7kgq8l8h.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.2jqptn6ylw.dll → MindExecution.Plugins.Admin.k085kt3dls.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.8hzwity3fl.dll → MindExecution.Plugins.Business.dsz9uwkxih.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.gc38fmee62.dll → MindExecution.Plugins.Concept.6gcazss69r.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.hr2p0grenr.dll → MindExecution.Plugins.Directory.ufbriq6js4.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.ivqd47y3cx.dll → MindExecution.Plugins.PlanMaster.zqc0kv9n5d.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.tpgm4p80hh.dll → MindExecution.Plugins.YouTube.5ojg8vbf9w.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.2r4r7zx1ek.dll → MindExecution.Shared.ahf7j9dymw.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Web.stbecd1tk9.dll → MindExecution.Web.6i9vy7xy1k.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +21 -21
- package/wwwroot/index.html +1 -1
- package/wwwroot/service-worker-assets.js +24 -24
- package/wwwroot/service-worker.js +1 -1
- package/wwwroot/_framework/MindExecution.Core.q469sducjw.dll +0 -0
package/package.json
CHANGED
package/remote-hub.js
CHANGED
|
@@ -5,12 +5,14 @@ import crypto from 'crypto';
|
|
|
5
5
|
const DEFAULT_REMOTE_HUB_PORT = 5197;
|
|
6
6
|
const DEFAULT_REMOTE_HUB_HOST = '127.0.0.1';
|
|
7
7
|
const DEFAULT_HEARTBEAT_MS = 5000;
|
|
8
|
+
const DEFAULT_AGENT_TASK_TIMEOUT_MS = 120000;
|
|
8
9
|
const MAX_LINE_CHARS = 4 * 1024 * 1024;
|
|
9
10
|
const MAX_THUMBNAIL_BASE64_CHARS = 3 * 1024 * 1024;
|
|
10
11
|
const MAX_STREAM_BASE64_CHARS = 3 * 1024 * 1024;
|
|
11
12
|
const MAX_AGENT_TASK_CHARS = 4000;
|
|
12
13
|
const MAX_AGENT_TASK_RESULT_CHARS = 3000;
|
|
13
14
|
const RECENT_TASK_LIMIT = 12;
|
|
15
|
+
const RECENT_TASK_BATCH_LIMIT = 16;
|
|
14
16
|
const REMOTE_PROTOCOL_VERSION = 1;
|
|
15
17
|
const MAX_SYNTHETIC_DEVICES = 1000;
|
|
16
18
|
const SYNTHETIC_FRAME_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC';
|
|
@@ -79,6 +81,7 @@ function createDeviceCounters(overrides = {}) {
|
|
|
79
81
|
tasksQueued: 0,
|
|
80
82
|
taskResultsReceived: 0,
|
|
81
83
|
taskResultsFailed: 0,
|
|
84
|
+
taskResultsTimedOut: 0,
|
|
82
85
|
...overrides
|
|
83
86
|
};
|
|
84
87
|
}
|
|
@@ -152,6 +155,31 @@ function serializeDevice(device) {
|
|
|
152
155
|
};
|
|
153
156
|
}
|
|
154
157
|
|
|
158
|
+
function serializeTaskBatch(batch) {
|
|
159
|
+
if (!batch) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
batchId: batch.batchId,
|
|
165
|
+
title: batch.title,
|
|
166
|
+
instructionPreview: batch.instructionPreview,
|
|
167
|
+
approvalLevel: batch.approvalLevel,
|
|
168
|
+
status: batch.status,
|
|
169
|
+
requestedAt: batch.requestedAt,
|
|
170
|
+
updatedAt: batch.updatedAt,
|
|
171
|
+
total: batch.total,
|
|
172
|
+
queued: batch.queued,
|
|
173
|
+
pending: batch.pending,
|
|
174
|
+
completed: batch.completed,
|
|
175
|
+
failed: batch.failed,
|
|
176
|
+
timedOut: batch.timedOut,
|
|
177
|
+
results: Array.isArray(batch.results)
|
|
178
|
+
? batch.results.map(item => ({ ...item }))
|
|
179
|
+
: []
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
155
183
|
function writeJsonLine(socket, payload) {
|
|
156
184
|
if (!socket || socket.destroyed) {
|
|
157
185
|
return false;
|
|
@@ -172,11 +200,17 @@ export function createRemoteHub(options = {}) {
|
|
|
172
200
|
const host = safeString(env.REMOTE_HUB_HOST || DEFAULT_REMOTE_HUB_HOST, 128);
|
|
173
201
|
const requestedPort = normalizePort(env.REMOTE_HUB_PORT || DEFAULT_REMOTE_HUB_PORT);
|
|
174
202
|
const heartbeatMs = clampNumber(env.REMOTE_HUB_HEARTBEAT_MS, 1000, 60000, DEFAULT_HEARTBEAT_MS);
|
|
203
|
+
const taskTimeoutMs = clampNumber(
|
|
204
|
+
options.taskTimeoutMs ?? env.REMOTE_HUB_TASK_TIMEOUT_MS,
|
|
205
|
+
50,
|
|
206
|
+
30 * 60 * 1000,
|
|
207
|
+
DEFAULT_AGENT_TASK_TIMEOUT_MS);
|
|
175
208
|
const pairToken = safeString(
|
|
176
209
|
env.REMOTE_HUB_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(6).toString('hex'),
|
|
177
210
|
256);
|
|
178
211
|
|
|
179
212
|
const devices = new Map();
|
|
213
|
+
const taskBatches = new Map();
|
|
180
214
|
const sockets = new Map();
|
|
181
215
|
const allSockets = new Set();
|
|
182
216
|
let server = null;
|
|
@@ -194,6 +228,7 @@ export function createRemoteHub(options = {}) {
|
|
|
194
228
|
protocol: 'tcp-jsonl',
|
|
195
229
|
protocolVersion: REMOTE_PROTOCOL_VERSION,
|
|
196
230
|
heartbeatMs,
|
|
231
|
+
taskTimeoutMs,
|
|
197
232
|
agentPackage: '@mindexec/remote',
|
|
198
233
|
agentEndpoint: `${host}:${boundPort || requestedPort}`,
|
|
199
234
|
pairToken: includeSecrets ? pairToken : undefined,
|
|
@@ -221,6 +256,17 @@ export function createRemoteHub(options = {}) {
|
|
|
221
256
|
});
|
|
222
257
|
}
|
|
223
258
|
|
|
259
|
+
function listTaskBatches() {
|
|
260
|
+
return [...taskBatches.values()]
|
|
261
|
+
.map(serializeTaskBatch)
|
|
262
|
+
.filter(Boolean)
|
|
263
|
+
.sort((left, right) => String(right.requestedAt || '').localeCompare(String(left.requestedAt || '')));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function getLatestTaskBatch() {
|
|
267
|
+
return listTaskBatches()[0] || null;
|
|
268
|
+
}
|
|
269
|
+
|
|
224
270
|
function emitRemoteEvent(type, device = null, extra = {}) {
|
|
225
271
|
emitEvent(type, {
|
|
226
272
|
...extra,
|
|
@@ -307,6 +353,7 @@ export function createRemoteHub(options = {}) {
|
|
|
307
353
|
}
|
|
308
354
|
|
|
309
355
|
existing.lastDisconnectReason = 'replaced-by-new-session';
|
|
356
|
+
failAllPendingTasks(existing, 'replaced-by-new-session');
|
|
310
357
|
writeJsonLine(existing.socket, {
|
|
311
358
|
type: 'disconnect',
|
|
312
359
|
reason: 'replaced-by-new-session',
|
|
@@ -427,6 +474,7 @@ export function createRemoteHub(options = {}) {
|
|
|
427
474
|
latestTask,
|
|
428
475
|
recentTasks: latestTask ? [latestTask] : [],
|
|
429
476
|
pendingTaskCommands: new Map(),
|
|
477
|
+
pendingTaskTimers: new Map(),
|
|
430
478
|
counters: createDeviceCounters({
|
|
431
479
|
messagesReceived: 1,
|
|
432
480
|
statusReceived: 1,
|
|
@@ -532,6 +580,7 @@ export function createRemoteHub(options = {}) {
|
|
|
532
580
|
latestTask: null,
|
|
533
581
|
recentTasks: [],
|
|
534
582
|
pendingTaskCommands: new Map(),
|
|
583
|
+
pendingTaskTimers: new Map(),
|
|
535
584
|
synthetic: false,
|
|
536
585
|
counters: createDeviceCounters({ messagesReceived: 1 })
|
|
537
586
|
};
|
|
@@ -573,6 +622,7 @@ export function createRemoteHub(options = {}) {
|
|
|
573
622
|
device.activeLiveStream.stoppedAt = device.disconnectedAt;
|
|
574
623
|
device.activeLiveStream.stopReason = reason;
|
|
575
624
|
}
|
|
625
|
+
failAllPendingTasks(device, 'device-disconnected');
|
|
576
626
|
logWarn('remote', `device disconnected ${device.deviceName} (${deviceId}): ${reason}`);
|
|
577
627
|
emitRemoteEvent('RemoteDeviceDisconnected', device, { reason });
|
|
578
628
|
}
|
|
@@ -601,6 +651,241 @@ export function createRemoteHub(options = {}) {
|
|
|
601
651
|
return task;
|
|
602
652
|
}
|
|
603
653
|
|
|
654
|
+
function trimTaskBatches() {
|
|
655
|
+
const ordered = [...taskBatches.values()]
|
|
656
|
+
.sort((left, right) => String(right.requestedAt || '').localeCompare(String(left.requestedAt || '')));
|
|
657
|
+
for (const batch of ordered.slice(RECENT_TASK_BATCH_LIMIT)) {
|
|
658
|
+
taskBatches.delete(batch.batchId);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function beginTaskBatch(deviceIds, options = {}) {
|
|
663
|
+
const targets = [...new Set((deviceIds || [])
|
|
664
|
+
.map(deviceId => safeString(deviceId, 128))
|
|
665
|
+
.filter(Boolean))]
|
|
666
|
+
.slice(0, 500);
|
|
667
|
+
const now = new Date().toISOString();
|
|
668
|
+
const instruction = safeText(options.instruction, MAX_AGENT_TASK_CHARS);
|
|
669
|
+
const batchId = safeString(options.batchId, 128) || crypto.randomUUID();
|
|
670
|
+
const title = safeString(options.title, 120)
|
|
671
|
+
|| safeString(instruction.split(/\r?\n/)[0], 120)
|
|
672
|
+
|| 'Remote task batch';
|
|
673
|
+
const batch = {
|
|
674
|
+
batchId,
|
|
675
|
+
title,
|
|
676
|
+
instructionPreview: safeText(instruction, 320),
|
|
677
|
+
approvalLevel: normalizeApprovalLevel(options.approvalLevel),
|
|
678
|
+
status: targets.length > 0 ? 'running' : 'failed',
|
|
679
|
+
requestedAt: now,
|
|
680
|
+
updatedAt: now,
|
|
681
|
+
total: targets.length,
|
|
682
|
+
queued: 0,
|
|
683
|
+
pending: 0,
|
|
684
|
+
completed: 0,
|
|
685
|
+
failed: targets.length > 0 ? 0 : 1,
|
|
686
|
+
timedOut: 0,
|
|
687
|
+
results: targets.map(deviceId => ({
|
|
688
|
+
deviceId,
|
|
689
|
+
ok: false,
|
|
690
|
+
status: 'targeted',
|
|
691
|
+
error: '',
|
|
692
|
+
commandId: '',
|
|
693
|
+
taskId: '',
|
|
694
|
+
approvalLevel: normalizeApprovalLevel(options.approvalLevel),
|
|
695
|
+
updatedAt: now
|
|
696
|
+
}))
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
taskBatches.set(batchId, batch);
|
|
700
|
+
trimTaskBatches();
|
|
701
|
+
return batch;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function getTaskBatchItem(batch, deviceId) {
|
|
705
|
+
if (!batch) {
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const id = safeString(deviceId, 128);
|
|
710
|
+
if (!id) {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
let item = batch.results.find(result => result.deviceId === id);
|
|
715
|
+
if (!item) {
|
|
716
|
+
item = {
|
|
717
|
+
deviceId: id,
|
|
718
|
+
ok: false,
|
|
719
|
+
status: 'targeted',
|
|
720
|
+
error: '',
|
|
721
|
+
commandId: '',
|
|
722
|
+
taskId: '',
|
|
723
|
+
approvalLevel: batch.approvalLevel,
|
|
724
|
+
updatedAt: new Date().toISOString()
|
|
725
|
+
};
|
|
726
|
+
batch.results.push(item);
|
|
727
|
+
batch.total = batch.results.length;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return item;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function recalculateTaskBatch(batch) {
|
|
734
|
+
if (!batch) {
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const results = Array.isArray(batch.results) ? batch.results : [];
|
|
739
|
+
batch.total = results.length;
|
|
740
|
+
batch.queued = results.filter(item => item.ok === true).length;
|
|
741
|
+
batch.completed = results.filter(item => item.status === 'completed').length;
|
|
742
|
+
batch.failed = results.filter(item => item.status === 'failed' || (item.ok === false && !!item.error)).length;
|
|
743
|
+
batch.timedOut = results.filter(item => item.error === 'task-timeout').length;
|
|
744
|
+
batch.pending = results.filter(item =>
|
|
745
|
+
item.ok === true
|
|
746
|
+
&& item.status !== 'completed'
|
|
747
|
+
&& item.status !== 'failed').length;
|
|
748
|
+
batch.updatedAt = new Date().toISOString();
|
|
749
|
+
|
|
750
|
+
if (batch.total === 0) {
|
|
751
|
+
batch.status = 'failed';
|
|
752
|
+
} else if (batch.completed + batch.failed >= batch.total) {
|
|
753
|
+
batch.status = batch.failed > 0 ? 'completed-with-failures' : 'completed';
|
|
754
|
+
} else if (batch.queued > 0) {
|
|
755
|
+
batch.status = 'running';
|
|
756
|
+
} else {
|
|
757
|
+
batch.status = 'failed';
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return batch;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function syncTaskBatchFromTask(device, task) {
|
|
764
|
+
const batchId = safeString(task?.batchId, 128);
|
|
765
|
+
if (!batchId) {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const batch = taskBatches.get(batchId);
|
|
770
|
+
if (!batch) {
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const item = getTaskBatchItem(batch, device?.deviceId);
|
|
775
|
+
if (!item) {
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
item.ok = true;
|
|
780
|
+
item.status = safeString(task.status, 40) || 'queued';
|
|
781
|
+
item.error = safeString(task.error, 500);
|
|
782
|
+
item.commandId = safeString(task.commandId, 128);
|
|
783
|
+
item.taskId = safeString(task.taskId, 128);
|
|
784
|
+
item.approvalLevel = normalizeApprovalLevel(task.approvalLevel || batch.approvalLevel);
|
|
785
|
+
item.updatedAt = task.updatedAt || task.completedAt || task.sentAt || new Date().toISOString();
|
|
786
|
+
return recalculateTaskBatch(batch);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function syncTaskBatchDispatchFailure(batchId, deviceId, error, extra = {}) {
|
|
790
|
+
const batch = taskBatches.get(safeString(batchId, 128));
|
|
791
|
+
if (!batch) {
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const item = getTaskBatchItem(batch, deviceId);
|
|
796
|
+
if (!item) {
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
item.ok = false;
|
|
801
|
+
item.status = 'failed';
|
|
802
|
+
item.error = safeString(error, 500) || 'task-dispatch-failed';
|
|
803
|
+
item.commandId = safeString(extra.commandId, 128);
|
|
804
|
+
item.taskId = safeString(extra.taskId, 128);
|
|
805
|
+
item.approvalLevel = normalizeApprovalLevel(extra.approvalLevel || batch.approvalLevel);
|
|
806
|
+
item.updatedAt = new Date().toISOString();
|
|
807
|
+
return recalculateTaskBatch(batch);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function clearTaskTimeout(device, commandId) {
|
|
811
|
+
const key = safeString(commandId, 128);
|
|
812
|
+
if (!device?.pendingTaskTimers || !key) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const timer = device.pendingTaskTimers.get(key);
|
|
817
|
+
if (timer) {
|
|
818
|
+
clearTimeout(timer);
|
|
819
|
+
}
|
|
820
|
+
device.pendingTaskTimers.delete(key);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function failPendingTask(device, commandId, error = 'task-timeout') {
|
|
824
|
+
const key = safeString(commandId, 128);
|
|
825
|
+
if (!device?.pendingTaskCommands || !key) {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const task = device.pendingTaskCommands.get(key);
|
|
830
|
+
if (!task) {
|
|
831
|
+
clearTaskTimeout(device, key);
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const now = new Date().toISOString();
|
|
836
|
+
clearTaskTimeout(device, key);
|
|
837
|
+
task.status = 'failed';
|
|
838
|
+
task.updatedAt = now;
|
|
839
|
+
task.completedAt = now;
|
|
840
|
+
task.error = safeString(error, 500) || 'task-timeout';
|
|
841
|
+
task.resultSummary = task.error;
|
|
842
|
+
task.resultKind = task.resultKind || 'agent-task';
|
|
843
|
+
device.latestTask = task;
|
|
844
|
+
device.pendingTaskCommands.delete(key);
|
|
845
|
+
device.counters.taskResultsReceived += 1;
|
|
846
|
+
device.counters.taskResultsFailed += 1;
|
|
847
|
+
if (task.error === 'task-timeout') {
|
|
848
|
+
device.counters.taskResultsTimedOut += 1;
|
|
849
|
+
}
|
|
850
|
+
syncTaskBatchFromTask(device, task);
|
|
851
|
+
emitRemoteEvent('RemoteTaskResult', device, {
|
|
852
|
+
commandId: key,
|
|
853
|
+
taskId: task.taskId,
|
|
854
|
+
status: task.status,
|
|
855
|
+
error: task.error
|
|
856
|
+
});
|
|
857
|
+
return task;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function failAllPendingTasks(device, error = 'device-disconnected') {
|
|
861
|
+
if (!device?.pendingTaskCommands) {
|
|
862
|
+
return 0;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const commandIds = [...device.pendingTaskCommands.keys()];
|
|
866
|
+
let failed = 0;
|
|
867
|
+
for (const commandId of commandIds) {
|
|
868
|
+
if (failPendingTask(device, commandId, error)) {
|
|
869
|
+
failed += 1;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return failed;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function scheduleTaskTimeout(device, task) {
|
|
876
|
+
const commandId = safeString(task?.commandId, 128);
|
|
877
|
+
if (!device?.pendingTaskTimers || !commandId) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
clearTaskTimeout(device, commandId);
|
|
882
|
+
const timer = setTimeout(() => {
|
|
883
|
+
failPendingTask(device, commandId, 'task-timeout');
|
|
884
|
+
}, taskTimeoutMs);
|
|
885
|
+
timer.unref?.();
|
|
886
|
+
device.pendingTaskTimers.set(commandId, timer);
|
|
887
|
+
}
|
|
888
|
+
|
|
604
889
|
function summarizeTaskResult(result) {
|
|
605
890
|
if (result && typeof result === 'object') {
|
|
606
891
|
return safeText(
|
|
@@ -634,6 +919,7 @@ export function createRemoteHub(options = {}) {
|
|
|
634
919
|
const status = error
|
|
635
920
|
? 'failed'
|
|
636
921
|
: safeString(result?.status, 40) || 'completed';
|
|
922
|
+
clearTaskTimeout(device, commandId);
|
|
637
923
|
task.status = status;
|
|
638
924
|
task.updatedAt = now;
|
|
639
925
|
task.completedAt = safeString(result?.completedAt, 80) || now;
|
|
@@ -648,6 +934,7 @@ export function createRemoteHub(options = {}) {
|
|
|
648
934
|
if (error) {
|
|
649
935
|
device.counters.taskResultsFailed += 1;
|
|
650
936
|
}
|
|
937
|
+
syncTaskBatchFromTask(device, task);
|
|
651
938
|
|
|
652
939
|
return task;
|
|
653
940
|
}
|
|
@@ -903,6 +1190,7 @@ export function createRemoteHub(options = {}) {
|
|
|
903
1190
|
|
|
904
1191
|
async function close() {
|
|
905
1192
|
for (const device of devices.values()) {
|
|
1193
|
+
failAllPendingTasks(device, 'hub-shutdown');
|
|
906
1194
|
if (device.socket && !device.socket.destroyed) {
|
|
907
1195
|
writeJsonLine(device.socket, { type: 'disconnect', reason: 'hub-shutdown' });
|
|
908
1196
|
device.socket.destroy();
|
|
@@ -958,6 +1246,7 @@ export function createRemoteHub(options = {}) {
|
|
|
958
1246
|
device.activeLiveStream.stoppedAt = device.disconnectedAt;
|
|
959
1247
|
device.activeLiveStream.stopReason = reason;
|
|
960
1248
|
}
|
|
1249
|
+
failAllPendingTasks(device, 'device-disconnected');
|
|
961
1250
|
emitRemoteEvent('RemoteDeviceDisconnected', device, { reason, synthetic: true });
|
|
962
1251
|
return true;
|
|
963
1252
|
}
|
|
@@ -1020,6 +1309,10 @@ export function createRemoteHub(options = {}) {
|
|
|
1020
1309
|
|
|
1021
1310
|
function requestAgentTask(deviceId, options = {}) {
|
|
1022
1311
|
const device = devices.get(String(deviceId || ''));
|
|
1312
|
+
if (!device) {
|
|
1313
|
+
return { ok: false, error: 'device-not-found' };
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1023
1316
|
if (device?.synthetic === true && device.connected) {
|
|
1024
1317
|
const instruction = safeText(options.instruction, MAX_AGENT_TASK_CHARS);
|
|
1025
1318
|
if (!instruction) {
|
|
@@ -1038,6 +1331,7 @@ export function createRemoteHub(options = {}) {
|
|
|
1038
1331
|
|| safeString(instruction.split(/\r?\n/)[0], 120)
|
|
1039
1332
|
|| 'Remote task';
|
|
1040
1333
|
const task = {
|
|
1334
|
+
batchId: safeString(options.batchId, 128),
|
|
1041
1335
|
taskId,
|
|
1042
1336
|
commandId,
|
|
1043
1337
|
title,
|
|
@@ -1068,6 +1362,7 @@ export function createRemoteHub(options = {}) {
|
|
|
1068
1362
|
device.lastSeenAt = now;
|
|
1069
1363
|
rememberDeviceTask(device, task);
|
|
1070
1364
|
device.pendingTaskCommands.delete(commandId);
|
|
1365
|
+
syncTaskBatchFromTask(device, task);
|
|
1071
1366
|
emitRemoteEvent('RemoteTaskQueued', device, {
|
|
1072
1367
|
commandId,
|
|
1073
1368
|
taskId,
|
|
@@ -1106,6 +1401,7 @@ export function createRemoteHub(options = {}) {
|
|
|
1106
1401
|
|| safeString(instruction.split(/\r?\n/)[0], 120)
|
|
1107
1402
|
|| 'Remote task';
|
|
1108
1403
|
const task = {
|
|
1404
|
+
batchId: safeString(options.batchId, 128),
|
|
1109
1405
|
taskId,
|
|
1110
1406
|
commandId,
|
|
1111
1407
|
title,
|
|
@@ -1145,6 +1441,8 @@ export function createRemoteHub(options = {}) {
|
|
|
1145
1441
|
device.counters.commandsSent += 1;
|
|
1146
1442
|
device.counters.tasksQueued += 1;
|
|
1147
1443
|
rememberDeviceTask(device, task);
|
|
1444
|
+
syncTaskBatchFromTask(device, task);
|
|
1445
|
+
scheduleTaskTimeout(device, task);
|
|
1148
1446
|
emitRemoteEvent('RemoteTaskQueued', device, {
|
|
1149
1447
|
commandId,
|
|
1150
1448
|
taskId,
|
|
@@ -1154,6 +1452,55 @@ export function createRemoteHub(options = {}) {
|
|
|
1154
1452
|
return { ok: true, commandId, taskId, approvalLevel };
|
|
1155
1453
|
}
|
|
1156
1454
|
|
|
1455
|
+
function requestAgentTaskBatch(deviceIds, options = {}) {
|
|
1456
|
+
const targets = [...new Set((deviceIds || [])
|
|
1457
|
+
.map(deviceId => safeString(deviceId, 128))
|
|
1458
|
+
.filter(Boolean))]
|
|
1459
|
+
.slice(0, 500);
|
|
1460
|
+
|
|
1461
|
+
if (targets.length === 0) {
|
|
1462
|
+
return {
|
|
1463
|
+
ok: false,
|
|
1464
|
+
error: 'no-target-devices',
|
|
1465
|
+
total: 0,
|
|
1466
|
+
queued: 0,
|
|
1467
|
+
approvalLevel: normalizeApprovalLevel(options.approvalLevel),
|
|
1468
|
+
results: []
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
const batch = beginTaskBatch(targets, options);
|
|
1473
|
+
const results = targets.map(deviceId => {
|
|
1474
|
+
const result = requestAgentTask(deviceId, {
|
|
1475
|
+
...options,
|
|
1476
|
+
batchId: batch.batchId
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
if (result.ok !== true) {
|
|
1480
|
+
syncTaskBatchDispatchFailure(batch.batchId, deviceId, result.error, result);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
return {
|
|
1484
|
+
deviceId,
|
|
1485
|
+
...result
|
|
1486
|
+
};
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
const queued = results.filter(result => result.ok === true).length;
|
|
1490
|
+
const updatedBatch = taskBatches.get(batch.batchId) || batch;
|
|
1491
|
+
recalculateTaskBatch(updatedBatch);
|
|
1492
|
+
return {
|
|
1493
|
+
ok: queued > 0,
|
|
1494
|
+
batchId: batch.batchId,
|
|
1495
|
+
total: targets.length,
|
|
1496
|
+
queued,
|
|
1497
|
+
approvalLevel: batch.approvalLevel,
|
|
1498
|
+
results,
|
|
1499
|
+
batch: serializeTaskBatch(updatedBatch),
|
|
1500
|
+
error: queued > 0 ? undefined : 'no-task-queued'
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1157
1504
|
function requestThumbnail(deviceId, options = {}) {
|
|
1158
1505
|
return sendCommand(deviceId, {
|
|
1159
1506
|
command: 'thumbnail.capture',
|
|
@@ -1329,9 +1676,12 @@ export function createRemoteHub(options = {}) {
|
|
|
1329
1676
|
close,
|
|
1330
1677
|
getStatus,
|
|
1331
1678
|
listDevices,
|
|
1679
|
+
listTaskBatches,
|
|
1680
|
+
getLatestTaskBatch,
|
|
1332
1681
|
disconnectDevice,
|
|
1333
1682
|
sendCommand,
|
|
1334
1683
|
requestAgentTask,
|
|
1684
|
+
requestAgentTaskBatch,
|
|
1335
1685
|
requestThumbnail,
|
|
1336
1686
|
startLiveStream,
|
|
1337
1687
|
stopLiveStream,
|
|
@@ -412,7 +412,7 @@ function wait(ms = 0) {
|
|
|
412
412
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
-
function buildMonitorNode(devices, hubStatus) {
|
|
415
|
+
function buildMonitorNode(devices, hubStatus, latestTaskBatch = null) {
|
|
416
416
|
const connected = devices.filter(device => device.Connected).length;
|
|
417
417
|
const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5197';
|
|
418
418
|
const pairToken = hubStatus.pairToken || 'render-smoke-token';
|
|
@@ -433,6 +433,7 @@ function buildMonitorNode(devices, hubStatus) {
|
|
|
433
433
|
RemoteFleetConnectedDeviceCount: String(connected),
|
|
434
434
|
RemoteFleetCanvasPagination: 'none',
|
|
435
435
|
RemoteFleetDevicesJson: JSON.stringify(devices),
|
|
436
|
+
RemoteFleetLatestTaskBatchJson: latestTaskBatch ? JSON.stringify(latestTaskBatch) : '',
|
|
436
437
|
RemoteFleetLastError: ''
|
|
437
438
|
}
|
|
438
439
|
};
|
|
@@ -531,6 +532,31 @@ try {
|
|
|
531
532
|
const aiCount = devices.filter(device => device.Connected && device.AiAssistEnabled).length;
|
|
532
533
|
const focusedDevice = devices.find(device => device.Connected);
|
|
533
534
|
assert.ok(focusedDevice);
|
|
535
|
+
focusedDevice.LatestTaskId = 'render-smoke-timeout-task';
|
|
536
|
+
focusedDevice.LatestTaskCommandId = 'render-smoke-timeout-command';
|
|
537
|
+
focusedDevice.LatestTaskTitle = 'Timed out smoke task';
|
|
538
|
+
focusedDevice.LatestTaskInstructionPreview = 'This synthetic task timed out before the agent replied.';
|
|
539
|
+
focusedDevice.LatestTaskStatus = 'failed';
|
|
540
|
+
focusedDevice.LatestTaskApprovalLevel = 'task-only';
|
|
541
|
+
focusedDevice.LatestTaskUpdatedAt = new Date().toISOString();
|
|
542
|
+
focusedDevice.LatestTaskError = 'task-timeout';
|
|
543
|
+
focusedDevice.LatestTaskResultSummary = '';
|
|
544
|
+
const latestTaskBatch = {
|
|
545
|
+
batchId: 'render-smoke-batch',
|
|
546
|
+
title: 'Render smoke batch',
|
|
547
|
+
instructionPreview: 'Render smoke batch instruction preview.',
|
|
548
|
+
approvalLevel: 'ai-assist',
|
|
549
|
+
status: 'running',
|
|
550
|
+
requestedAt: new Date(Date.now() - 2500).toISOString(),
|
|
551
|
+
updatedAt: new Date().toISOString(),
|
|
552
|
+
total: 210,
|
|
553
|
+
queued: 210,
|
|
554
|
+
pending: 1,
|
|
555
|
+
completed: 208,
|
|
556
|
+
failed: 1,
|
|
557
|
+
timedOut: 1,
|
|
558
|
+
results: []
|
|
559
|
+
};
|
|
534
560
|
|
|
535
561
|
const { manager, document } = await loadCss3DManager();
|
|
536
562
|
assert.equal(typeof manager?.renderRemoteFleetMonitorForTest, 'function');
|
|
@@ -564,7 +590,7 @@ try {
|
|
|
564
590
|
bodyView.dataset.remoteFleetFocusDeviceId = focusedDevice.DeviceId;
|
|
565
591
|
document.body.appendChild(bodyView);
|
|
566
592
|
|
|
567
|
-
manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true })));
|
|
593
|
+
manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch));
|
|
568
594
|
|
|
569
595
|
let cards = bodyView.querySelectorAll('article[data-device-id]');
|
|
570
596
|
assert.equal(cards.length, SYNTHETIC_COUNT);
|
|
@@ -577,6 +603,12 @@ try {
|
|
|
577
603
|
assert.ok(bodyView.textContent.includes('all devices, no paging'));
|
|
578
604
|
assert.ok(bodyView.querySelector('code')?.textContent.includes('npx @mindexec/remote connect'));
|
|
579
605
|
assert.ok(bodyView.textContent.includes('synthetic-render-ai'));
|
|
606
|
+
assert.ok(bodyView.textContent.includes('Task timed out waiting for the agent response.'));
|
|
607
|
+
const batchSummary = bodyView.querySelector('[data-remote-fleet-task-batch="true"]');
|
|
608
|
+
assert.ok(batchSummary);
|
|
609
|
+
assert.ok(batchSummary.textContent.includes('Render smoke batch'));
|
|
610
|
+
assert.ok(batchSummary.textContent.includes('208/210 done'));
|
|
611
|
+
assert.ok(batchSummary.textContent.includes('1 timed out'));
|
|
580
612
|
assert.ok(devices.some(device => /^synthetic-response-/.test(device.LatestTaskResultResponseId)));
|
|
581
613
|
|
|
582
614
|
const searchInput = bodyView.querySelector('[data-remote-fleet-search="true"]');
|
|
@@ -630,6 +662,7 @@ try {
|
|
|
630
662
|
assert.ok(deviceBody.textContent.includes(focusedDevice.Name));
|
|
631
663
|
assert.ok(deviceBody.querySelector('[data-remote-fleet-action="refresh-device"]'));
|
|
632
664
|
assert.ok(deviceBody.querySelector('[data-remote-fleet-action="task-device"]'));
|
|
665
|
+
assert.ok(deviceBody.textContent.includes('Task timed out waiting for the agent response.'));
|
|
633
666
|
assert.ok(deviceBody.textContent.includes(focusedDevice.DeviceId));
|
|
634
667
|
|
|
635
668
|
console.log(`RemoteFleet render smoke OK (${SYNTHETIC_COUNT} synthetic devices, ${connectedCount} connected, ${aiCount} AI-ready)`);
|
|
@@ -77,13 +77,20 @@ try {
|
|
|
77
77
|
.filter(device => device.connected && device.capabilities?.taskDispatch)
|
|
78
78
|
.slice(0, 200);
|
|
79
79
|
assert.ok(connectedTargets.length >= 100);
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
assert.equal(
|
|
86
|
-
assert.equal(
|
|
80
|
+
const scaleBatch = hub.requestAgentTaskBatch(connectedTargets.map(device => device.deviceId), {
|
|
81
|
+
instruction: 'Synthetic scale task: report current status to the manager.',
|
|
82
|
+
title: 'Scale task batch'
|
|
83
|
+
});
|
|
84
|
+
assert.equal(scaleBatch.ok, true);
|
|
85
|
+
assert.equal(scaleBatch.total, connectedTargets.length);
|
|
86
|
+
assert.equal(scaleBatch.queued, connectedTargets.length);
|
|
87
|
+
assert.equal(scaleBatch.results.every(result => result.ok === true), true);
|
|
88
|
+
assert.equal(scaleBatch.batch?.completed, connectedTargets.length);
|
|
89
|
+
assert.equal(scaleBatch.batch?.failed, 0);
|
|
90
|
+
assert.equal(scaleBatch.batch?.status, 'completed');
|
|
91
|
+
const latestBatch = hub.getLatestTaskBatch();
|
|
92
|
+
assert.equal(latestBatch?.batchId, scaleBatch.batchId);
|
|
93
|
+
assert.equal(latestBatch?.completed, connectedTargets.length);
|
|
87
94
|
|
|
88
95
|
const aiTarget = hub.listDevices().find(device =>
|
|
89
96
|
device.connected && device.capabilities?.taskDispatch && device.capabilities?.aiAssist);
|
|
@@ -30,7 +30,8 @@ const hub = createRemoteHub({
|
|
|
30
30
|
MINDEXEC_REMOTE_HUB: '1',
|
|
31
31
|
REMOTE_HUB_HOST: '127.0.0.1',
|
|
32
32
|
REMOTE_HUB_PORT: '0',
|
|
33
|
-
REMOTE_HUB_PAIR_TOKEN: 'smoke-token'
|
|
33
|
+
REMOTE_HUB_PAIR_TOKEN: 'smoke-token',
|
|
34
|
+
REMOTE_HUB_TASK_TIMEOUT_MS: '120'
|
|
34
35
|
}
|
|
35
36
|
});
|
|
36
37
|
|
|
@@ -230,6 +231,63 @@ try {
|
|
|
230
231
|
assert.equal(aiTaskDevice.counters.tasksQueued, 2);
|
|
231
232
|
assert.equal(aiTaskDevice.counters.taskResultsReceived, 2);
|
|
232
233
|
|
|
234
|
+
const batchTask = hub.requestAgentTaskBatch(['smoke-device', 'missing-smoke-device'], {
|
|
235
|
+
instruction: 'Dispatch a smoke batch to one connected and one missing device.',
|
|
236
|
+
title: 'Smoke batch'
|
|
237
|
+
});
|
|
238
|
+
assert.equal(batchTask.ok, true);
|
|
239
|
+
assert.equal(batchTask.total, 2);
|
|
240
|
+
assert.equal(batchTask.queued, 1);
|
|
241
|
+
assert.ok(batchTask.batchId);
|
|
242
|
+
assert.equal(batchTask.batch?.pending, 1);
|
|
243
|
+
assert.equal(batchTask.batch?.failed, 1);
|
|
244
|
+
assert.equal(batchTask.batch?.status, 'running');
|
|
245
|
+
const acceptedBatchItem = batchTask.results.find(result => result.deviceId === 'smoke-device');
|
|
246
|
+
assert.ok(acceptedBatchItem?.commandId);
|
|
247
|
+
writeJsonLine(socket, {
|
|
248
|
+
type: 'command.result',
|
|
249
|
+
commandId: acceptedBatchItem.commandId,
|
|
250
|
+
result: {
|
|
251
|
+
kind: 'agent.task',
|
|
252
|
+
taskId: acceptedBatchItem.taskId,
|
|
253
|
+
status: 'completed',
|
|
254
|
+
summary: 'Smoke batch item accepted.',
|
|
255
|
+
completedAt: new Date().toISOString()
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const completedBatch = await waitFor(() => {
|
|
260
|
+
const latest = hub.getLatestTaskBatch();
|
|
261
|
+
return latest?.batchId === batchTask.batchId
|
|
262
|
+
&& latest.completed === 1
|
|
263
|
+
&& latest.failed === 1
|
|
264
|
+
? latest
|
|
265
|
+
: null;
|
|
266
|
+
});
|
|
267
|
+
assert.equal(completedBatch.pending, 0);
|
|
268
|
+
assert.equal(completedBatch.status, 'completed-with-failures');
|
|
269
|
+
assert.equal(completedBatch.results.length, 2);
|
|
270
|
+
assert.equal(completedBatch.results.find(result => result.deviceId === 'missing-smoke-device')?.error, 'device-not-found');
|
|
271
|
+
|
|
272
|
+
const timeoutTaskCommand = hub.requestAgentTask('smoke-device', {
|
|
273
|
+
instruction: 'This task intentionally receives no agent response.',
|
|
274
|
+
title: 'Timeout task'
|
|
275
|
+
});
|
|
276
|
+
assert.equal(timeoutTaskCommand.ok, true);
|
|
277
|
+
const timeoutTaskDevice = await waitFor(() => {
|
|
278
|
+
const current = hub.listDevices();
|
|
279
|
+
return current[0]?.latestTask?.taskId === timeoutTaskCommand.taskId
|
|
280
|
+
&& current[0]?.latestTask?.status === 'failed'
|
|
281
|
+
? current[0]
|
|
282
|
+
: null;
|
|
283
|
+
}, 1000);
|
|
284
|
+
assert.equal(timeoutTaskDevice.latestTask.error, 'task-timeout');
|
|
285
|
+
assert.equal(timeoutTaskDevice.latestTask.resultSummary, 'task-timeout');
|
|
286
|
+
assert.equal(timeoutTaskDevice.counters.tasksQueued, 4);
|
|
287
|
+
assert.equal(timeoutTaskDevice.counters.taskResultsReceived, 4);
|
|
288
|
+
assert.equal(timeoutTaskDevice.counters.taskResultsFailed, 1);
|
|
289
|
+
assert.equal(timeoutTaskDevice.counters.taskResultsTimedOut, 1);
|
|
290
|
+
|
|
233
291
|
socket.destroy();
|
|
234
292
|
await waitFor(() => hub.listDevices()[0]?.connected === false);
|
|
235
293
|
console.log('RemoteHub smoke OK');
|
package/server.js
CHANGED
|
@@ -6969,6 +6969,8 @@ app.get('/api/remote/devices', (req, res) => {
|
|
|
6969
6969
|
total: devices.length,
|
|
6970
6970
|
pagination: 'none',
|
|
6971
6971
|
canvasDeviceListMode: 'all-devices',
|
|
6972
|
+
latestTaskBatch: remoteHub.getLatestTaskBatch(),
|
|
6973
|
+
recentTaskBatches: remoteHub.listTaskBatches(),
|
|
6972
6974
|
devices
|
|
6973
6975
|
});
|
|
6974
6976
|
});
|
|
@@ -7040,23 +7042,20 @@ app.post('/api/remote/tasks', (req, res) => {
|
|
|
7040
7042
|
? requestedDeviceIds
|
|
7041
7043
|
: (allConnected ? devices.filter(device => device.connected).map(device => device.deviceId) : []);
|
|
7042
7044
|
const uniqueTargetIds = [...new Set(targetIds)].slice(0, 500);
|
|
7043
|
-
const
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
})
|
|
7051
|
-
}));
|
|
7052
|
-
const queued = results.filter(result => result.ok === true).length;
|
|
7045
|
+
const result = remoteHub.requestAgentTaskBatch(uniqueTargetIds, {
|
|
7046
|
+
instruction: req.body?.instruction,
|
|
7047
|
+
title: req.body?.title,
|
|
7048
|
+
approvalLevel: req.body?.approvalLevel,
|
|
7049
|
+
model: req.body?.model,
|
|
7050
|
+
batchId: req.body?.batchId
|
|
7051
|
+
});
|
|
7053
7052
|
res.json({
|
|
7054
|
-
|
|
7053
|
+
...result,
|
|
7054
|
+
ok: result.ok === true,
|
|
7055
7055
|
total: uniqueTargetIds.length,
|
|
7056
|
-
queued,
|
|
7056
|
+
queued: result.queued || 0,
|
|
7057
7057
|
approvalLevel: req.body?.approvalLevel === 'ai-assist' ? 'ai-assist' : 'task-only',
|
|
7058
|
-
|
|
7059
|
-
error: queued > 0 ? undefined : (uniqueTargetIds.length === 0 ? 'no-target-devices' : 'no-task-queued')
|
|
7058
|
+
error: result.ok === true ? undefined : (uniqueTargetIds.length === 0 ? 'no-target-devices' : (result.error || 'no-task-queued'))
|
|
7060
7059
|
});
|
|
7061
7060
|
});
|
|
7062
7061
|
|
|
@@ -11975,6 +11975,22 @@
|
|
|
11975
11975
|
}
|
|
11976
11976
|
}
|
|
11977
11977
|
|
|
11978
|
+
function parseRemoteFleetLatestTaskBatch(nodeModel) {
|
|
11979
|
+
const raw = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLatestTaskBatchJson', '');
|
|
11980
|
+
if (!raw.trim()) {
|
|
11981
|
+
return null;
|
|
11982
|
+
}
|
|
11983
|
+
|
|
11984
|
+
try {
|
|
11985
|
+
const parsed = JSON.parse(raw);
|
|
11986
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
11987
|
+
? parsed
|
|
11988
|
+
: null;
|
|
11989
|
+
} catch {
|
|
11990
|
+
return null;
|
|
11991
|
+
}
|
|
11992
|
+
}
|
|
11993
|
+
|
|
11978
11994
|
function parseRemoteFleetPinnedDevice(nodeModel) {
|
|
11979
11995
|
const raw = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetPinnedDeviceJson', '{}');
|
|
11980
11996
|
if (!raw.trim()) {
|
|
@@ -12204,6 +12220,22 @@
|
|
|
12204
12220
|
return getRemoteFleetDeviceField(device, 'thumbnailEnabled', 'ThumbnailEnabled', false) === true;
|
|
12205
12221
|
}
|
|
12206
12222
|
|
|
12223
|
+
function formatRemoteFleetTaskError(error) {
|
|
12224
|
+
const value = String(error || '').trim();
|
|
12225
|
+
switch (value) {
|
|
12226
|
+
case 'task-timeout':
|
|
12227
|
+
return 'Task timed out waiting for the agent response.';
|
|
12228
|
+
case 'device-disconnected':
|
|
12229
|
+
return 'Agent disconnected before returning a task result.';
|
|
12230
|
+
case 'replaced-by-new-session':
|
|
12231
|
+
return 'Agent reconnected before returning a task result.';
|
|
12232
|
+
case 'hub-shutdown':
|
|
12233
|
+
return 'RemoteHub shut down before the task completed.';
|
|
12234
|
+
default:
|
|
12235
|
+
return value;
|
|
12236
|
+
}
|
|
12237
|
+
}
|
|
12238
|
+
|
|
12207
12239
|
function getRemoteFleetGroupInfo(device, groupMode) {
|
|
12208
12240
|
const mode = String(groupMode || 'none');
|
|
12209
12241
|
const connected = isRemoteFleetDeviceConnected(device);
|
|
@@ -12601,7 +12633,7 @@
|
|
|
12601
12633
|
: taskLine.textContent;
|
|
12602
12634
|
taskLine.style.cssText = `color:${taskTone === 'error' ? '#991b1b' : taskTone === 'done' ? '#047857' : '#1d4ed8'};font-size:10px;font-weight:900;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;`;
|
|
12603
12635
|
const taskSummary = document.createElement('div');
|
|
12604
|
-
taskSummary.textContent = latestTaskError || latestTaskResult || latestTaskTitle || 'Task queued';
|
|
12636
|
+
taskSummary.textContent = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || 'Task queued';
|
|
12605
12637
|
taskSummary.title = taskSummary.textContent;
|
|
12606
12638
|
taskSummary.style.cssText = 'color:#334155;font-size:11px;font-weight:750;line-height:1.3;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;letter-spacing:0;';
|
|
12607
12639
|
taskBox.appendChild(taskLine);
|
|
@@ -12852,6 +12884,7 @@
|
|
|
12852
12884
|
const aiAssistState = bodyView.dataset.remoteFleetAiAssist === 'true';
|
|
12853
12885
|
const autoMonitorState = bodyView.dataset.remoteFleetAutoMonitor !== 'false';
|
|
12854
12886
|
const focusState = bodyView.dataset.remoteFleetFocusDeviceId || '';
|
|
12887
|
+
const latestTaskBatch = parseRemoteFleetLatestTaskBatch(nodeModel);
|
|
12855
12888
|
const sortedDevices = [...parseRemoteFleetDevices(nodeModel)]
|
|
12856
12889
|
.sort((left, right) => compareRemoteFleetDevices(left, right, sortState));
|
|
12857
12890
|
const devices = groupState === 'none'
|
|
@@ -13159,6 +13192,64 @@
|
|
|
13159
13192
|
`;
|
|
13160
13193
|
bodyView.appendChild(taskFeedback);
|
|
13161
13194
|
|
|
13195
|
+
if (latestTaskBatch) {
|
|
13196
|
+
const batchTotal = Number(latestTaskBatch.total ?? latestTaskBatch.Total ?? 0);
|
|
13197
|
+
const batchQueued = Number(latestTaskBatch.queued ?? latestTaskBatch.Queued ?? 0);
|
|
13198
|
+
const batchPending = Number(latestTaskBatch.pending ?? latestTaskBatch.Pending ?? 0);
|
|
13199
|
+
const batchCompleted = Number(latestTaskBatch.completed ?? latestTaskBatch.Completed ?? 0);
|
|
13200
|
+
const batchFailed = Number(latestTaskBatch.failed ?? latestTaskBatch.Failed ?? 0);
|
|
13201
|
+
const batchTimedOut = Number(latestTaskBatch.timedOut ?? latestTaskBatch.TimedOut ?? 0);
|
|
13202
|
+
const batchStatus = String(latestTaskBatch.status ?? latestTaskBatch.Status ?? '').trim();
|
|
13203
|
+
const batchTitle = String(latestTaskBatch.title ?? latestTaskBatch.Title ?? 'Remote task batch').trim();
|
|
13204
|
+
const batchUpdatedAt = String(latestTaskBatch.updatedAt ?? latestTaskBatch.UpdatedAt ?? latestTaskBatch.requestedAt ?? latestTaskBatch.RequestedAt ?? '').trim();
|
|
13205
|
+
const doneCount = Math.max(0, batchCompleted + batchFailed);
|
|
13206
|
+
const progress = batchTotal > 0 ? Math.max(0, Math.min(100, Math.round(doneCount * 100 / batchTotal))) : 0;
|
|
13207
|
+
const batchTone = batchFailed > 0
|
|
13208
|
+
? 'error'
|
|
13209
|
+
: batchPending > 0
|
|
13210
|
+
? 'pending'
|
|
13211
|
+
: 'done';
|
|
13212
|
+
const batchBox = document.createElement('div');
|
|
13213
|
+
batchBox.dataset.remoteFleetTaskBatch = 'true';
|
|
13214
|
+
batchBox.style.cssText = `
|
|
13215
|
+
flex: 0 0 auto;
|
|
13216
|
+
display: grid;
|
|
13217
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
13218
|
+
gap: 8px 12px;
|
|
13219
|
+
align-items: center;
|
|
13220
|
+
padding: 8px 10px;
|
|
13221
|
+
border-radius: 8px;
|
|
13222
|
+
background: ${batchTone === 'error' ? 'rgba(248, 113, 113, 0.11)' : batchTone === 'done' ? 'rgba(16, 185, 129, 0.10)' : 'rgba(37, 99, 235, 0.08)'};
|
|
13223
|
+
border: 1px solid ${batchTone === 'error' ? 'rgba(248, 113, 113, 0.24)' : batchTone === 'done' ? 'rgba(16, 185, 129, 0.20)' : 'rgba(37, 99, 235, 0.16)'};
|
|
13224
|
+
min-width: 0;
|
|
13225
|
+
`;
|
|
13226
|
+
const batchMain = document.createElement('div');
|
|
13227
|
+
batchMain.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:5px;';
|
|
13228
|
+
const batchLabel = document.createElement('div');
|
|
13229
|
+
batchLabel.textContent = `${batchTitle} - ${batchStatus || 'running'}${batchUpdatedAt ? ` - ${formatRemoteFleetAge(batchUpdatedAt)}` : ''}`;
|
|
13230
|
+
batchLabel.title = batchLabel.textContent;
|
|
13231
|
+
batchLabel.style.cssText = `color:${batchTone === 'error' ? '#991b1b' : batchTone === 'done' ? '#047857' : '#1d4ed8'};font-size:11px;font-weight:950;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;`;
|
|
13232
|
+
const progressTrack = document.createElement('div');
|
|
13233
|
+
progressTrack.style.cssText = 'position:relative;height:7px;border-radius:999px;background:rgba(148,163,184,0.22);overflow:hidden;';
|
|
13234
|
+
const progressBar = document.createElement('div');
|
|
13235
|
+
progressBar.style.cssText = `
|
|
13236
|
+
width: ${progress}%;
|
|
13237
|
+
height: 100%;
|
|
13238
|
+
border-radius: inherit;
|
|
13239
|
+
background: ${batchTone === 'error' ? '#ef4444' : batchTone === 'done' ? '#10b981' : '#2563eb'};
|
|
13240
|
+
`;
|
|
13241
|
+
progressTrack.appendChild(progressBar);
|
|
13242
|
+
batchMain.appendChild(batchLabel);
|
|
13243
|
+
batchMain.appendChild(progressTrack);
|
|
13244
|
+
const batchStats = document.createElement('div');
|
|
13245
|
+
batchStats.textContent = `${batchCompleted}/${batchTotal || batchQueued || 0} done - ${batchPending} pending - ${batchFailed} failed${batchTimedOut > 0 ? ` - ${batchTimedOut} timed out` : ''}`;
|
|
13246
|
+
batchStats.title = batchStats.textContent;
|
|
13247
|
+
batchStats.style.cssText = 'color:#334155;font-size:10px;font-weight:900;line-height:1.25;white-space:nowrap;letter-spacing:0;';
|
|
13248
|
+
batchBox.appendChild(batchMain);
|
|
13249
|
+
batchBox.appendChild(batchStats);
|
|
13250
|
+
bodyView.appendChild(batchBox);
|
|
13251
|
+
}
|
|
13252
|
+
|
|
13162
13253
|
if (focusedDevice) {
|
|
13163
13254
|
const focusedId = getRemoteFleetDeviceId(focusedDevice);
|
|
13164
13255
|
const focusedName = getRemoteFleetDeviceName(focusedDevice);
|
|
@@ -13641,7 +13732,7 @@
|
|
|
13641
13732
|
letter-spacing: 0;
|
|
13642
13733
|
`;
|
|
13643
13734
|
const taskSummary = document.createElement('div');
|
|
13644
|
-
taskSummary.textContent = latestTaskError || latestTaskResult || latestTaskTitle || 'Task queued';
|
|
13735
|
+
taskSummary.textContent = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || 'Task queued';
|
|
13645
13736
|
taskSummary.title = taskSummary.textContent;
|
|
13646
13737
|
taskSummary.style.cssText = `
|
|
13647
13738
|
color: #334155;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/wwwroot/_framework/{MindExecution.Web.stbecd1tk9.dll → MindExecution.Web.6i9vy7xy1k.dll}
RENAMED
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"mainAssemblyName": "MindExecution.Web",
|
|
3
3
|
"resources": {
|
|
4
|
-
"hash": "sha256-
|
|
4
|
+
"hash": "sha256-x+zuHEgludyG90VHm7vAvh24gxrcXgpLsHEMSmn3EZA=",
|
|
5
5
|
"fingerprinting": {
|
|
6
6
|
"Google.Protobuf.9h59ukbel7.dll": "Google.Protobuf.dll",
|
|
7
7
|
"Markdig.d1j7v41cl1.dll": "Markdig.dll",
|
|
@@ -123,16 +123,16 @@
|
|
|
123
123
|
"System.m05i39uvk9.dll": "System.dll",
|
|
124
124
|
"netstandard.0xet7jg7ky.dll": "netstandard.dll",
|
|
125
125
|
"System.Private.CoreLib.rkafq04oma.dll": "System.Private.CoreLib.dll",
|
|
126
|
-
"MindExecution.Core.
|
|
127
|
-
"MindExecution.Kernel.
|
|
128
|
-
"MindExecution.Plugins.Admin.
|
|
129
|
-
"MindExecution.Plugins.Business.
|
|
130
|
-
"MindExecution.Plugins.Concept.
|
|
131
|
-
"MindExecution.Plugins.Directory.
|
|
132
|
-
"MindExecution.Plugins.PlanMaster.
|
|
133
|
-
"MindExecution.Plugins.YouTube.
|
|
134
|
-
"MindExecution.Shared.
|
|
135
|
-
"MindExecution.Web.
|
|
126
|
+
"MindExecution.Core.ckgjbdvvxl.dll": "MindExecution.Core.dll",
|
|
127
|
+
"MindExecution.Kernel.lm7kgq8l8h.dll": "MindExecution.Kernel.dll",
|
|
128
|
+
"MindExecution.Plugins.Admin.k085kt3dls.dll": "MindExecution.Plugins.Admin.dll",
|
|
129
|
+
"MindExecution.Plugins.Business.dsz9uwkxih.dll": "MindExecution.Plugins.Business.dll",
|
|
130
|
+
"MindExecution.Plugins.Concept.6gcazss69r.dll": "MindExecution.Plugins.Concept.dll",
|
|
131
|
+
"MindExecution.Plugins.Directory.ufbriq6js4.dll": "MindExecution.Plugins.Directory.dll",
|
|
132
|
+
"MindExecution.Plugins.PlanMaster.zqc0kv9n5d.dll": "MindExecution.Plugins.PlanMaster.dll",
|
|
133
|
+
"MindExecution.Plugins.YouTube.5ojg8vbf9w.dll": "MindExecution.Plugins.YouTube.dll",
|
|
134
|
+
"MindExecution.Shared.ahf7j9dymw.dll": "MindExecution.Shared.dll",
|
|
135
|
+
"MindExecution.Web.6i9vy7xy1k.dll": "MindExecution.Web.dll",
|
|
136
136
|
"dotnet.js": "dotnet.js",
|
|
137
137
|
"dotnet.native.xsn1d6x2kd.js": "dotnet.native.js",
|
|
138
138
|
"dotnet.native.vz0adxojrz.wasm": "dotnet.native.wasm",
|
|
@@ -278,18 +278,18 @@
|
|
|
278
278
|
"System.Xml.XDocument.c539ki6cuq.dll": "sha256-MPTRJkptrL9nGa2tl4kF46+wErNUYRPCGblX3ANoKoY=",
|
|
279
279
|
"System.m05i39uvk9.dll": "sha256-5jDfIdbYAigw7/Q/lMzt5W/+cayGbW9ko9FvuaN1GsQ=",
|
|
280
280
|
"netstandard.0xet7jg7ky.dll": "sha256-xENDv620uJ8fHwLJ2bdhrTHz4QPjvqXOztnk2a4wr0c=",
|
|
281
|
-
"MindExecution.Core.
|
|
282
|
-
"MindExecution.Kernel.
|
|
283
|
-
"MindExecution.Plugins.Concept.
|
|
284
|
-
"MindExecution.Plugins.PlanMaster.
|
|
285
|
-
"MindExecution.Shared.
|
|
286
|
-
"MindExecution.Web.
|
|
281
|
+
"MindExecution.Core.ckgjbdvvxl.dll": "sha256-ZDas50sgMQGCNt0UC1ZErP+uv7PQ7z/skK7zCwnmU2I=",
|
|
282
|
+
"MindExecution.Kernel.lm7kgq8l8h.dll": "sha256-ndscepu8d8g/nrJmBP8VrkIOTIkSA57t5shOq/rp/ys=",
|
|
283
|
+
"MindExecution.Plugins.Concept.6gcazss69r.dll": "sha256-hpCyKxiQxk50zHE8iOWtoIiBapBG83wfaos3jZzeaYg=",
|
|
284
|
+
"MindExecution.Plugins.PlanMaster.zqc0kv9n5d.dll": "sha256-iwATrRDt3Wwr/DKLxUl7GzqwKdhMLZNBKYVJPAkrhdM=",
|
|
285
|
+
"MindExecution.Shared.ahf7j9dymw.dll": "sha256-HlFaI9iZybI0VDe6KpsgRqLb3GTfUVXoU3KMlx0gQ0o=",
|
|
286
|
+
"MindExecution.Web.6i9vy7xy1k.dll": "sha256-IlDxFyeBhZWgici5UD0RtmqRNwynyD6n9Q0IJ8f4eBE="
|
|
287
287
|
},
|
|
288
288
|
"lazyAssembly": {
|
|
289
|
-
"MindExecution.Plugins.Admin.
|
|
290
|
-
"MindExecution.Plugins.Business.
|
|
291
|
-
"MindExecution.Plugins.Directory.
|
|
292
|
-
"MindExecution.Plugins.YouTube.
|
|
289
|
+
"MindExecution.Plugins.Admin.k085kt3dls.dll": "sha256-LLDLGzE3yaP+rL+8CMZGWBmGQZBfrRWgUMijX/fxTxw=",
|
|
290
|
+
"MindExecution.Plugins.Business.dsz9uwkxih.dll": "sha256-bfvFu7Pq58deN7TnwfhsUZdvWhS4RpA0LdO77UsBnY8=",
|
|
291
|
+
"MindExecution.Plugins.Directory.ufbriq6js4.dll": "sha256-qoxaegrVr9oaXJgQqU7t7CrCf0EYgsd22BcWXg/+91I=",
|
|
292
|
+
"MindExecution.Plugins.YouTube.5ojg8vbf9w.dll": "sha256-a+S4Xha/L7um0foEwb8E0p8VqEw21M6ETzjrEUpIYTk="
|
|
293
293
|
}
|
|
294
294
|
},
|
|
295
295
|
"cacheBootResources": true,
|
package/wwwroot/index.html
CHANGED
|
@@ -558,7 +558,7 @@
|
|
|
558
558
|
}
|
|
559
559
|
|
|
560
560
|
const base = '_content/MindExecution.Shared/js/';
|
|
561
|
-
const scriptVersion = '20260612-remote-
|
|
561
|
+
const scriptVersion = '20260612-remote-task-batch-v476';
|
|
562
562
|
const scriptUrl = (script) => `${base}${script}?v=${scriptVersion}`;
|
|
563
563
|
console.log(`[Script Loader] Shared JS version: ${scriptVersion}`);
|
|
564
564
|
const criticalScripts = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
self.assetsManifest = {
|
|
2
|
-
"version": "
|
|
2
|
+
"version": "wUCVSTgn",
|
|
3
3
|
"assets": [
|
|
4
4
|
{
|
|
5
5
|
"hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
"hash": "sha256-
|
|
89
|
+
"hash": "sha256-MaP05yuNb/XsfihViUh/bL9svf11oMZGMZiAWnpm/Gk=",
|
|
90
90
|
"url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
|
|
91
91
|
},
|
|
92
92
|
{
|
|
@@ -410,44 +410,44 @@
|
|
|
410
410
|
"url": "_framework/MimeMapping.og9ys58ylm.dll"
|
|
411
411
|
},
|
|
412
412
|
{
|
|
413
|
-
"hash": "sha256-
|
|
414
|
-
"url": "_framework/MindExecution.Core.
|
|
413
|
+
"hash": "sha256-ZDas50sgMQGCNt0UC1ZErP+uv7PQ7z/skK7zCwnmU2I=",
|
|
414
|
+
"url": "_framework/MindExecution.Core.ckgjbdvvxl.dll"
|
|
415
415
|
},
|
|
416
416
|
{
|
|
417
|
-
"hash": "sha256-
|
|
418
|
-
"url": "_framework/MindExecution.Kernel.
|
|
417
|
+
"hash": "sha256-ndscepu8d8g/nrJmBP8VrkIOTIkSA57t5shOq/rp/ys=",
|
|
418
|
+
"url": "_framework/MindExecution.Kernel.lm7kgq8l8h.dll"
|
|
419
419
|
},
|
|
420
420
|
{
|
|
421
|
-
"hash": "sha256-
|
|
422
|
-
"url": "_framework/MindExecution.Plugins.Admin.
|
|
421
|
+
"hash": "sha256-LLDLGzE3yaP+rL+8CMZGWBmGQZBfrRWgUMijX/fxTxw=",
|
|
422
|
+
"url": "_framework/MindExecution.Plugins.Admin.k085kt3dls.dll"
|
|
423
423
|
},
|
|
424
424
|
{
|
|
425
|
-
"hash": "sha256-
|
|
426
|
-
"url": "_framework/MindExecution.Plugins.Business.
|
|
425
|
+
"hash": "sha256-bfvFu7Pq58deN7TnwfhsUZdvWhS4RpA0LdO77UsBnY8=",
|
|
426
|
+
"url": "_framework/MindExecution.Plugins.Business.dsz9uwkxih.dll"
|
|
427
427
|
},
|
|
428
428
|
{
|
|
429
|
-
"hash": "sha256-
|
|
430
|
-
"url": "_framework/MindExecution.Plugins.Concept.
|
|
429
|
+
"hash": "sha256-hpCyKxiQxk50zHE8iOWtoIiBapBG83wfaos3jZzeaYg=",
|
|
430
|
+
"url": "_framework/MindExecution.Plugins.Concept.6gcazss69r.dll"
|
|
431
431
|
},
|
|
432
432
|
{
|
|
433
|
-
"hash": "sha256-
|
|
434
|
-
"url": "_framework/MindExecution.Plugins.Directory.
|
|
433
|
+
"hash": "sha256-qoxaegrVr9oaXJgQqU7t7CrCf0EYgsd22BcWXg/+91I=",
|
|
434
|
+
"url": "_framework/MindExecution.Plugins.Directory.ufbriq6js4.dll"
|
|
435
435
|
},
|
|
436
436
|
{
|
|
437
|
-
"hash": "sha256-
|
|
438
|
-
"url": "_framework/MindExecution.Plugins.PlanMaster.
|
|
437
|
+
"hash": "sha256-iwATrRDt3Wwr/DKLxUl7GzqwKdhMLZNBKYVJPAkrhdM=",
|
|
438
|
+
"url": "_framework/MindExecution.Plugins.PlanMaster.zqc0kv9n5d.dll"
|
|
439
439
|
},
|
|
440
440
|
{
|
|
441
|
-
"hash": "sha256-
|
|
442
|
-
"url": "_framework/MindExecution.Plugins.YouTube.
|
|
441
|
+
"hash": "sha256-a+S4Xha/L7um0foEwb8E0p8VqEw21M6ETzjrEUpIYTk=",
|
|
442
|
+
"url": "_framework/MindExecution.Plugins.YouTube.5ojg8vbf9w.dll"
|
|
443
443
|
},
|
|
444
444
|
{
|
|
445
|
-
"hash": "sha256-
|
|
446
|
-
"url": "_framework/MindExecution.Shared.
|
|
445
|
+
"hash": "sha256-HlFaI9iZybI0VDe6KpsgRqLb3GTfUVXoU3KMlx0gQ0o=",
|
|
446
|
+
"url": "_framework/MindExecution.Shared.ahf7j9dymw.dll"
|
|
447
447
|
},
|
|
448
448
|
{
|
|
449
|
-
"hash": "sha256-
|
|
450
|
-
"url": "_framework/MindExecution.Web.
|
|
449
|
+
"hash": "sha256-IlDxFyeBhZWgici5UD0RtmqRNwynyD6n9Q0IJ8f4eBE=",
|
|
450
|
+
"url": "_framework/MindExecution.Web.6i9vy7xy1k.dll"
|
|
451
451
|
},
|
|
452
452
|
{
|
|
453
453
|
"hash": "sha256-IsZJ91/OW+fHzNqIgEc7Y072ns8z9dGritiSyvR9Wgc=",
|
|
@@ -770,7 +770,7 @@
|
|
|
770
770
|
"url": "_framework/Websocket.Client.vapounvmnl.dll"
|
|
771
771
|
},
|
|
772
772
|
{
|
|
773
|
-
"hash": "sha256-
|
|
773
|
+
"hash": "sha256-fGH3vBiA9K4LY1+siDEi6C1uyYfJgDAywmyOxn+xGY8=",
|
|
774
774
|
"url": "_framework/blazor.boot.json"
|
|
775
775
|
},
|
|
776
776
|
{
|
|
@@ -834,7 +834,7 @@
|
|
|
834
834
|
"url": "image-manifest.json"
|
|
835
835
|
},
|
|
836
836
|
{
|
|
837
|
-
"hash": "sha256-
|
|
837
|
+
"hash": "sha256-2i4bXinqysl6fMxtRowvbfVpJyKIfPSm3wdbCBcX5i4=",
|
|
838
838
|
"url": "index.html"
|
|
839
839
|
},
|
|
840
840
|
{
|
|
Binary file
|