@mindexec/cli 0.2.2 → 0.2.4
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/README.md +50 -0
- package/package.json +6 -4
- package/remote-hub.js +737 -0
- package/scripts/remote-hub-smoke.mjs +146 -0
- package/server.js +144 -28
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +11 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +732 -2
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +3 -1
- package/wwwroot/_framework/MindExecution.Core.rydw4mhsbd.dll +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.gwwc40sc45.dll → MindExecution.Kernel.8sz1fl3k6s.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.0jgrn1sckv.dll → MindExecution.Plugins.Admin.iltai5c3i9.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.13mme2qcag.dll → MindExecution.Plugins.Business.mscgb1gwpf.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.9al2g3v3f9.dll → MindExecution.Plugins.Concept.s888y8snr4.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.3w4t6n3se0.dll → MindExecution.Plugins.Directory.281klijdzl.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.vfmfbygv5y.dll → MindExecution.Plugins.PlanMaster.2gy2ozelqp.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.32jyiqs383.dll → MindExecution.Plugins.YouTube.1v8o9nnlzq.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.7ttmykvopx.dll → MindExecution.Shared.04anisxh35.dll} +0 -0
- package/wwwroot/_framework/MindExecution.Web.0qdidsf6sl.dll +0 -0
- package/wwwroot/_framework/blazor.boot.json +21 -21
- package/wwwroot/index.html +1 -1
- package/wwwroot/service-worker-assets.js +26 -26
- package/wwwroot/service-worker.js +1 -1
- package/wwwroot/_framework/MindExecution.Core.1q1trifbuu.dll +0 -0
- package/wwwroot/_framework/MindExecution.Web.ozzcqp30uy.dll +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
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 wait(ms) {
|
|
12
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function waitFor(predicate, timeoutMs = 3000) {
|
|
16
|
+
const startedAt = Date.now();
|
|
17
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
18
|
+
const value = predicate();
|
|
19
|
+
if (value) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
await wait(25);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new Error('Timed out waiting for RemoteHub smoke condition.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const hub = createRemoteHub({
|
|
29
|
+
env: {
|
|
30
|
+
MINDEXEC_REMOTE_HUB: '1',
|
|
31
|
+
REMOTE_HUB_HOST: '127.0.0.1',
|
|
32
|
+
REMOTE_HUB_PORT: '0',
|
|
33
|
+
REMOTE_HUB_PAIR_TOKEN: 'smoke-token'
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await hub.start();
|
|
39
|
+
const status = hub.getStatus({ includeSecrets: true });
|
|
40
|
+
assert.equal(status.started, true);
|
|
41
|
+
assert.equal(status.canvasPagination, 'none');
|
|
42
|
+
|
|
43
|
+
const socket = net.createConnection({ host: '127.0.0.1', port: status.port });
|
|
44
|
+
socket.setEncoding('utf8');
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
46
|
+
socket.once('connect', resolve);
|
|
47
|
+
socket.once('error', reject);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
writeJsonLine(socket, {
|
|
51
|
+
type: 'hello',
|
|
52
|
+
pairToken: 'smoke-token',
|
|
53
|
+
deviceId: 'smoke-device',
|
|
54
|
+
deviceName: 'Smoke Device',
|
|
55
|
+
hostname: 'smoke-host',
|
|
56
|
+
platform: process.platform,
|
|
57
|
+
arch: process.arch,
|
|
58
|
+
pid: process.pid,
|
|
59
|
+
agentVersion: '0.0.0-smoke',
|
|
60
|
+
capabilities: {
|
|
61
|
+
status: true,
|
|
62
|
+
thumbnail: false,
|
|
63
|
+
control: false,
|
|
64
|
+
computerAgent: true,
|
|
65
|
+
taskDispatch: true
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
writeJsonLine(socket, {
|
|
69
|
+
type: 'status',
|
|
70
|
+
status: {
|
|
71
|
+
uptimeSec: 1,
|
|
72
|
+
totalMem: 2,
|
|
73
|
+
freeMem: 1
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const devices = await waitFor(() => {
|
|
78
|
+
const current = hub.listDevices();
|
|
79
|
+
return current.length === 1 && current[0].status?.uptimeSec === 1 ? current : null;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
assert.equal(devices[0].deviceId, 'smoke-device');
|
|
83
|
+
assert.equal(devices[0].connected, true);
|
|
84
|
+
assert.equal(hub.getStatus().deviceCount, 1);
|
|
85
|
+
|
|
86
|
+
const thumbnailCommand = hub.requestThumbnail('smoke-device', {
|
|
87
|
+
streamId: 'smoke-thumb',
|
|
88
|
+
maxWidth: 320,
|
|
89
|
+
maxHeight: 180,
|
|
90
|
+
quality: 50
|
|
91
|
+
});
|
|
92
|
+
assert.equal(thumbnailCommand.ok, true);
|
|
93
|
+
|
|
94
|
+
writeJsonLine(socket, {
|
|
95
|
+
type: 'thumbnail.frame',
|
|
96
|
+
commandId: thumbnailCommand.commandId,
|
|
97
|
+
streamId: 'smoke-thumb',
|
|
98
|
+
frameSeq: 1,
|
|
99
|
+
width: 2,
|
|
100
|
+
height: 1,
|
|
101
|
+
mimeType: 'image/png',
|
|
102
|
+
capturedAt: new Date().toISOString(),
|
|
103
|
+
data: 'iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const thumbnailDevice = await waitFor(() => {
|
|
107
|
+
const current = hub.listDevices();
|
|
108
|
+
return current[0]?.latestThumbnail?.frameSeq === 1 ? current[0] : null;
|
|
109
|
+
});
|
|
110
|
+
assert.equal(thumbnailDevice.latestThumbnail.streamId, 'smoke-thumb');
|
|
111
|
+
assert.equal(thumbnailDevice.counters.thumbnailFramesReceived, 1);
|
|
112
|
+
|
|
113
|
+
const taskCommand = hub.requestAgentTask('smoke-device', {
|
|
114
|
+
instruction: 'Summarize current desktop status for the manager.',
|
|
115
|
+
title: 'Smoke task'
|
|
116
|
+
});
|
|
117
|
+
assert.equal(taskCommand.ok, true);
|
|
118
|
+
writeJsonLine(socket, {
|
|
119
|
+
type: 'command.result',
|
|
120
|
+
commandId: taskCommand.commandId,
|
|
121
|
+
result: {
|
|
122
|
+
kind: 'agent.task',
|
|
123
|
+
taskId: taskCommand.taskId,
|
|
124
|
+
status: 'completed',
|
|
125
|
+
summary: 'Smoke task accepted.',
|
|
126
|
+
completedAt: new Date().toISOString()
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const taskDevice = await waitFor(() => {
|
|
131
|
+
const current = hub.listDevices();
|
|
132
|
+
return current[0]?.latestTask?.taskId === taskCommand.taskId
|
|
133
|
+
&& current[0]?.latestTask?.status === 'completed'
|
|
134
|
+
? current[0]
|
|
135
|
+
: null;
|
|
136
|
+
});
|
|
137
|
+
assert.equal(taskDevice.latestTask.resultSummary, 'Smoke task accepted.');
|
|
138
|
+
assert.equal(taskDevice.counters.tasksQueued, 1);
|
|
139
|
+
assert.equal(taskDevice.counters.taskResultsReceived, 1);
|
|
140
|
+
|
|
141
|
+
socket.destroy();
|
|
142
|
+
await waitFor(() => hub.listDevices()[0]?.connected === false);
|
|
143
|
+
console.log('RemoteHub smoke OK');
|
|
144
|
+
} finally {
|
|
145
|
+
await hub.close();
|
|
146
|
+
}
|
package/server.js
CHANGED
|
@@ -22,6 +22,7 @@ import chokidar from 'chokidar';
|
|
|
22
22
|
import Parser from 'web-tree-sitter';
|
|
23
23
|
import { fileURLToPath } from 'url';
|
|
24
24
|
import { createCodexRuntime } from './codex-runtime.js';
|
|
25
|
+
import { createRemoteHub } from './remote-hub.js';
|
|
25
26
|
import portGuard from './port-guard.cjs';
|
|
26
27
|
|
|
27
28
|
const execAsync = promisify(exec);
|
|
@@ -1746,11 +1747,13 @@ const PROTECTED_BRIDGE_ROUTES = [
|
|
|
1746
1747
|
{ method: 'GET', prefix: '/api/codex/' },
|
|
1747
1748
|
{ method: 'POST', prefix: '/api/codex/' },
|
|
1748
1749
|
{ method: 'GET', prefix: '/api/shell/' },
|
|
1749
|
-
{ method: 'POST', prefix: '/api/shell/' },
|
|
1750
|
-
{ method: '
|
|
1751
|
-
{ method: 'POST',
|
|
1752
|
-
{ method: 'POST',
|
|
1753
|
-
|
|
1750
|
+
{ method: 'POST', prefix: '/api/shell/' },
|
|
1751
|
+
{ method: 'GET', prefix: '/api/remote/' },
|
|
1752
|
+
{ method: 'POST', prefix: '/api/remote/' },
|
|
1753
|
+
{ method: 'POST', prefix: '/project/' },
|
|
1754
|
+
{ method: 'POST', exact: '/agent/connect' },
|
|
1755
|
+
{ method: 'POST', exact: '/api/tool/trace' }
|
|
1756
|
+
];
|
|
1754
1757
|
|
|
1755
1758
|
function getBridgeTokenFromRequest(req) {
|
|
1756
1759
|
const headerToken = String(req.get(bridgeTokenHeader) || '').trim();
|
|
@@ -2438,11 +2441,11 @@ function pushChangeFeedItem(item) {
|
|
|
2438
2441
|
}
|
|
2439
2442
|
}
|
|
2440
2443
|
|
|
2441
|
-
function emitBridgeEvent(type, payload) {
|
|
2442
|
-
if (wsClients.size === 0) return;
|
|
2443
|
-
const message = JSON.stringify({
|
|
2444
|
-
type,
|
|
2445
|
-
timestamp: new Date().toISOString(),
|
|
2444
|
+
function emitBridgeEvent(type, payload) {
|
|
2445
|
+
if (wsClients.size === 0) return;
|
|
2446
|
+
const message = JSON.stringify({
|
|
2447
|
+
type,
|
|
2448
|
+
timestamp: new Date().toISOString(),
|
|
2446
2449
|
payload
|
|
2447
2450
|
});
|
|
2448
2451
|
for (const client of wsClients) {
|
|
@@ -2452,11 +2455,18 @@ function emitBridgeEvent(type, payload) {
|
|
|
2452
2455
|
} catch {
|
|
2453
2456
|
// Ignore one-off client send errors
|
|
2454
2457
|
}
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
}
|
|
2458
|
-
|
|
2459
|
-
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
const remoteHub = createRemoteHub({
|
|
2463
|
+
logEvent,
|
|
2464
|
+
logWarn,
|
|
2465
|
+
logError,
|
|
2466
|
+
emitEvent: emitBridgeEvent
|
|
2467
|
+
});
|
|
2468
|
+
|
|
2469
|
+
function trimShellOutput(value, maxLength) {
|
|
2460
2470
|
const text = String(value || '');
|
|
2461
2471
|
if (text.length <= maxLength) {
|
|
2462
2472
|
return text;
|
|
@@ -6906,10 +6916,11 @@ app.get('/api/status', async (req, res) => {
|
|
|
6906
6916
|
workspace: workspacePath,
|
|
6907
6917
|
wsToken,
|
|
6908
6918
|
wsPath: '/events',
|
|
6909
|
-
bridgeToken,
|
|
6910
|
-
bridgeTokenHeader,
|
|
6911
|
-
bridgeAuthRequired,
|
|
6912
|
-
|
|
6919
|
+
bridgeToken,
|
|
6920
|
+
bridgeTokenHeader,
|
|
6921
|
+
bridgeAuthRequired,
|
|
6922
|
+
remoteHub: remoteHub.getStatus({ includeSecrets: false }),
|
|
6923
|
+
shellJobsPath: '/api/shell/jobs',
|
|
6913
6924
|
companyCore: {
|
|
6914
6925
|
baseUrl: companyCoreBaseUrl,
|
|
6915
6926
|
proxyPath: '/api/company-core',
|
|
@@ -6946,6 +6957,92 @@ app.get('/api/status', async (req, res) => {
|
|
|
6946
6957
|
});
|
|
6947
6958
|
});
|
|
6948
6959
|
|
|
6960
|
+
app.get('/api/remote/status', (req, res) => {
|
|
6961
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
6962
|
+
res.json(remoteHub.getStatus({ includeSecrets: true }));
|
|
6963
|
+
});
|
|
6964
|
+
|
|
6965
|
+
app.get('/api/remote/devices', (req, res) => {
|
|
6966
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
6967
|
+
const devices = remoteHub.listDevices();
|
|
6968
|
+
res.json({
|
|
6969
|
+
total: devices.length,
|
|
6970
|
+
pagination: 'none',
|
|
6971
|
+
canvasDeviceListMode: 'all-devices',
|
|
6972
|
+
devices
|
|
6973
|
+
});
|
|
6974
|
+
});
|
|
6975
|
+
|
|
6976
|
+
app.post('/api/remote/devices/:deviceId/disconnect', (req, res) => {
|
|
6977
|
+
const disconnected = remoteHub.disconnectDevice(req.params.deviceId, 'manager-request');
|
|
6978
|
+
res.json({ ok: disconnected });
|
|
6979
|
+
});
|
|
6980
|
+
|
|
6981
|
+
app.post('/api/remote/devices/:deviceId/ping', (req, res) => {
|
|
6982
|
+
res.json(remoteHub.sendCommand(req.params.deviceId, {
|
|
6983
|
+
command: 'ping',
|
|
6984
|
+
payload: {
|
|
6985
|
+
requestedAt: new Date().toISOString()
|
|
6986
|
+
}
|
|
6987
|
+
}));
|
|
6988
|
+
});
|
|
6989
|
+
|
|
6990
|
+
app.post('/api/remote/devices/:deviceId/tasks', (req, res) => {
|
|
6991
|
+
res.json(remoteHub.requestAgentTask(req.params.deviceId, {
|
|
6992
|
+
instruction: req.body?.instruction,
|
|
6993
|
+
title: req.body?.title,
|
|
6994
|
+
taskId: req.body?.taskId,
|
|
6995
|
+
commandId: req.body?.commandId
|
|
6996
|
+
}));
|
|
6997
|
+
});
|
|
6998
|
+
|
|
6999
|
+
app.post('/api/remote/tasks', (req, res) => {
|
|
7000
|
+
const requestedDeviceIds = Array.isArray(req.body?.deviceIds)
|
|
7001
|
+
? req.body.deviceIds.map(value => String(value || '').trim()).filter(Boolean)
|
|
7002
|
+
: [];
|
|
7003
|
+
const allConnected = req.body?.allConnected !== false;
|
|
7004
|
+
const devices = remoteHub.listDevices();
|
|
7005
|
+
const targetIds = requestedDeviceIds.length > 0
|
|
7006
|
+
? requestedDeviceIds
|
|
7007
|
+
: (allConnected ? devices.filter(device => device.connected).map(device => device.deviceId) : []);
|
|
7008
|
+
const uniqueTargetIds = [...new Set(targetIds)].slice(0, 500);
|
|
7009
|
+
const results = uniqueTargetIds.map(deviceId => ({
|
|
7010
|
+
deviceId,
|
|
7011
|
+
...remoteHub.requestAgentTask(deviceId, {
|
|
7012
|
+
instruction: req.body?.instruction,
|
|
7013
|
+
title: req.body?.title
|
|
7014
|
+
})
|
|
7015
|
+
}));
|
|
7016
|
+
const queued = results.filter(result => result.ok === true).length;
|
|
7017
|
+
res.json({
|
|
7018
|
+
ok: queued > 0,
|
|
7019
|
+
total: uniqueTargetIds.length,
|
|
7020
|
+
queued,
|
|
7021
|
+
results,
|
|
7022
|
+
error: queued > 0 ? undefined : (uniqueTargetIds.length === 0 ? 'no-target-devices' : 'no-task-queued')
|
|
7023
|
+
});
|
|
7024
|
+
});
|
|
7025
|
+
|
|
7026
|
+
app.get('/api/remote/devices/:deviceId/thumbnail', (req, res) => {
|
|
7027
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
7028
|
+
const thumbnail = remoteHub.getDeviceThumbnail(req.params.deviceId);
|
|
7029
|
+
if (!thumbnail) {
|
|
7030
|
+
res.status(404).json({ ok: false, error: 'thumbnail-not-available' });
|
|
7031
|
+
return;
|
|
7032
|
+
}
|
|
7033
|
+
|
|
7034
|
+
res.json({ ok: true, thumbnail });
|
|
7035
|
+
});
|
|
7036
|
+
|
|
7037
|
+
app.post('/api/remote/devices/:deviceId/thumbnail/request', (req, res) => {
|
|
7038
|
+
res.json(remoteHub.requestThumbnail(req.params.deviceId, {
|
|
7039
|
+
maxWidth: req.body?.maxWidth,
|
|
7040
|
+
maxHeight: req.body?.maxHeight,
|
|
7041
|
+
quality: req.body?.quality,
|
|
7042
|
+
streamId: req.body?.streamId
|
|
7043
|
+
}));
|
|
7044
|
+
});
|
|
7045
|
+
|
|
6949
7046
|
app.get('/api/codex/capabilities', async (req, res) => {
|
|
6950
7047
|
try {
|
|
6951
7048
|
res.json(await getCodexRuntime().getCapabilities());
|
|
@@ -8347,6 +8444,12 @@ async function startBridgeServer() {
|
|
|
8347
8444
|
process.exit(1);
|
|
8348
8445
|
});
|
|
8349
8446
|
|
|
8447
|
+
try {
|
|
8448
|
+
await remoteHub.start();
|
|
8449
|
+
} catch (remoteHubError) {
|
|
8450
|
+
logError('remote', 'failed to start RemoteHub.', remoteHubError);
|
|
8451
|
+
}
|
|
8452
|
+
|
|
8350
8453
|
httpServer.listen(PORT, '127.0.0.1', async () => {
|
|
8351
8454
|
try {
|
|
8352
8455
|
await ensureWorkspaceDataLayout();
|
|
@@ -8355,11 +8458,18 @@ async function startBridgeServer() {
|
|
|
8355
8458
|
}
|
|
8356
8459
|
|
|
8357
8460
|
const startupCodexRuntime = getCurrentCodexRuntime();
|
|
8461
|
+
const remoteHubStatus = remoteHub.getStatus({ includeSecrets: true });
|
|
8358
8462
|
logSection('MindExec Local Bridge', [
|
|
8359
8463
|
formatKeyValue('port', tone(PORT, 'accent')),
|
|
8360
8464
|
formatKeyValue('workspace', tone(normalizePathForClient(workspacePath), 'path')),
|
|
8361
8465
|
formatKeyValue('status', tone('ready', 'success')),
|
|
8362
8466
|
formatKeyValue('exec', tone(formatCodexRuntime(startupCodexRuntime), 'accent')),
|
|
8467
|
+
formatKeyValue('remote', remoteHubStatus.started
|
|
8468
|
+
? tone(`tcp://${remoteHubStatus.host}:${remoteHubStatus.port}`, 'accent')
|
|
8469
|
+
: tone(remoteHubStatus.enabled ? 'failed' : 'disabled', 'warn')),
|
|
8470
|
+
formatKeyValue('pair', remoteHubStatus.started
|
|
8471
|
+
? tone(remoteHubStatus.pairTokenPreview, 'warn')
|
|
8472
|
+
: tone('-', 'muted')),
|
|
8363
8473
|
tone('--------', 'muted'),
|
|
8364
8474
|
WEB_APP_ROOT
|
|
8365
8475
|
? `${tone('app', 'muted')} ${tone(getLocalAppUrl(), 'path')}`
|
|
@@ -8396,15 +8506,21 @@ async function shutdownBridge(signal) {
|
|
|
8396
8506
|
}
|
|
8397
8507
|
}
|
|
8398
8508
|
|
|
8399
|
-
try {
|
|
8400
|
-
wss.close();
|
|
8401
|
-
} catch {
|
|
8402
|
-
// Ignore if already closed
|
|
8403
|
-
}
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8509
|
+
try {
|
|
8510
|
+
wss.close();
|
|
8511
|
+
} catch {
|
|
8512
|
+
// Ignore if already closed
|
|
8513
|
+
}
|
|
8514
|
+
|
|
8515
|
+
try {
|
|
8516
|
+
await remoteHub.close();
|
|
8517
|
+
} catch {
|
|
8518
|
+
// Ignore RemoteHub close errors during shutdown
|
|
8519
|
+
}
|
|
8520
|
+
|
|
8521
|
+
await new Promise((resolve) => {
|
|
8522
|
+
try {
|
|
8523
|
+
httpServer.close(() => resolve());
|
|
8408
8524
|
} catch {
|
|
8409
8525
|
resolve();
|
|
8410
8526
|
}
|
|
@@ -6272,6 +6272,16 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
|
|
|
6272
6272
|
if (!ready || !moduleInstance) return;
|
|
6273
6273
|
await MindMapNodes.addNode(moduleInstance, m);
|
|
6274
6274
|
},
|
|
6275
|
+
addRemoteFleetMonitor: async () => {
|
|
6276
|
+
const ready = await waitForMindMapReady();
|
|
6277
|
+
if (!ready || !moduleInstance?.dotNetHelper) {
|
|
6278
|
+
return { success: false, error: 'mind-map-not-ready' };
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
const x = Number(moduleInstance.cursorPosition?.x ?? moduleInstance.camera?.position?.x ?? 0);
|
|
6282
|
+
const y = Number(moduleInstance.cursorPosition?.y ?? moduleInstance.camera?.position?.y ?? 0);
|
|
6283
|
+
return await moduleInstance.dotNetHelper.invokeMethodAsync('AddRemoteFleetMonitorNodeFromJs', x, y);
|
|
6284
|
+
},
|
|
6275
6285
|
// ▼▼▼ [Perf] Expose nodeObjectsById for will-change optimization ▼▼▼
|
|
6276
6286
|
getNodeObjectsById: () => moduleInstance?.nodeObjectsById || null,
|
|
6277
6287
|
// ▲▲▲ [Perf] ▲▲▲
|
|
@@ -8032,6 +8042,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
|
|
|
8032
8042
|
|
|
8033
8043
|
// Backward-compatible alias: MindCanvas is the new page/internal name, but the underlying surface is the same.
|
|
8034
8044
|
window.mindCanvas = window.mindMap;
|
|
8045
|
+
window.addRemoteFleetMonitorNode = () => window.mindMap?.addRemoteFleetMonitor?.();
|
|
8035
8046
|
window.MindMapCoreBuildInfo = {
|
|
8036
8047
|
build: MINDMAP_CORE_BUILD_ID,
|
|
8037
8048
|
hasMovingUpdateDue: true,
|