@mindexec/cli 0.2.1 → 0.2.3
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 +38 -0
- package/package.json +6 -4
- package/remote-hub.js +571 -0
- package/scripts/remote-hub-smoke.mjs +117 -0
- package/server.js +108 -28
- package/wwwroot/_content/MindExecution.Shared/css/mind-map-overrides.css +11 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +76 -1
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +629 -5
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +16 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +16 -5
- package/wwwroot/_framework/MindExecution.Core.5luow1xgjs.dll +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.gwwc40sc45.dll → MindExecution.Kernel.mot9nj6bzm.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.0jgrn1sckv.dll → MindExecution.Plugins.Admin.x9v2drg2f7.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.13mme2qcag.dll → MindExecution.Plugins.Business.b0kjoyx31x.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.dfp2mdt45q.dll → MindExecution.Plugins.Concept.6tojojgh1a.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.3w4t6n3se0.dll → MindExecution.Plugins.Directory.fqtbuqadsx.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.s0qpntz420.dll → MindExecution.Plugins.PlanMaster.j7llfeae6l.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.iu11fq8d16.dll → MindExecution.Plugins.YouTube.yo5fwdhugr.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.7j27dcqnrc.dll → MindExecution.Shared.0qi7vbn9a4.dll} +0 -0
- package/wwwroot/_framework/MindExecution.Web.6cv7ad7rik.dll +0 -0
- package/wwwroot/_framework/blazor.boot.json +21 -21
- package/wwwroot/index.html +3 -3
- package/wwwroot/service-worker-assets.js +28 -28
- package/wwwroot/service-worker.js +1 -1
- package/wwwroot/_framework/MindExecution.Core.1q1trifbuu.dll +0 -0
- package/wwwroot/_framework/MindExecution.Web.pq1ty8ov2v.dll +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
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: false
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
writeJsonLine(socket, {
|
|
68
|
+
type: 'status',
|
|
69
|
+
status: {
|
|
70
|
+
uptimeSec: 1,
|
|
71
|
+
totalMem: 2,
|
|
72
|
+
freeMem: 1
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const devices = await waitFor(() => {
|
|
77
|
+
const current = hub.listDevices();
|
|
78
|
+
return current.length === 1 && current[0].status?.uptimeSec === 1 ? current : null;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
assert.equal(devices[0].deviceId, 'smoke-device');
|
|
82
|
+
assert.equal(devices[0].connected, true);
|
|
83
|
+
assert.equal(hub.getStatus().deviceCount, 1);
|
|
84
|
+
|
|
85
|
+
const thumbnailCommand = hub.requestThumbnail('smoke-device', {
|
|
86
|
+
streamId: 'smoke-thumb',
|
|
87
|
+
maxWidth: 320,
|
|
88
|
+
maxHeight: 180,
|
|
89
|
+
quality: 50
|
|
90
|
+
});
|
|
91
|
+
assert.equal(thumbnailCommand.ok, true);
|
|
92
|
+
|
|
93
|
+
writeJsonLine(socket, {
|
|
94
|
+
type: 'thumbnail.frame',
|
|
95
|
+
commandId: thumbnailCommand.commandId,
|
|
96
|
+
streamId: 'smoke-thumb',
|
|
97
|
+
frameSeq: 1,
|
|
98
|
+
width: 2,
|
|
99
|
+
height: 1,
|
|
100
|
+
mimeType: 'image/png',
|
|
101
|
+
capturedAt: new Date().toISOString(),
|
|
102
|
+
data: 'iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC'
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const thumbnailDevice = await waitFor(() => {
|
|
106
|
+
const current = hub.listDevices();
|
|
107
|
+
return current[0]?.latestThumbnail?.frameSeq === 1 ? current[0] : null;
|
|
108
|
+
});
|
|
109
|
+
assert.equal(thumbnailDevice.latestThumbnail.streamId, 'smoke-thumb');
|
|
110
|
+
assert.equal(thumbnailDevice.counters.thumbnailFramesReceived, 1);
|
|
111
|
+
|
|
112
|
+
socket.destroy();
|
|
113
|
+
await waitFor(() => hub.listDevices()[0]?.connected === false);
|
|
114
|
+
console.log('RemoteHub smoke OK');
|
|
115
|
+
} finally {
|
|
116
|
+
await hub.close();
|
|
117
|
+
}
|
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,56 @@ 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.get('/api/remote/devices/:deviceId/thumbnail', (req, res) => {
|
|
6991
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
6992
|
+
const thumbnail = remoteHub.getDeviceThumbnail(req.params.deviceId);
|
|
6993
|
+
if (!thumbnail) {
|
|
6994
|
+
res.status(404).json({ ok: false, error: 'thumbnail-not-available' });
|
|
6995
|
+
return;
|
|
6996
|
+
}
|
|
6997
|
+
|
|
6998
|
+
res.json({ ok: true, thumbnail });
|
|
6999
|
+
});
|
|
7000
|
+
|
|
7001
|
+
app.post('/api/remote/devices/:deviceId/thumbnail/request', (req, res) => {
|
|
7002
|
+
res.json(remoteHub.requestThumbnail(req.params.deviceId, {
|
|
7003
|
+
maxWidth: req.body?.maxWidth,
|
|
7004
|
+
maxHeight: req.body?.maxHeight,
|
|
7005
|
+
quality: req.body?.quality,
|
|
7006
|
+
streamId: req.body?.streamId
|
|
7007
|
+
}));
|
|
7008
|
+
});
|
|
7009
|
+
|
|
6949
7010
|
app.get('/api/codex/capabilities', async (req, res) => {
|
|
6950
7011
|
try {
|
|
6951
7012
|
res.json(await getCodexRuntime().getCapabilities());
|
|
@@ -8347,6 +8408,12 @@ async function startBridgeServer() {
|
|
|
8347
8408
|
process.exit(1);
|
|
8348
8409
|
});
|
|
8349
8410
|
|
|
8411
|
+
try {
|
|
8412
|
+
await remoteHub.start();
|
|
8413
|
+
} catch (remoteHubError) {
|
|
8414
|
+
logError('remote', 'failed to start RemoteHub.', remoteHubError);
|
|
8415
|
+
}
|
|
8416
|
+
|
|
8350
8417
|
httpServer.listen(PORT, '127.0.0.1', async () => {
|
|
8351
8418
|
try {
|
|
8352
8419
|
await ensureWorkspaceDataLayout();
|
|
@@ -8355,11 +8422,18 @@ async function startBridgeServer() {
|
|
|
8355
8422
|
}
|
|
8356
8423
|
|
|
8357
8424
|
const startupCodexRuntime = getCurrentCodexRuntime();
|
|
8425
|
+
const remoteHubStatus = remoteHub.getStatus({ includeSecrets: true });
|
|
8358
8426
|
logSection('MindExec Local Bridge', [
|
|
8359
8427
|
formatKeyValue('port', tone(PORT, 'accent')),
|
|
8360
8428
|
formatKeyValue('workspace', tone(normalizePathForClient(workspacePath), 'path')),
|
|
8361
8429
|
formatKeyValue('status', tone('ready', 'success')),
|
|
8362
8430
|
formatKeyValue('exec', tone(formatCodexRuntime(startupCodexRuntime), 'accent')),
|
|
8431
|
+
formatKeyValue('remote', remoteHubStatus.started
|
|
8432
|
+
? tone(`tcp://${remoteHubStatus.host}:${remoteHubStatus.port}`, 'accent')
|
|
8433
|
+
: tone(remoteHubStatus.enabled ? 'failed' : 'disabled', 'warn')),
|
|
8434
|
+
formatKeyValue('pair', remoteHubStatus.started
|
|
8435
|
+
? tone(remoteHubStatus.pairTokenPreview, 'warn')
|
|
8436
|
+
: tone('-', 'muted')),
|
|
8363
8437
|
tone('--------', 'muted'),
|
|
8364
8438
|
WEB_APP_ROOT
|
|
8365
8439
|
? `${tone('app', 'muted')} ${tone(getLocalAppUrl(), 'path')}`
|
|
@@ -8396,15 +8470,21 @@ async function shutdownBridge(signal) {
|
|
|
8396
8470
|
}
|
|
8397
8471
|
}
|
|
8398
8472
|
|
|
8399
|
-
try {
|
|
8400
|
-
wss.close();
|
|
8401
|
-
} catch {
|
|
8402
|
-
// Ignore if already closed
|
|
8403
|
-
}
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8473
|
+
try {
|
|
8474
|
+
wss.close();
|
|
8475
|
+
} catch {
|
|
8476
|
+
// Ignore if already closed
|
|
8477
|
+
}
|
|
8478
|
+
|
|
8479
|
+
try {
|
|
8480
|
+
await remoteHub.close();
|
|
8481
|
+
} catch {
|
|
8482
|
+
// Ignore RemoteHub close errors during shutdown
|
|
8483
|
+
}
|
|
8484
|
+
|
|
8485
|
+
await new Promise((resolve) => {
|
|
8486
|
+
try {
|
|
8487
|
+
httpServer.close(() => resolve());
|
|
8408
8488
|
} catch {
|
|
8409
8489
|
resolve();
|
|
8410
8490
|
}
|
|
@@ -36,6 +36,17 @@
|
|
|
36
36
|
contain-intrinsic-size: auto !important;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
.css3d-resolution-wrapper.node-type-templatelauncher,
|
|
40
|
+
.css3d-resolution-wrapper.node-type-templatelauncher > .map-node,
|
|
41
|
+
.css3d-resolution-wrapper.node-type-templatelauncher > .map-node-template-card {
|
|
42
|
+
contain: layout style !important;
|
|
43
|
+
content-visibility: visible !important;
|
|
44
|
+
contain-intrinsic-size: auto !important;
|
|
45
|
+
isolation: isolate !important;
|
|
46
|
+
-webkit-backface-visibility: visible !important;
|
|
47
|
+
backface-visibility: visible !important;
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
.css3d-resolution-wrapper.node-type-image > .map-node,
|
|
40
51
|
.css3d-resolution-wrapper.node-type-video > .map-node,
|
|
41
52
|
.css3d-resolution-wrapper.node-type-embed > .map-node {
|
|
@@ -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 = '
|
|
8
|
+
const MINDMAP_CORE_BUILD_ID = '20260610-template-board-ready-v205';
|
|
9
9
|
const CanvasPhase = Object.freeze({
|
|
10
10
|
Booting: 'booting',
|
|
11
11
|
BoardFileLoading: 'board-file-loading',
|
|
@@ -170,6 +170,52 @@
|
|
|
170
170
|
let moduleInstance = null;
|
|
171
171
|
let pendingAuthToken = null;
|
|
172
172
|
let pendingSettings = null;
|
|
173
|
+
let _lastCompletedBoardLoadId = null;
|
|
174
|
+
let _lastCompletedBoardLoadAt = 0;
|
|
175
|
+
|
|
176
|
+
function normalizeBoardIdValue(boardId) {
|
|
177
|
+
return String(boardId ?? '').trim().toLowerCase();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function invalidateCurrentBoardLoadCompletion(reason = 'unknown') {
|
|
181
|
+
_lastCompletedBoardLoadId = null;
|
|
182
|
+
_lastCompletedBoardLoadAt = 0;
|
|
183
|
+
emitMindCanvasTrace('board.load.pending', {
|
|
184
|
+
boardId: _currentBoardId,
|
|
185
|
+
reason
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isCurrentBoardLoadComplete() {
|
|
190
|
+
const activeBoardId = normalizeBoardIdValue(_currentBoardId);
|
|
191
|
+
return !!activeBoardId && activeBoardId === _lastCompletedBoardLoadId;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function markBoardLoadComplete(boardId, reason = 'unknown') {
|
|
195
|
+
const expectedBoardId = normalizeBoardIdValue(boardId);
|
|
196
|
+
const activeBoardId = normalizeBoardIdValue(_currentBoardId);
|
|
197
|
+
if (!expectedBoardId || expectedBoardId !== activeBoardId) {
|
|
198
|
+
emitMindCanvasTrace('board.staleMutationBlocked', {
|
|
199
|
+
mutation: 'markBoardLoadComplete',
|
|
200
|
+
expectedBoardId,
|
|
201
|
+
activeBoardId,
|
|
202
|
+
reason
|
|
203
|
+
});
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_lastCompletedBoardLoadId = expectedBoardId;
|
|
208
|
+
_lastCompletedBoardLoadAt = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
209
|
+
emitMindCanvasTrace('board.load.complete', {
|
|
210
|
+
boardId: _currentBoardId,
|
|
211
|
+
reason,
|
|
212
|
+
nodeCount: moduleInstance?.nodeObjectsById?.size || 0,
|
|
213
|
+
isLoading: moduleInstance?.isLoading === true,
|
|
214
|
+
phase: moduleInstance?.canvasPhase || null
|
|
215
|
+
});
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
173
219
|
function isDocumentHidden() {
|
|
174
220
|
return typeof document !== 'undefined' && document.visibilityState === 'hidden';
|
|
175
221
|
}
|
|
@@ -1997,7 +2043,11 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
|
|
|
1997
2043
|
let _currentBoardId = null;
|
|
1998
2044
|
|
|
1999
2045
|
function setActiveBoardIdRuntime(boardId, reason = 'unknown') {
|
|
2046
|
+
const previousBoardId = _currentBoardId;
|
|
2000
2047
|
_currentBoardId = boardId || null;
|
|
2048
|
+
if (normalizeBoardIdValue(previousBoardId) !== normalizeBoardIdValue(_currentBoardId)) {
|
|
2049
|
+
invalidateCurrentBoardLoadCompletion(reason);
|
|
2050
|
+
}
|
|
2001
2051
|
window.MindCanvasBuildInfo?.setRuntime?.('activeBoardId', _currentBoardId);
|
|
2002
2052
|
emitMindCanvasTrace('board.active', {
|
|
2003
2053
|
boardId: _currentBoardId,
|
|
@@ -6222,6 +6272,16 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
|
|
|
6222
6272
|
if (!ready || !moduleInstance) return;
|
|
6223
6273
|
await MindMapNodes.addNode(moduleInstance, m);
|
|
6224
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
|
+
},
|
|
6225
6285
|
// ▼▼▼ [Perf] Expose nodeObjectsById for will-change optimization ▼▼▼
|
|
6226
6286
|
getNodeObjectsById: () => moduleInstance?.nodeObjectsById || null,
|
|
6227
6287
|
// ▲▲▲ [Perf] ▲▲▲
|
|
@@ -7258,6 +7318,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
|
|
|
7258
7318
|
if (boardId) {
|
|
7259
7319
|
setActiveBoardIdRuntime(boardId, 'resetBoard');
|
|
7260
7320
|
}
|
|
7321
|
+
invalidateCurrentBoardLoadCompletion('resetBoard');
|
|
7261
7322
|
// ▲▲▲ [Fix] ▲▲▲
|
|
7262
7323
|
const resetPhase = moduleInstance.canvasPhase === CanvasPhase.Booting
|
|
7263
7324
|
? CanvasPhase.BoardFileLoading
|
|
@@ -7369,9 +7430,22 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
|
|
|
7369
7430
|
// ▼▼▼ [Fix] Expose activeBoardId for async guard in addNode ▼▼▼
|
|
7370
7431
|
get activeBoardId() { return _currentBoardId; },
|
|
7371
7432
|
setActiveBoardId: (boardId) => { setActiveBoardIdRuntime(boardId, 'external-setActiveBoardId'); },
|
|
7433
|
+
markBoardLoadCompleteForBoard: (boardId) => markBoardLoadComplete(boardId, 'csharp-board-load-complete'),
|
|
7434
|
+
getActiveBoardRuntimeState: () => ({
|
|
7435
|
+
activeBoardId: _currentBoardId,
|
|
7436
|
+
activeBoardIdNormalized: normalizeBoardIdValue(_currentBoardId),
|
|
7437
|
+
isLoading: moduleInstance?.isLoading === true,
|
|
7438
|
+
canvasPhase: moduleInstance?.canvasPhase || null,
|
|
7439
|
+
nodeCount: moduleInstance?.nodeObjectsById?.size || 0,
|
|
7440
|
+
boardLoadComplete: isCurrentBoardLoadComplete(),
|
|
7441
|
+
boardLoadCompletedAt: _lastCompletedBoardLoadAt || 0
|
|
7442
|
+
}),
|
|
7372
7443
|
getRuntimeDiagnostics: () => ({
|
|
7373
7444
|
activeBoardId: _currentBoardId,
|
|
7374
7445
|
canvasPhase: moduleInstance?.canvasPhase || null,
|
|
7446
|
+
isLoading: moduleInstance?.isLoading === true,
|
|
7447
|
+
boardLoadComplete: isCurrentBoardLoadComplete(),
|
|
7448
|
+
nodeCount: moduleInstance?.nodeObjectsById?.size || 0,
|
|
7375
7449
|
buildInfo: window.MindCanvasBuildInfo?.dump?.() || null,
|
|
7376
7450
|
guardCounts: window.MindCanvasDevGuards?.getCounts?.() || {},
|
|
7377
7451
|
trace: {
|
|
@@ -7968,6 +8042,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
|
|
|
7968
8042
|
|
|
7969
8043
|
// Backward-compatible alias: MindCanvas is the new page/internal name, but the underlying surface is the same.
|
|
7970
8044
|
window.mindCanvas = window.mindMap;
|
|
8045
|
+
window.addRemoteFleetMonitorNode = () => window.mindMap?.addRemoteFleetMonitor?.();
|
|
7971
8046
|
window.MindMapCoreBuildInfo = {
|
|
7972
8047
|
build: MINDMAP_CORE_BUILD_ID,
|
|
7973
8048
|
hasMovingUpdateDue: true,
|