@mindexec/cli 0.2.457 → 0.2.459
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/desktop-workspace-state.cjs +120 -0
- package/electron/main.cjs +17 -6
- package/electron/source-smoke.mjs +21 -0
- package/electron/windows-package-smoke.mjs +8 -2
- package/package.json +6 -5
- package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
- package/scripts/desktop-workspace-state-smoke.mjs +81 -0
- package/scripts/remote-fleet-render-smoke.mjs +29 -186
- package/server.js +36 -16
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-animated-image-preview.js +270 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +57 -53
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +51 -346
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +30 -26
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +94 -124
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-menu-manager.js +43 -21
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +45 -13
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-render-plan.js +117 -1
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js +4 -1
- package/wwwroot/_framework/{MindExecution.Core.speado072l.dll → MindExecution.Core.b973f5f64y.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.mjy31ssdac.dll → MindExecution.Kernel.k4h9fvi9wb.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.vbjjmdaao2.dll → MindExecution.Plugins.Admin.u2emae8cb5.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.0cr5jc6wqe.dll → MindExecution.Plugins.Business.cysw4qtyke.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.m7p666e0jd.dll → MindExecution.Plugins.Concept.8iehgvzjio.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.kmdtirnljl.dll → MindExecution.Plugins.Directory.t621fsxq2i.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.m1pfeozacj.dll → MindExecution.Plugins.PlanMaster.ft631uc7ki.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.nvsc9q4s6b.dll → MindExecution.Plugins.YouTube.arivgu92h1.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.9jaznballd.dll → MindExecution.Shared.wm997st9eb.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Web.k5jxwawryl.dll → MindExecution.Web.osdbdrue4h.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +21 -21
- package/wwwroot/app-icon-1024.png +0 -0
- package/wwwroot/apple-touch-icon.png +0 -0
- package/wwwroot/appsettings.json +81 -81
- package/wwwroot/favicon-32x32.png +0 -0
- package/wwwroot/favicon.ico +0 -0
- package/wwwroot/icon-192.png +0 -0
- package/wwwroot/icon-512.png +0 -0
- package/wwwroot/index.html +750 -726
- package/wwwroot/manifest.webmanifest +4 -4
- package/wwwroot/mindexec-favicon-v3.png +0 -0
- package/wwwroot/service-worker-assets.js +888 -880
- package/wwwroot/service-worker.js +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const DESKTOP_WORKSPACE_STATE_VERSION = 1;
|
|
7
|
+
|
|
8
|
+
function normalizeWorkspacePath(value) {
|
|
9
|
+
const candidate = String(value || '').trim();
|
|
10
|
+
return candidate ? path.resolve(candidate) : '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isExistingDirectory(candidatePath) {
|
|
14
|
+
if (!candidatePath) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
return fs.statSync(candidatePath).isDirectory();
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function loadDesktopWorkspaceState(statePath) {
|
|
26
|
+
const resolvedStatePath = normalizeWorkspacePath(statePath);
|
|
27
|
+
if (!resolvedStatePath) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const payload = JSON.parse(fs.readFileSync(resolvedStatePath, 'utf8'));
|
|
33
|
+
const workspacePath = normalizeWorkspacePath(payload?.workspacePath);
|
|
34
|
+
if (Number(payload?.version) !== DESKTOP_WORKSPACE_STATE_VERSION || !workspacePath) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
version: DESKTOP_WORKSPACE_STATE_VERSION,
|
|
40
|
+
workspacePath
|
|
41
|
+
};
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveDesktopWorkspace(options = {}) {
|
|
48
|
+
const explicitWorkspace = normalizeWorkspacePath(options.explicitWorkspace);
|
|
49
|
+
if (explicitWorkspace) {
|
|
50
|
+
return {
|
|
51
|
+
workspacePath: explicitWorkspace,
|
|
52
|
+
source: 'explicit'
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const savedState = loadDesktopWorkspaceState(options.statePath);
|
|
57
|
+
if (savedState && isExistingDirectory(savedState.workspacePath)) {
|
|
58
|
+
return {
|
|
59
|
+
workspacePath: savedState.workspacePath,
|
|
60
|
+
source: 'saved'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const fallbackWorkspace = normalizeWorkspacePath(options.fallbackWorkspace);
|
|
65
|
+
if (!fallbackWorkspace) {
|
|
66
|
+
throw new Error('A fallback desktop workspace is required.');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
workspacePath: fallbackWorkspace,
|
|
71
|
+
source: 'fallback'
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function saveDesktopWorkspaceState(statePath, workspacePath) {
|
|
76
|
+
const resolvedStatePath = normalizeWorkspacePath(statePath);
|
|
77
|
+
if (!resolvedStatePath) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const resolvedWorkspacePath = normalizeWorkspacePath(workspacePath);
|
|
82
|
+
if (!isExistingDirectory(resolvedWorkspacePath)) {
|
|
83
|
+
throw new Error(`Desktop workspace must be an existing directory: ${resolvedWorkspacePath || workspacePath}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fs.mkdirSync(path.dirname(resolvedStatePath), { recursive: true });
|
|
87
|
+
const payload = {
|
|
88
|
+
version: DESKTOP_WORKSPACE_STATE_VERSION,
|
|
89
|
+
workspacePath: resolvedWorkspacePath
|
|
90
|
+
};
|
|
91
|
+
const tempPath = `${resolvedStatePath}.${process.pid}.${Date.now()}.tmp`;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
95
|
+
encoding: 'utf8',
|
|
96
|
+
mode: 0o600
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
fs.renameSync(tempPath, resolvedStatePath);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error?.code !== 'EEXIST' && error?.code !== 'EPERM' && error?.code !== 'ENOTEMPTY') {
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fs.rmSync(resolvedStatePath, { force: true });
|
|
106
|
+
fs.renameSync(tempPath, resolvedStatePath);
|
|
107
|
+
}
|
|
108
|
+
} finally {
|
|
109
|
+
fs.rmSync(tempPath, { force: true });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
DESKTOP_WORKSPACE_STATE_VERSION,
|
|
117
|
+
loadDesktopWorkspaceState,
|
|
118
|
+
resolveDesktopWorkspace,
|
|
119
|
+
saveDesktopWorkspaceState
|
|
120
|
+
};
|
package/electron/main.cjs
CHANGED
|
@@ -6,6 +6,9 @@ const fs = require('fs');
|
|
|
6
6
|
const http = require('http');
|
|
7
7
|
const net = require('net');
|
|
8
8
|
const path = require('path');
|
|
9
|
+
const workspaceState = require('../desktop-workspace-state.cjs');
|
|
10
|
+
|
|
11
|
+
const { resolveDesktopWorkspace } = workspaceState;
|
|
9
12
|
|
|
10
13
|
const DEFAULT_BRIDGE_PORT = 5147;
|
|
11
14
|
const DEFAULT_REMOTE_HUB_PORT = 5198;
|
|
@@ -20,6 +23,7 @@ let requestedExitCode = 0;
|
|
|
20
23
|
let activeBridgePort = DEFAULT_BRIDGE_PORT;
|
|
21
24
|
let activeRemoteHubPort = DEFAULT_REMOTE_HUB_PORT;
|
|
22
25
|
let activeWorkspace = '';
|
|
26
|
+
let desktopWorkspaceStatePath = '';
|
|
23
27
|
|
|
24
28
|
function resolveDesktopIconPath() {
|
|
25
29
|
const iconPath = path.resolve(__dirname, '..', 'wwwroot', 'icon-512.png');
|
|
@@ -121,6 +125,7 @@ function spawnBundledBridge(packageRoot) {
|
|
|
121
125
|
ELECTRON_RUN_AS_NODE: '1',
|
|
122
126
|
MINDEXEC_DESKTOP: '1',
|
|
123
127
|
MINDEXEC_NO_OPEN: '1',
|
|
128
|
+
MINDEXEC_DESKTOP_WORKSPACE_STATE_PATH: desktopWorkspaceStatePath,
|
|
124
129
|
WORKSPACE_PATH: activeWorkspace,
|
|
125
130
|
BRIDGE_PORT: String(activeBridgePort),
|
|
126
131
|
REMOTE_HUB_PORT: String(activeRemoteHubPort)
|
|
@@ -180,15 +185,21 @@ async function prepareBundledRuntime() {
|
|
|
180
185
|
'RemoteHub',
|
|
181
186
|
activeBridgePort
|
|
182
187
|
);
|
|
183
|
-
|
|
184
|
-
process.env.
|
|
185
|
-
||
|
|
186
|
-
|| path.join(app.getPath('documents'), 'MindExecution')
|
|
188
|
+
desktopWorkspaceStatePath = path.resolve(
|
|
189
|
+
process.env.MINDEXEC_DESKTOP_WORKSPACE_STATE_PATH
|
|
190
|
+
|| path.join(app.getPath('userData'), 'workspace-state.json')
|
|
187
191
|
);
|
|
192
|
+
const workspaceResolution = resolveDesktopWorkspace({
|
|
193
|
+
explicitWorkspace: process.env.MINDEXEC_DESKTOP_WORKSPACE || process.env.WORKSPACE_PATH,
|
|
194
|
+
statePath: desktopWorkspaceStatePath,
|
|
195
|
+
fallbackWorkspace: path.join(app.getPath('documents'), 'MindExecution')
|
|
196
|
+
});
|
|
197
|
+
activeWorkspace = workspaceResolution.workspacePath;
|
|
188
198
|
|
|
189
199
|
fs.mkdirSync(activeWorkspace, { recursive: true });
|
|
190
200
|
appendDesktopLog(
|
|
191
|
-
`Starting bundled runtime: bridge=${activeBridgePort}, remoteHub=${activeRemoteHubPort},
|
|
201
|
+
`Starting bundled runtime: bridge=${activeBridgePort}, remoteHub=${activeRemoteHubPort}, ` +
|
|
202
|
+
`workspace=${activeWorkspace}, workspaceSource=${workspaceResolution.source}`
|
|
192
203
|
);
|
|
193
204
|
spawnBundledBridge(packageRoot);
|
|
194
205
|
return waitForBundledBridge();
|
|
@@ -203,7 +214,7 @@ function createMainWindow() {
|
|
|
203
214
|
minHeight: 640,
|
|
204
215
|
show: !hiddenSmokeMode,
|
|
205
216
|
autoHideMenuBar: true,
|
|
206
|
-
backgroundColor: '#
|
|
217
|
+
backgroundColor: '#f8fafc',
|
|
207
218
|
title: 'MindExec',
|
|
208
219
|
icon: resolveDesktopIconPath(),
|
|
209
220
|
webPreferences: {
|
|
@@ -8,12 +8,14 @@ const electronDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
|
8
8
|
const packageRoot = path.resolve(electronDirectory, '..');
|
|
9
9
|
const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
10
10
|
const mainSource = fs.readFileSync(path.join(electronDirectory, 'main.cjs'), 'utf8');
|
|
11
|
+
const serverSource = fs.readFileSync(path.join(packageRoot, 'server.js'), 'utf8');
|
|
11
12
|
|
|
12
13
|
assert.equal(packageJson.build?.appId, 'com.mindexec.desktop', 'Stable desktop appId is required.');
|
|
13
14
|
assert.equal(packageJson.build?.productName, 'MindExec', 'Desktop product name changed unexpectedly.');
|
|
14
15
|
assert.equal(packageJson.build?.extraMetadata?.main, 'electron/main.cjs', 'Packaged Electron entry point is missing.');
|
|
15
16
|
assert.equal(packageJson.build?.asar, false, 'Bundled runtime executables require an unpacked app directory.');
|
|
16
17
|
assert.ok(packageJson.files?.includes('electron/'), 'The npm package must include the Electron source contract.');
|
|
18
|
+
assert.ok(packageJson.files?.includes('desktop-workspace-state.cjs'), 'The npm package must include desktop workspace state.');
|
|
17
19
|
assert.match(packageJson.scripts?.['desktop:build:win'] || '', /electron-builder/, 'Windows build script is missing.');
|
|
18
20
|
assert.match(packageJson.scripts?.['test:desktop:win'] || '', /windows-package-smoke/, 'Packaged smoke test is missing.');
|
|
19
21
|
|
|
@@ -27,6 +29,21 @@ assert.ok(fs.existsSync(builderIconPath), `Electron builder icon is missing: ${b
|
|
|
27
29
|
const builderIconMetadata = await sharp(builderIconPath).metadata();
|
|
28
30
|
assert.equal(builderIconMetadata.width, 1024, 'Electron builder icon must be 1024px wide.');
|
|
29
31
|
assert.equal(builderIconMetadata.height, 1024, 'Electron builder icon must be 1024px tall.');
|
|
32
|
+
assert.equal(builderIconMetadata.hasAlpha, true, 'Electron builder icon must preserve transparency.');
|
|
33
|
+
const { data: builderIconPixels, info: builderIconInfo } = await sharp(builderIconPath)
|
|
34
|
+
.ensureAlpha()
|
|
35
|
+
.raw()
|
|
36
|
+
.toBuffer({ resolveWithObject: true });
|
|
37
|
+
const builderIconCornerAlpha = [
|
|
38
|
+
builderIconPixels[3],
|
|
39
|
+
builderIconPixels[(builderIconInfo.width - 1) * 4 + 3],
|
|
40
|
+
builderIconPixels[(builderIconInfo.height - 1) * builderIconInfo.width * 4 + 3],
|
|
41
|
+
builderIconPixels[(builderIconInfo.width * builderIconInfo.height - 1) * 4 + 3]
|
|
42
|
+
];
|
|
43
|
+
assert.ok(
|
|
44
|
+
builderIconCornerAlpha.every((alpha) => alpha === 0),
|
|
45
|
+
'Electron builder icon must have fully transparent corners.'
|
|
46
|
+
);
|
|
30
47
|
|
|
31
48
|
const targets = packageJson.build?.win?.target || [];
|
|
32
49
|
assert.ok(targets.some((target) => target.target === 'nsis'), 'NSIS target is required.');
|
|
@@ -43,6 +60,10 @@ assert.doesNotMatch(mainSource, /adopted|probeBridge/, 'Electron must not adopt
|
|
|
43
60
|
assert.match(mainSource, /MINDEXEC_DESKTOP_SMOKE_RESULT/, 'Packaged runtime smoke contract is missing.');
|
|
44
61
|
assert.match(mainSource, /icon:\s*resolveDesktopIconPath\(\)/, 'Electron windows must use the packaged MindExec icon.');
|
|
45
62
|
assert.match(mainSource, /app\.setAppUserModelId\(DESKTOP_APP_ID\)/, 'Windows taskbar identity must use the stable app id.');
|
|
63
|
+
assert.match(mainSource, /resolveDesktopWorkspace/, 'Electron startup must restore the last desktop workspace.');
|
|
64
|
+
assert.match(mainSource, /MINDEXEC_DESKTOP_WORKSPACE_STATE_PATH/, 'Electron must share its workspace state path with LocalBridge.');
|
|
65
|
+
assert.match(serverSource, /saveDesktopWorkspaceState\(DESKTOP_WORKSPACE_STATE_PATH, resolvedPath\)/,
|
|
66
|
+
'A successful desktop workspace switch must be saved for the next launch.');
|
|
46
67
|
|
|
47
68
|
console.log(JSON.stringify({
|
|
48
69
|
ok: true,
|
|
@@ -5,6 +5,9 @@ import net from 'node:net';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import desktopWorkspaceState from '../desktop-workspace-state.cjs';
|
|
9
|
+
|
|
10
|
+
const { saveDesktopWorkspaceState } = desktopWorkspaceState;
|
|
8
11
|
|
|
9
12
|
const electronDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
10
13
|
const packageRoot = path.resolve(electronDirectory, '..');
|
|
@@ -85,9 +88,11 @@ function canBind(port) {
|
|
|
85
88
|
const bridgePort = await reservePort();
|
|
86
89
|
const remoteHubPort = await reservePort();
|
|
87
90
|
const smokeRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mindexec-electron-smoke-'));
|
|
88
|
-
const workspace = path.join(smokeRoot, 'workspace');
|
|
91
|
+
const workspace = path.join(smokeRoot, 'saved-workspace');
|
|
89
92
|
const resultPath = path.join(smokeRoot, 'result.json');
|
|
93
|
+
const workspaceStatePath = path.join(smokeRoot, 'workspace-state.json');
|
|
90
94
|
fs.mkdirSync(workspace, { recursive: true });
|
|
95
|
+
saveDesktopWorkspaceState(workspaceStatePath, workspace);
|
|
91
96
|
|
|
92
97
|
let child;
|
|
93
98
|
try {
|
|
@@ -97,7 +102,7 @@ try {
|
|
|
97
102
|
...process.env,
|
|
98
103
|
MINDEXEC_DESKTOP_SMOKE: '1',
|
|
99
104
|
MINDEXEC_DESKTOP_SMOKE_RESULT: resultPath,
|
|
100
|
-
|
|
105
|
+
MINDEXEC_DESKTOP_WORKSPACE_STATE_PATH: workspaceStatePath,
|
|
101
106
|
MINDEXEC_NO_OPEN: '1',
|
|
102
107
|
BRIDGE_PORT: String(bridgePort),
|
|
103
108
|
REMOTE_HUB_PORT: String(remoteHubPort)
|
|
@@ -123,6 +128,7 @@ try {
|
|
|
123
128
|
assert.equal(result.runtimeMode, 'bundled', 'Electron did not use its bundled runtime.');
|
|
124
129
|
assert.equal(result.bridgePort, bridgePort, 'Packaged shell did not use the requested Bridge port.');
|
|
125
130
|
assert.equal(result.remoteHubPort, remoteHubPort, 'Packaged shell did not use the requested RemoteHub port.');
|
|
131
|
+
assert.equal(path.resolve(result.workspace), path.resolve(workspace), 'Packaged Electron did not restore the saved workspace.');
|
|
126
132
|
|
|
127
133
|
const exitCode = await waitForExit(child, 15_000);
|
|
128
134
|
assert.equal(exitCode, 0, `Packaged Electron smoke exited with code ${exitCode}.`);
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindexec/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.459",
|
|
4
4
|
"description": "MindExec local runtime and bridge CLI",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
8
8
|
"start-bridge.bat",
|
|
9
9
|
"start-bridge.sh",
|
|
10
|
-
"launch-bridge.cjs",
|
|
11
|
-
"
|
|
10
|
+
"launch-bridge.cjs",
|
|
11
|
+
"desktop-workspace-state.cjs",
|
|
12
|
+
"server.js",
|
|
12
13
|
"remote-hub.js",
|
|
13
14
|
"lzo1x.js",
|
|
14
15
|
"mode4-atlas.js",
|
|
@@ -28,9 +29,9 @@
|
|
|
28
29
|
"dev": "node launch-bridge.cjs --watch",
|
|
29
30
|
"desktop:start": "electron electron/main.cjs",
|
|
30
31
|
"desktop:build:win": "electron-builder --win --x64 --publish never",
|
|
31
|
-
"test:desktop": "node electron/source-smoke.mjs",
|
|
32
|
+
"test:desktop": "node electron/source-smoke.mjs && node scripts/desktop-workspace-state-smoke.mjs",
|
|
32
33
|
"test:desktop:win": "node electron/windows-package-smoke.mjs",
|
|
33
|
-
"test:syntax": "node --check server.js && node --check remote-hub.js && node --check lzo1x.js && node --check mode4-atlas.js && node --check mode4-atlas-worker.js && node --check codex-runtime.js && node --check codex-model-catalog.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check electron/main.cjs && node --check electron/source-smoke.mjs && node --check electron/windows-package-smoke.mjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/npm-fresh-install-smoke.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/codex-model-catalog-smoke.mjs && node --check scripts/codex-sdk-runtime-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
|
|
34
|
+
"test:syntax": "node --check server.js && node --check remote-hub.js && node --check lzo1x.js && node --check mode4-atlas.js && node --check mode4-atlas-worker.js && node --check codex-runtime.js && node --check codex-model-catalog.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check desktop-workspace-state.cjs && node --check electron/main.cjs && node --check electron/source-smoke.mjs && node --check electron/windows-package-smoke.mjs && node --check scripts/desktop-workspace-state-smoke.mjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/npm-fresh-install-smoke.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/codex-model-catalog-smoke.mjs && node --check scripts/codex-sdk-runtime-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
|
|
34
35
|
"test:codex-sdk": "node scripts/codex-sdk-runtime-smoke.mjs",
|
|
35
36
|
"test:codex-sdk:live": "node scripts/codex-sdk-runtime-smoke.mjs --live",
|
|
36
37
|
"test:codex-sdk:live-tools": "node scripts/codex-sdk-runtime-smoke.mjs --live-tools",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import desktopWorkspaceState from '../desktop-workspace-state.cjs';
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
loadDesktopWorkspaceState,
|
|
9
|
+
resolveDesktopWorkspace,
|
|
10
|
+
saveDesktopWorkspaceState
|
|
11
|
+
} = desktopWorkspaceState;
|
|
12
|
+
|
|
13
|
+
const smokeRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mindexec-workspace-state-'));
|
|
14
|
+
const savedWorkspace = path.join(smokeRoot, 'saved-workspace');
|
|
15
|
+
const explicitWorkspace = path.join(smokeRoot, 'explicit-workspace');
|
|
16
|
+
const fallbackWorkspace = path.join(smokeRoot, 'fallback-workspace');
|
|
17
|
+
const missingWorkspace = path.join(smokeRoot, 'missing-workspace');
|
|
18
|
+
const statePath = path.join(smokeRoot, 'profile', 'workspace-state.json');
|
|
19
|
+
|
|
20
|
+
fs.mkdirSync(savedWorkspace, { recursive: true });
|
|
21
|
+
fs.mkdirSync(explicitWorkspace, { recursive: true });
|
|
22
|
+
fs.mkdirSync(fallbackWorkspace, { recursive: true });
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
assert.equal(saveDesktopWorkspaceState('', savedWorkspace), false, 'Non-desktop runtimes must remain a no-op.');
|
|
26
|
+
assert.equal(saveDesktopWorkspaceState(statePath, savedWorkspace), true);
|
|
27
|
+
assert.deepEqual(loadDesktopWorkspaceState(statePath), {
|
|
28
|
+
version: 1,
|
|
29
|
+
workspacePath: path.resolve(savedWorkspace)
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
assert.equal(saveDesktopWorkspaceState(statePath, explicitWorkspace), true);
|
|
33
|
+
assert.equal(loadDesktopWorkspaceState(statePath)?.workspacePath, path.resolve(explicitWorkspace),
|
|
34
|
+
'Changing folders must replace the prior desktop workspace state.');
|
|
35
|
+
assert.equal(saveDesktopWorkspaceState(statePath, savedWorkspace), true);
|
|
36
|
+
|
|
37
|
+
assert.deepEqual(resolveDesktopWorkspace({
|
|
38
|
+
statePath,
|
|
39
|
+
fallbackWorkspace
|
|
40
|
+
}), {
|
|
41
|
+
workspacePath: path.resolve(savedWorkspace),
|
|
42
|
+
source: 'saved'
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
assert.deepEqual(resolveDesktopWorkspace({
|
|
46
|
+
explicitWorkspace,
|
|
47
|
+
statePath,
|
|
48
|
+
fallbackWorkspace
|
|
49
|
+
}), {
|
|
50
|
+
workspacePath: path.resolve(explicitWorkspace),
|
|
51
|
+
source: 'explicit'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
fs.writeFileSync(statePath, `${JSON.stringify({ version: 1, workspacePath: missingWorkspace })}\n`, 'utf8');
|
|
55
|
+
assert.deepEqual(resolveDesktopWorkspace({
|
|
56
|
+
statePath,
|
|
57
|
+
fallbackWorkspace
|
|
58
|
+
}), {
|
|
59
|
+
workspacePath: path.resolve(fallbackWorkspace),
|
|
60
|
+
source: 'fallback'
|
|
61
|
+
});
|
|
62
|
+
assert.equal(fs.existsSync(missingWorkspace), false, 'A deleted saved workspace must not be recreated.');
|
|
63
|
+
|
|
64
|
+
fs.writeFileSync(statePath, '{broken json', 'utf8');
|
|
65
|
+
assert.deepEqual(resolveDesktopWorkspace({
|
|
66
|
+
statePath,
|
|
67
|
+
fallbackWorkspace
|
|
68
|
+
}), {
|
|
69
|
+
workspacePath: path.resolve(fallbackWorkspace),
|
|
70
|
+
source: 'fallback'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log(JSON.stringify({
|
|
74
|
+
ok: true,
|
|
75
|
+
restoredWorkspace: path.resolve(savedWorkspace),
|
|
76
|
+
invalidSavedWorkspaceFallsBack: true,
|
|
77
|
+
explicitWorkspaceWins: true
|
|
78
|
+
}));
|
|
79
|
+
} finally {
|
|
80
|
+
fs.rmSync(smokeRoot, { recursive: true, force: true });
|
|
81
|
+
}
|
|
@@ -1020,9 +1020,8 @@ try {
|
|
|
1020
1020
|
const patchCard = Array.from(cards).find(card => card.dataset.deviceId === patchTarget.DeviceId);
|
|
1021
1021
|
const patchPreview = patchCard?.querySelector('[data-remote-fleet-device-preview="tile"]');
|
|
1022
1022
|
assert.ok(patchPreview);
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
: 'thumbnail';
|
|
1023
|
+
assert.notEqual(patchPreview.dataset.remoteFleetFrameKind, 'live');
|
|
1024
|
+
const patchKind = 'thumbnail';
|
|
1026
1025
|
const patchUrl = `/api/remote/devices/${encodeURIComponent(patchTarget.DeviceId)}/${patchKind}?token=render-smoke-frame&seq=9999`;
|
|
1027
1026
|
const patchCount = manager.applyRemoteFleetFramePatchesForTest(bodyView, [{
|
|
1028
1027
|
deviceId: patchTarget.DeviceId,
|
|
@@ -1047,168 +1046,13 @@ try {
|
|
|
1047
1046
|
assert.equal(patchPreview.querySelector('img[data-remote-fleet-frame-image="true"]')?.dataset.remoteFleetFrameUrl, patchUrl);
|
|
1048
1047
|
assert.equal(patchPreview.querySelector('[data-remote-fleet-frame-badge="true"]'), null);
|
|
1049
1048
|
|
|
1050
|
-
const objectUrlCountBeforeBinary = diagnostics.objectUrlCalls.length;
|
|
1051
|
-
const imageBitmapCountBeforeBinary = diagnostics.imageBitmapCalls.length;
|
|
1052
|
-
const drawCountBeforeBinary = document.canvasDrawCalls.length;
|
|
1053
|
-
const binaryPatchCount = manager.applyRemoteFleetFramePatchesForTest(bodyView, [{
|
|
1054
|
-
deviceId: patchTarget.DeviceId,
|
|
1055
|
-
kind: 'live',
|
|
1056
|
-
frameSeq: 10000,
|
|
1057
|
-
frameUrl: '',
|
|
1058
|
-
streamId: 'render-smoke-binary-live',
|
|
1059
|
-
contentHash: 'render-smoke-binary-live-10000',
|
|
1060
|
-
receivedAt: new Date().toISOString(),
|
|
1061
|
-
_remoteFleetPayloadBlob: new Blob([new Uint8Array([0xff, 0xd8, 0xff, 0xd9])], { type: 'image/jpeg' }),
|
|
1062
|
-
_remoteFleetBinaryFrame: true
|
|
1063
|
-
}]);
|
|
1064
|
-
assert.equal(binaryPatchCount, 1);
|
|
1065
|
-
await wait(20);
|
|
1066
|
-
const binaryCanvas = patchPreview.querySelector('canvas[data-remote-fleet-frame-canvas="true"]');
|
|
1067
|
-
const binaryImage = patchPreview.querySelector('img[data-remote-fleet-frame-image="true"]');
|
|
1068
|
-
assert.equal(patchPreview.dataset.remoteFleetFrameKind, 'live');
|
|
1069
|
-
assert.equal(patchPreview.dataset.remoteFleetFrameSeq, '10000');
|
|
1070
|
-
assert.equal(binaryCanvas?.dataset.remoteFleetFrameSeq, '10000');
|
|
1071
|
-
assert.equal(binaryCanvas?.style.display, 'block');
|
|
1072
|
-
assert.equal(binaryImage?.style.display, 'none');
|
|
1073
|
-
assert.equal(diagnostics.imageBitmapCalls.length, imageBitmapCountBeforeBinary + 1);
|
|
1074
|
-
assert.equal(diagnostics.objectUrlCalls.length, objectUrlCountBeforeBinary);
|
|
1075
|
-
const binaryDrawCalls = document.canvasDrawCalls.slice(drawCountBeforeBinary);
|
|
1076
|
-
assert.ok(
|
|
1077
|
-
binaryDrawCalls.some(call => call.op === 'drawImage'),
|
|
1078
|
-
'expected binary Blob frame to draw directly to canvas');
|
|
1079
|
-
assert.equal(
|
|
1080
|
-
binaryDrawCalls.some(call => call.op === 'clearRect' || call.op === 'fillRect'),
|
|
1081
|
-
false,
|
|
1082
|
-
'binary Blob frame paint must not clear/fill the canvas before drawing');
|
|
1083
|
-
assert.ok(
|
|
1084
|
-
Number(moduleRef._forceUpdateFrames || 0) >= 2,
|
|
1085
|
-
'binary live frame commits must wake the passive MDM CSS3D frame loop');
|
|
1086
|
-
assert.equal(moduleRef._lastAnimationWakeReason, 'remote-live-css3d-frame');
|
|
1087
|
-
assert.ok(
|
|
1088
|
-
diagnostics.runtimeTrace.some(event => event.type === 'remote.frame.css3dWake'),
|
|
1089
|
-
'binary live frame commits must trace the MDM CSS3D wake path');
|
|
1090
|
-
const binaryCanvasBeforeRerender = binaryCanvas;
|
|
1091
|
-
|
|
1092
|
-
const liveOverlayCard = document.createElement('div');
|
|
1093
|
-
liveOverlayCard.dataset.nodeId = 'remote-fleet-render-smoke';
|
|
1094
|
-
liveOverlayCard.dataset.sourceKind = 'live';
|
|
1095
|
-
liveOverlayCard.dataset.liveInteractive = 'true';
|
|
1096
|
-
liveOverlayCard.style.display = 'block';
|
|
1097
|
-
moduleRef._textOverlayV2State.cards.set('selection:remote-fleet-render-smoke', liveOverlayCard);
|
|
1098
|
-
moduleRef._forceUpdateFrames = 0;
|
|
1099
|
-
moduleRef._animationWakeCount = 0;
|
|
1100
|
-
moduleRef._lastAnimationWakeReason = '';
|
|
1101
|
-
|
|
1102
|
-
await wait(20);
|
|
1103
|
-
const liveWs = diagnostics.webSocketConnections.find(connection =>
|
|
1104
|
-
String(connection?.url || '').includes('/api/remote/atlas/ws'));
|
|
1105
|
-
assert.ok(
|
|
1106
|
-
liveWs,
|
|
1107
|
-
`expected MDM render to open the Mode 4 Atlas WebSocket: ${diagnostics.webSocketConnections.map(connection => connection.url).join(', ')}`
|
|
1108
|
-
);
|
|
1109
|
-
assert.equal(liveWs.readyState, 1, 'Mode 4 Atlas WebSocket should be open before rerender');
|
|
1110
|
-
const wsCountBeforeRerender = diagnostics.webSocketConnections.length;
|
|
1111
|
-
const wsCloseCountBeforeRerender = liveWs.closeCount;
|
|
1112
|
-
const websocketFrameSeq = 10001;
|
|
1113
|
-
liveWs.onmessage?.({
|
|
1114
|
-
data: JSON.stringify({
|
|
1115
|
-
type: 'Mode4AtlasLayout',
|
|
1116
|
-
tiles: [{
|
|
1117
|
-
deviceId: patchTarget.DeviceId,
|
|
1118
|
-
x: 0,
|
|
1119
|
-
y: 0,
|
|
1120
|
-
width: 64,
|
|
1121
|
-
height: 36
|
|
1122
|
-
}]
|
|
1123
|
-
})
|
|
1124
|
-
});
|
|
1125
|
-
liveWs.onmessage?.({
|
|
1126
|
-
data: new Blob([encodeRemoteBinaryFrame({
|
|
1127
|
-
type: 'remote.frame.binary',
|
|
1128
|
-
deviceId: patchTarget.DeviceId,
|
|
1129
|
-
kind: 'live',
|
|
1130
|
-
frameSeq: websocketFrameSeq,
|
|
1131
|
-
streamId: 'render-smoke-ws-live',
|
|
1132
|
-
contentHash: `render-smoke-ws-live-${websocketFrameSeq}`,
|
|
1133
|
-
mimeType: 'image/jpeg',
|
|
1134
|
-
frameMode: 'mode4-h264-atlas',
|
|
1135
|
-
width: 64,
|
|
1136
|
-
height: 36,
|
|
1137
|
-
capturedAt: new Date().toISOString(),
|
|
1138
|
-
receivedAt: new Date().toISOString()
|
|
1139
|
-
})])
|
|
1140
|
-
});
|
|
1141
|
-
await waitFor(
|
|
1142
|
-
() => patchPreview.dataset.remoteFleetFrameSeq === String(websocketFrameSeq)
|
|
1143
|
-
);
|
|
1144
|
-
assert.equal(
|
|
1145
|
-
patchPreview.dataset.remoteFleetFrameSeq,
|
|
1146
|
-
String(websocketFrameSeq),
|
|
1147
|
-
JSON.stringify(diagnostics.runtimeTrace.slice(-12)));
|
|
1148
|
-
assert.ok(
|
|
1149
|
-
diagnostics.runtimeTrace.some(event =>
|
|
1150
|
-
event.type === 'remote.atlas.painted' &&
|
|
1151
|
-
Number(event.frameSeq || 0) === websocketFrameSeq),
|
|
1152
|
-
'Mode 4 Atlas frame must paint its configured device tile');
|
|
1153
|
-
|
|
1154
|
-
manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
|
|
1155
|
-
await wait(30);
|
|
1156
|
-
assert.equal(
|
|
1157
|
-
liveWs.closeCount,
|
|
1158
|
-
wsCloseCountBeforeRerender,
|
|
1159
|
-
'MDM rerender must not close the active Mode 4 Atlas WebSocket');
|
|
1160
|
-
assert.equal(
|
|
1161
|
-
diagnostics.webSocketConnections.length,
|
|
1162
|
-
wsCountBeforeRerender,
|
|
1163
|
-
'MDM rerender must reuse the active Mode 4 Atlas WebSocket instead of opening a replacement');
|
|
1164
|
-
const rerenderedPatchPreview = bodyView
|
|
1165
|
-
.querySelector(`article[data-device-id="${patchTarget.DeviceId}"]`)
|
|
1166
|
-
?.querySelector('[data-remote-fleet-device-preview="tile"]');
|
|
1167
|
-
assert.ok(rerenderedPatchPreview);
|
|
1168
|
-
assert.equal(rerenderedPatchPreview.dataset.remoteFleetFrameSeq, String(websocketFrameSeq));
|
|
1169
|
-
const rerenderedPatchCanvas = rerenderedPatchPreview.querySelector('canvas[data-remote-fleet-frame-canvas="true"]');
|
|
1170
|
-
assert.equal(rerenderedPatchCanvas?.style.display, 'block');
|
|
1171
|
-
assert.equal(
|
|
1172
|
-
rerenderedPatchCanvas,
|
|
1173
|
-
binaryCanvasBeforeRerender,
|
|
1174
|
-
'MDM rerender should reuse the already-painted canvas instead of flashing a blank replacement');
|
|
1175
|
-
|
|
1176
|
-
const otherBoardNodeId = 'remote-fleet-render-smoke-other-board';
|
|
1177
|
-
const { nodeShell: otherBoardShell, bodyView: otherBoardBodyView } =
|
|
1178
|
-
createRemoteFleetTemplateShell(document, focusedDevice.DeviceId, otherBoardNodeId);
|
|
1179
|
-
document.body.appendChild(otherBoardShell);
|
|
1180
|
-
manager.renderRemoteFleetMonitorForTest(
|
|
1181
|
-
otherBoardBodyView,
|
|
1182
|
-
buildMonitorNode(
|
|
1183
|
-
devices,
|
|
1184
|
-
hub.getStatus({ includeSecrets: true }),
|
|
1185
|
-
latestTaskBatch,
|
|
1186
|
-
recentTaskBatches,
|
|
1187
|
-
'',
|
|
1188
|
-
otherBoardNodeId));
|
|
1189
|
-
await wait(30);
|
|
1190
|
-
const otherBoardPatchPreview = otherBoardBodyView
|
|
1191
|
-
.querySelector(`article[data-device-id="${patchTarget.DeviceId}"]`)
|
|
1192
|
-
?.querySelector('[data-remote-fleet-device-preview="tile"]');
|
|
1193
|
-
assert.ok(otherBoardPatchPreview);
|
|
1194
|
-
assert.ok(
|
|
1195
|
-
diagnostics.webSocketConnections.length > wsCountBeforeRerender,
|
|
1196
|
-
'a different-board MDM must own a separate Mode 4 Atlas session');
|
|
1197
|
-
assert.ok(diagnostics.runtimeTrace.some(event =>
|
|
1198
|
-
event.type === 'remote.atlas.wsOpen'
|
|
1199
|
-
&& event.nodeId === otherBoardNodeId));
|
|
1200
|
-
otherBoardShell.remove();
|
|
1201
|
-
|
|
1202
1049
|
const controlCard = bodyView.querySelector(`article[data-device-id="${patchTarget.DeviceId}"]`);
|
|
1203
1050
|
assert.ok(controlCard);
|
|
1204
1051
|
controlCard.dispatchEvent({ type: 'dblclick', button: 0 });
|
|
1205
1052
|
await wait(30);
|
|
1206
1053
|
assert.equal(document.body.querySelector('[data-remote-fleet-control-popup="true"]'), null);
|
|
1207
|
-
const addControlNodeCall = dotNetCalls.find(call =>
|
|
1208
|
-
|
|
1209
|
-
&& call.args[0] === 'remote-fleet-render-smoke'
|
|
1210
|
-
&& call.args[1] === patchTarget.DeviceId);
|
|
1211
|
-
assert.ok(addControlNodeCall, 'double-clicking a screen tile should create a remote workstation canvas node');
|
|
1054
|
+
const addControlNodeCall = dotNetCalls.find(call => call.methodName === 'AddRemoteFleetDeviceNodeFromJs');
|
|
1055
|
+
assert.equal(addControlNodeCall, undefined, 'MDM thumbnails must not create an expanded device node');
|
|
1212
1056
|
|
|
1213
1057
|
const alternateDevice = devices.find(device =>
|
|
1214
1058
|
device.Connected === true
|
|
@@ -1224,10 +1068,20 @@ try {
|
|
|
1224
1068
|
assert.equal(selectedAfterClick?.querySelector('[data-remote-fleet-selected-indicator="true"]'), null);
|
|
1225
1069
|
const livePanel = bodyView.querySelector('[data-remote-fleet-live-panel="true"]');
|
|
1226
1070
|
assert.equal(livePanel, null);
|
|
1227
|
-
assert.ok(bodyView.querySelector('[data-remote-fleet-action="task-
|
|
1071
|
+
assert.ok(bodyView.querySelector('[data-remote-fleet-action="task-selected"]'));
|
|
1228
1072
|
assert.ok(bodyView.querySelector('[data-remote-fleet-action="task-connected"]'));
|
|
1229
1073
|
assert.ok(bodyView.querySelector('[data-remote-fleet-task-input="true"]'));
|
|
1230
1074
|
assert.ok(bodyView.querySelector('[data-remote-fleet-ai-toggle="true"]'));
|
|
1075
|
+
const selectedTaskInput = bodyView.querySelector('[data-remote-fleet-task-input="true"]');
|
|
1076
|
+
const selectedTaskButton = bodyView.querySelector('[data-remote-fleet-action="task-selected"]');
|
|
1077
|
+
selectedTaskInput.value = 'Inspect Git status and report the result.';
|
|
1078
|
+
selectedTaskButton.dispatchEvent({ type: 'click' });
|
|
1079
|
+
await wait(20);
|
|
1080
|
+
const selectedTaskCall = dotNetCalls.find(call =>
|
|
1081
|
+
call.methodName === 'DispatchRemoteFleetTaskFromJs'
|
|
1082
|
+
&& call.args[0] === 'remote-fleet-render-smoke'
|
|
1083
|
+
&& call.args[1] === alternateDevice.DeviceId);
|
|
1084
|
+
assert.ok(selectedTaskCall, 'selected thumbnail must target its agent command');
|
|
1231
1085
|
assert.equal(bodyView.textContent.includes('all devices, no paging'), false);
|
|
1232
1086
|
assert.equal(bodyView.textContent.includes('Host: Inactive'), false);
|
|
1233
1087
|
assert.equal(bodyView.textContent.includes('No screen'), false);
|
|
@@ -1290,28 +1144,17 @@ try {
|
|
|
1290
1144
|
await wait(20);
|
|
1291
1145
|
const liveStartCalls = dotNetCalls
|
|
1292
1146
|
.filter(call => call.methodName === 'StartRemoteFleetLiveStreamFromJs');
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
.
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
assert.ok(diagnostics.fetchCalls.some(call => call.url.includes('/api/status?remoteFrames=ws')));
|
|
1305
|
-
assert.ok(diagnostics.webSocketConnections.length >= 1, 'expected MDM render to open Mode 4 Atlas WebSocket');
|
|
1306
|
-
assert.ok(atlasConfigure, 'expected MDM render to configure the Mode 4 Atlas WebSocket');
|
|
1307
|
-
assert.equal(atlasConfigure.fps, 2);
|
|
1308
|
-
assert.equal(atlasConfigure.width, 1920);
|
|
1309
|
-
assert.equal(atlasConfigure.height, 1080);
|
|
1310
|
-
assert.equal(atlasConfigure.tileWidth, 320);
|
|
1311
|
-
assert.equal(atlasConfigure.tileHeight, 180);
|
|
1312
|
-
assert.ok(atlasConfigure.deviceIds.length > 1, 'expected visible connected devices in Atlas configuration');
|
|
1313
|
-
assert.ok(diagnostics.runtimeTrace.some(event => event.type === 'remote.live.wsAutoStartRequested'));
|
|
1314
|
-
assert.equal(liveStartCalls.length, 0, 'WebSocket live path must not call DotNet live-start fallback');
|
|
1147
|
+
assert.equal(liveStartCalls.length, 0, 'thumbnail-only MDM must not start live streams');
|
|
1148
|
+
assert.equal(
|
|
1149
|
+
diagnostics.webSocketConnections.some(connection =>
|
|
1150
|
+
String(connection?.url || '').includes('/api/remote/atlas/ws')
|
|
1151
|
+
|| String(connection?.url || '').includes('/api/remote/frames/ws')),
|
|
1152
|
+
false,
|
|
1153
|
+
'thumbnail-only MDM must not open live frame WebSockets');
|
|
1154
|
+
assert.equal(
|
|
1155
|
+
diagnostics.fetchCalls.some(call => call.url.includes('/api/status?remoteFrames=ws')),
|
|
1156
|
+
false,
|
|
1157
|
+
'thumbnail-only MDM must not initialize live-frame transport');
|
|
1315
1158
|
await wait(320);
|
|
1316
1159
|
assert.ok(dotNetCalls.some(call => call.methodName === 'RefreshRemoteFleetMonitorNodeFromJs'));
|
|
1317
1160
|
assert.equal(bodyView.dataset.remoteFleetTaskFollowKey, 'render-smoke-batch');
|
|
@@ -1321,9 +1164,9 @@ try {
|
|
|
1321
1164
|
assert.ok(feedback);
|
|
1322
1165
|
|
|
1323
1166
|
assert.ok(bodyView.querySelector('[data-remote-fleet-ai-toggle="true"]'));
|
|
1324
|
-
assert.
|
|
1325
|
-
call.methodName === '
|
|
1326
|
-
|
|
1167
|
+
assert.ok(dotNetCalls.some(call =>
|
|
1168
|
+
call.methodName === 'DispatchRemoteFleetTaskFromJs'
|
|
1169
|
+
&& call.args[1] === alternateDevice.DeviceId));
|
|
1327
1170
|
|
|
1328
1171
|
const { nodeShell: emptyShell, bodyView: emptyBody } = createRemoteFleetTemplateShell(document);
|
|
1329
1172
|
document.body.appendChild(emptyShell);
|