@mindexec/cli 0.2.494 → 0.2.496

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.
Files changed (30) hide show
  1. package/electron/main.cjs +108 -7
  2. package/electron/native-renderer-owner-smoke.mjs +387 -0
  3. package/electron/native-renderer-owner.cjs +1044 -0
  4. package/electron/preload.cjs +12 -0
  5. package/electron/source-smoke.mjs +57 -0
  6. package/electron/windows-package-smoke.mjs +100 -0
  7. package/package.json +18 -8
  8. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  9. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  10. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  11. package/scripts/desktop-update-publisher-smoke.mjs +33 -5
  12. package/scripts/publish-mindexec-desktop-updates.mjs +34 -5
  13. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +3 -0
  14. package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +21 -1
  15. package/wwwroot/_content/MindExecution.Shared/js/mind-map-native-render-mirror.js +586 -0
  16. package/wwwroot/_content/MindExecution.Shared/native-core/native-core-manifest.json +2 -2
  17. package/wwwroot/_framework/{MindExecution.Core.tre9kny2uo.dll → MindExecution.Core.s6sxjy1v7h.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Kernel.lvfnhwdksf.dll → MindExecution.Kernel.yta9wud6wh.dll} +0 -0
  19. package/wwwroot/_framework/{MindExecution.Plugins.Admin.k2vbe4lt64.dll → MindExecution.Plugins.Admin.7aytj09ejo.dll} +0 -0
  20. package/wwwroot/_framework/{MindExecution.Plugins.Business.u7i3r45xp5.dll → MindExecution.Plugins.Business.wzj1pi81by.dll} +0 -0
  21. package/wwwroot/_framework/{MindExecution.Plugins.Concept.7eoasvev17.dll → MindExecution.Plugins.Concept.o6eyyttldg.dll} +0 -0
  22. package/wwwroot/_framework/{MindExecution.Plugins.Directory.rhh13u0z6c.dll → MindExecution.Plugins.Directory.rkqhevehjj.dll} +0 -0
  23. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.xzl2x2cqk4.dll → MindExecution.Plugins.PlanMaster.qfmuwz1kj6.dll} +0 -0
  24. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.945tf0zc9m.dll → MindExecution.Plugins.YouTube.p6hp1w5nfq.dll} +0 -0
  25. package/wwwroot/_framework/{MindExecution.Shared.e0w408b0ti.dll → MindExecution.Shared.b4arhy9bqr.dll} +0 -0
  26. package/wwwroot/_framework/{MindExecution.Web.vc6ux7xjcw.dll → MindExecution.Web.hlrui65yuu.dll} +0 -0
  27. package/wwwroot/_framework/blazor.boot.json +21 -21
  28. package/wwwroot/index.html +2 -1
  29. package/wwwroot/service-worker-assets.js +30 -26
  30. package/wwwroot/service-worker.js +1 -1
@@ -0,0 +1,1044 @@
1
+ 'use strict';
2
+
3
+ const { spawn } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const PROTOCOL_MAGIC = 0x31524e4d;
8
+ const PROTOCOL_VERSION = 1;
9
+ const HEADER_BYTES = 12;
10
+ const MAX_PAYLOAD_BYTES = 32 * 1024 * 1024;
11
+ const READY_TIMEOUT_MS = 10_000;
12
+ const RESPONSE_TIMEOUT_MS = 5_000;
13
+ const STOP_TIMEOUT_MS = 5_000;
14
+ const FORCE_EXIT_TIMEOUT_MS = 5_000;
15
+ const MAX_BOARD_ID_BYTES = 256;
16
+ const MAX_NODE_COUNT = 250_000;
17
+ const MAX_EDGE_COUNT = 500_000;
18
+ const MAX_EDGE_POINT_COUNT = 2_000_000;
19
+ const MAX_PENDING_REQUESTS = 8;
20
+
21
+ const LIFECYCLE_STATE = Object.freeze({
22
+ stopped: 'stopped',
23
+ starting: 'starting',
24
+ ready: 'ready',
25
+ stopping: 'stopping',
26
+ failed: 'failed',
27
+ stopFailed: 'stop-failed'
28
+ });
29
+
30
+ const REQUEST_TYPE = Object.freeze({
31
+ snapshot: 1,
32
+ camera: 2,
33
+ detach: 3,
34
+ shutdown: 4
35
+ });
36
+
37
+ const RESPONSE_TYPE = Object.freeze({
38
+ ready: 0x8001,
39
+ accepted: 0x8002,
40
+ rejected: 0x8003,
41
+ shutdown: 0x8004
42
+ });
43
+
44
+ const MAX_UINT32 = 0xffff_ffff;
45
+ const MAX_UINT64 = 0xffff_ffff_ffff_ffffn;
46
+
47
+ function createProtocolError(code) {
48
+ const error = new Error(code);
49
+ error.code = code;
50
+ return error;
51
+ }
52
+
53
+ function toFiniteNumber(value, name) {
54
+ const number = Number(value);
55
+ if (!Number.isFinite(number)) {
56
+ throw createProtocolError(`native-renderer-invalid-${name}`);
57
+ }
58
+ return number;
59
+ }
60
+
61
+ function toUint32(value, name) {
62
+ const number = Number(value);
63
+ if (!Number.isInteger(number) || number < 0 || number > MAX_UINT32) {
64
+ throw createProtocolError(`native-renderer-invalid-${name}`);
65
+ }
66
+ return number;
67
+ }
68
+
69
+ function toUint64(value, name) {
70
+ let result;
71
+ try {
72
+ result = typeof value === 'bigint' ? value : BigInt(value);
73
+ } catch {
74
+ throw createProtocolError(`native-renderer-invalid-${name}`);
75
+ }
76
+ if (result < 0n || result > MAX_UINT64) {
77
+ throw createProtocolError(`native-renderer-invalid-${name}`);
78
+ }
79
+ if (typeof value === 'number' && !Number.isSafeInteger(value)) {
80
+ throw createProtocolError(`native-renderer-unsafe-${name}`);
81
+ }
82
+ return result;
83
+ }
84
+
85
+ function encodeUtf8U16(value, name, { allowEmpty = false, maxBytes = 0xffff } = {}) {
86
+ if (String(value ?? '').includes('\0')) {
87
+ throw createProtocolError(`native-renderer-invalid-${name}`);
88
+ }
89
+ const encoded = Buffer.from(String(value ?? ''), 'utf8');
90
+ if ((!allowEmpty && encoded.length === 0) || encoded.length > maxBytes) {
91
+ throw createProtocolError(`native-renderer-invalid-${name}`);
92
+ }
93
+ return encoded;
94
+ }
95
+
96
+ function normalizeOwner(owner) {
97
+ if (!owner || typeof owner !== 'object' || Array.isArray(owner)) {
98
+ throw createProtocolError('native-renderer-invalid-owner');
99
+ }
100
+ const normalized = {
101
+ boardId: encodeUtf8U16(owner.boardId, 'board-id', { maxBytes: MAX_BOARD_ID_BYTES }),
102
+ engineGeneration: toUint64(owner.engineGeneration, 'engine-generation'),
103
+ boardEpoch: toUint64(owner.boardEpoch, 'board-epoch'),
104
+ boardRevision: toUint64(owner.boardRevision, 'board-revision'),
105
+ requestSequence: toUint64(owner.requestSequence, 'request-sequence')
106
+ };
107
+ if (
108
+ normalized.engineGeneration === 0n ||
109
+ normalized.boardEpoch === 0n ||
110
+ normalized.boardRevision === 0n ||
111
+ normalized.requestSequence === 0n
112
+ ) {
113
+ throw createProtocolError('native-renderer-owner-token-zero');
114
+ }
115
+ return normalized;
116
+ }
117
+
118
+ function normalizedOwnerKey(requestType, owner) {
119
+ return [
120
+ requestType,
121
+ owner.boardId.toString('hex'),
122
+ owner.engineGeneration,
123
+ owner.boardEpoch,
124
+ owner.boardRevision,
125
+ owner.requestSequence
126
+ ].join(':');
127
+ }
128
+
129
+ function normalizeCamera(camera) {
130
+ if (!camera || typeof camera !== 'object' || Array.isArray(camera)) {
131
+ throw createProtocolError('native-renderer-invalid-camera');
132
+ }
133
+ const normalized = {
134
+ x: toFiniteNumber(camera.x, 'camera-x'),
135
+ y: toFiniteNumber(camera.y, 'camera-y'),
136
+ z: toFiniteNumber(camera.z, 'camera-z'),
137
+ near: toFiniteNumber(camera.near, 'camera-near'),
138
+ far: toFiniteNumber(camera.far, 'camera-far'),
139
+ fov: toFiniteNumber(camera.fov, 'camera-fov'),
140
+ viewportW: toFiniteNumber(camera.viewportW, 'camera-viewport-w'),
141
+ viewportH: toFiniteNumber(camera.viewportH, 'camera-viewport-h'),
142
+ zoom: toFiniteNumber(camera.zoom, 'camera-zoom')
143
+ };
144
+ if (
145
+ normalized.near <= 0 ||
146
+ normalized.far <= normalized.near ||
147
+ normalized.fov <= 0 ||
148
+ normalized.fov >= 180 ||
149
+ normalized.viewportW <= 0 ||
150
+ normalized.viewportH <= 0 ||
151
+ normalized.zoom <= 0
152
+ ) {
153
+ throw createProtocolError('native-renderer-invalid-camera-range');
154
+ }
155
+ return normalized;
156
+ }
157
+
158
+ function normalizeNodes(nodes) {
159
+ if (!Array.isArray(nodes)) {
160
+ throw createProtocolError('native-renderer-invalid-nodes');
161
+ }
162
+ if (nodes.length > MAX_NODE_COUNT) {
163
+ throw createProtocolError('native-renderer-too-many-nodes');
164
+ }
165
+ const handles = new Set();
166
+ return nodes.map((node, index) => {
167
+ if (!node || typeof node !== 'object' || Array.isArray(node)) {
168
+ throw createProtocolError(`native-renderer-invalid-node-${index}`);
169
+ }
170
+ const handle = toUint32(node.handle, `node-handle-${index}`);
171
+ if (handle === 0 || handles.has(handle)) {
172
+ throw createProtocolError(`native-renderer-duplicate-node-handle-${index}`);
173
+ }
174
+ handles.add(handle);
175
+ const normalized = {
176
+ handle,
177
+ x: toFiniteNumber(node.x, `node-x-${index}`),
178
+ y: toFiniteNumber(node.y, `node-y-${index}`),
179
+ z: toFiniteNumber(node.z, `node-z-${index}`),
180
+ width: toFiniteNumber(node.width, `node-width-${index}`),
181
+ height: toFiniteNumber(node.height, `node-height-${index}`),
182
+ fillRgba: toUint32(node.fillRgba, `node-fill-${index}`)
183
+ };
184
+ if (normalized.width <= 0 || normalized.height <= 0) {
185
+ throw createProtocolError(`native-renderer-invalid-node-size-${index}`);
186
+ }
187
+ return normalized;
188
+ });
189
+ }
190
+
191
+ function normalizeEdges(edges, nodeHandles) {
192
+ if (!Array.isArray(edges)) {
193
+ throw createProtocolError('native-renderer-invalid-edges');
194
+ }
195
+ if (edges.length > MAX_EDGE_COUNT) {
196
+ throw createProtocolError('native-renderer-too-many-edges');
197
+ }
198
+ const handles = new Set();
199
+ let totalPointCount = 0;
200
+ const normalizedEdges = edges.map((edge, index) => {
201
+ if (!edge || typeof edge !== 'object' || Array.isArray(edge) || !Array.isArray(edge.points)) {
202
+ throw createProtocolError(`native-renderer-invalid-edge-${index}`);
203
+ }
204
+ const handle = toUint32(edge.handle, `edge-handle-${index}`);
205
+ if (handle === 0 || handles.has(handle)) {
206
+ throw createProtocolError(`native-renderer-duplicate-edge-handle-${index}`);
207
+ }
208
+ handles.add(handle);
209
+ if (edge.points.length < 2) {
210
+ throw createProtocolError(`native-renderer-invalid-edge-points-${index}`);
211
+ }
212
+ totalPointCount += edge.points.length;
213
+ if (totalPointCount > MAX_EDGE_POINT_COUNT) {
214
+ throw createProtocolError('native-renderer-too-many-edge-points');
215
+ }
216
+ const points = edge.points.map((point, pointIndex) => {
217
+ if (!point || typeof point !== 'object' || Array.isArray(point)) {
218
+ throw createProtocolError(`native-renderer-invalid-edge-point-${index}-${pointIndex}`);
219
+ }
220
+ return {
221
+ x: toFiniteNumber(point.x, `edge-point-x-${index}-${pointIndex}`),
222
+ y: toFiniteNumber(point.y, `edge-point-y-${index}-${pointIndex}`)
223
+ };
224
+ });
225
+ const normalized = {
226
+ handle,
227
+ sourceHandle: toUint32(edge.sourceHandle, `edge-source-${index}`),
228
+ targetHandle: toUint32(edge.targetHandle, `edge-target-${index}`),
229
+ z: toFiniteNumber(edge.z, `edge-z-${index}`),
230
+ width: toFiniteNumber(edge.width, `edge-width-${index}`),
231
+ colorRgba: toUint32(edge.colorRgba, `edge-color-${index}`),
232
+ points
233
+ };
234
+ if (
235
+ normalized.sourceHandle === 0 ||
236
+ normalized.targetHandle === 0 ||
237
+ !nodeHandles.has(normalized.sourceHandle) ||
238
+ !nodeHandles.has(normalized.targetHandle) ||
239
+ normalized.width <= 0
240
+ ) {
241
+ throw createProtocolError(`native-renderer-invalid-edge-range-${index}`);
242
+ }
243
+ return normalized;
244
+ });
245
+ return { edges: normalizedEdges, totalPointCount };
246
+ }
247
+
248
+ function ownerPayloadBytes(owner) {
249
+ return 2 + owner.boardId.length + 32;
250
+ }
251
+
252
+ function writeOwner(buffer, offset, owner) {
253
+ buffer.writeUInt16LE(owner.boardId.length, offset);
254
+ offset += 2;
255
+ owner.boardId.copy(buffer, offset);
256
+ offset += owner.boardId.length;
257
+ buffer.writeBigUInt64LE(owner.engineGeneration, offset);
258
+ offset += 8;
259
+ buffer.writeBigUInt64LE(owner.boardEpoch, offset);
260
+ offset += 8;
261
+ buffer.writeBigUInt64LE(owner.boardRevision, offset);
262
+ offset += 8;
263
+ buffer.writeBigUInt64LE(owner.requestSequence, offset);
264
+ return offset + 8;
265
+ }
266
+
267
+ function writeCamera(buffer, offset, camera) {
268
+ for (const value of [
269
+ camera.x,
270
+ camera.y,
271
+ camera.z,
272
+ camera.near,
273
+ camera.far,
274
+ camera.fov,
275
+ camera.viewportW,
276
+ camera.viewportH,
277
+ camera.zoom
278
+ ]) {
279
+ buffer.writeDoubleLE(value, offset);
280
+ offset += 8;
281
+ }
282
+ return offset;
283
+ }
284
+
285
+ function framePayload(requestType, payload) {
286
+ if (payload.length > MAX_PAYLOAD_BYTES) {
287
+ throw createProtocolError('native-renderer-payload-too-large');
288
+ }
289
+ const frame = Buffer.allocUnsafe(HEADER_BYTES + payload.length);
290
+ frame.writeUInt32LE(PROTOCOL_MAGIC, 0);
291
+ frame.writeUInt16LE(PROTOCOL_VERSION, 4);
292
+ frame.writeUInt16LE(requestType, 6);
293
+ frame.writeUInt32LE(payload.length, 8);
294
+ payload.copy(frame, HEADER_BYTES);
295
+ return frame;
296
+ }
297
+
298
+ function encodeSnapshotMessage(scene) {
299
+ if (!scene || typeof scene !== 'object' || Array.isArray(scene)) {
300
+ throw createProtocolError('native-renderer-invalid-scene');
301
+ }
302
+ const owner = normalizeOwner(scene.owner);
303
+ const camera = normalizeCamera(scene.camera);
304
+ const nodes = normalizeNodes(scene.nodes);
305
+ const normalizedEdges = normalizeEdges(scene.edges, new Set(nodes.map(node => node.handle)));
306
+ const payloadLength = ownerPayloadBytes(owner) + 72 + 12 +
307
+ nodes.length * 48 + normalizedEdges.edges.length * 36 + normalizedEdges.totalPointCount * 16;
308
+ if (payloadLength > MAX_PAYLOAD_BYTES) {
309
+ throw createProtocolError('native-renderer-payload-too-large');
310
+ }
311
+ const payload = Buffer.allocUnsafe(payloadLength);
312
+ let offset = writeOwner(payload, 0, owner);
313
+ offset = writeCamera(payload, offset, camera);
314
+ payload.writeUInt32LE(nodes.length, offset);
315
+ payload.writeUInt32LE(normalizedEdges.edges.length, offset + 4);
316
+ payload.writeUInt32LE(normalizedEdges.totalPointCount, offset + 8);
317
+ offset += 12;
318
+
319
+ for (const node of nodes) {
320
+ payload.writeUInt32LE(node.handle, offset);
321
+ offset += 4;
322
+ for (const value of [node.x, node.y, node.z, node.width, node.height]) {
323
+ payload.writeDoubleLE(value, offset);
324
+ offset += 8;
325
+ }
326
+ payload.writeUInt32LE(node.fillRgba, offset);
327
+ offset += 4;
328
+ }
329
+
330
+ for (const edge of normalizedEdges.edges) {
331
+ payload.writeUInt32LE(edge.handle, offset);
332
+ payload.writeUInt32LE(edge.sourceHandle, offset + 4);
333
+ payload.writeUInt32LE(edge.targetHandle, offset + 8);
334
+ offset += 12;
335
+ payload.writeDoubleLE(edge.z, offset);
336
+ payload.writeDoubleLE(edge.width, offset + 8);
337
+ offset += 16;
338
+ payload.writeUInt32LE(edge.colorRgba, offset);
339
+ payload.writeUInt32LE(edge.points.length, offset + 4);
340
+ offset += 8;
341
+ for (const point of edge.points) {
342
+ payload.writeDoubleLE(point.x, offset);
343
+ payload.writeDoubleLE(point.y, offset + 8);
344
+ offset += 16;
345
+ }
346
+ }
347
+
348
+ if (offset !== payload.length) {
349
+ throw createProtocolError('native-renderer-snapshot-size-mismatch');
350
+ }
351
+ return { frame: framePayload(REQUEST_TYPE.snapshot, payload), owner };
352
+ }
353
+
354
+ function encodeCameraMessage(frame) {
355
+ if (!frame || typeof frame !== 'object' || Array.isArray(frame)) {
356
+ throw createProtocolError('native-renderer-invalid-camera-frame');
357
+ }
358
+ const owner = normalizeOwner(frame.owner);
359
+ const camera = normalizeCamera(frame.camera);
360
+ const payload = Buffer.allocUnsafe(ownerPayloadBytes(owner) + 72);
361
+ const offset = writeCamera(payload, writeOwner(payload, 0, owner), camera);
362
+ if (offset !== payload.length) {
363
+ throw createProtocolError('native-renderer-camera-size-mismatch');
364
+ }
365
+ return { frame: framePayload(REQUEST_TYPE.camera, payload), owner };
366
+ }
367
+
368
+ function encodeDetachMessage(ownerValue) {
369
+ const owner = normalizeOwner(ownerValue);
370
+ const payload = Buffer.allocUnsafe(ownerPayloadBytes(owner));
371
+ const offset = writeOwner(payload, 0, owner);
372
+ if (offset !== payload.length) {
373
+ throw createProtocolError('native-renderer-detach-size-mismatch');
374
+ }
375
+ return { frame: framePayload(REQUEST_TYPE.detach, payload), owner };
376
+ }
377
+
378
+ function encodeShutdownMessage() {
379
+ return framePayload(REQUEST_TYPE.shutdown, Buffer.alloc(0));
380
+ }
381
+
382
+ class PayloadReader {
383
+ constructor(buffer) {
384
+ this.buffer = buffer;
385
+ this.offset = 0;
386
+ }
387
+
388
+ require(bytes) {
389
+ if (this.offset + bytes > this.buffer.length) {
390
+ throw createProtocolError('native-renderer-response-truncated');
391
+ }
392
+ }
393
+
394
+ uint8() {
395
+ this.require(1);
396
+ return this.buffer.readUInt8(this.offset++);
397
+ }
398
+
399
+ uint16() {
400
+ this.require(2);
401
+ const value = this.buffer.readUInt16LE(this.offset);
402
+ this.offset += 2;
403
+ return value;
404
+ }
405
+
406
+ uint64() {
407
+ this.require(8);
408
+ const value = this.buffer.readBigUInt64LE(this.offset);
409
+ this.offset += 8;
410
+ return value;
411
+ }
412
+
413
+ stringU16({ allowEmpty = true } = {}) {
414
+ const length = this.uint16();
415
+ this.require(length);
416
+ const value = this.buffer.toString('utf8', this.offset, this.offset + length);
417
+ this.offset += length;
418
+ if (!allowEmpty && value.length === 0) {
419
+ throw createProtocolError('native-renderer-response-empty-string');
420
+ }
421
+ return value;
422
+ }
423
+
424
+ owner() {
425
+ return {
426
+ boardId: this.stringU16({ allowEmpty: false }),
427
+ engineGeneration: this.uint64(),
428
+ boardEpoch: this.uint64(),
429
+ boardRevision: this.uint64(),
430
+ requestSequence: this.uint64()
431
+ };
432
+ }
433
+
434
+ finish() {
435
+ if (this.offset !== this.buffer.length) {
436
+ throw createProtocolError('native-renderer-response-trailing-bytes');
437
+ }
438
+ }
439
+ }
440
+
441
+ function decodeResponse(responseType, payload) {
442
+ const reader = new PayloadReader(payload);
443
+ let response;
444
+ switch (responseType) {
445
+ case RESPONSE_TYPE.ready:
446
+ response = { type: 'ready', backend: reader.stringU16({ allowEmpty: false }) };
447
+ break;
448
+ case RESPONSE_TYPE.accepted:
449
+ response = { type: 'accepted', requestType: reader.uint16(), owner: reader.owner() };
450
+ break;
451
+ case RESPONSE_TYPE.rejected: {
452
+ const requestType = reader.uint16();
453
+ const hasOwner = reader.uint8();
454
+ if (hasOwner !== 0 && hasOwner !== 1) {
455
+ throw createProtocolError('native-renderer-response-invalid-owner-flag');
456
+ }
457
+ response = {
458
+ type: 'rejected',
459
+ requestType,
460
+ owner: hasOwner === 1 ? reader.owner() : null,
461
+ reason: reader.stringU16({ allowEmpty: false })
462
+ };
463
+ break;
464
+ }
465
+ case RESPONSE_TYPE.shutdown:
466
+ response = { type: 'shutdown' };
467
+ break;
468
+ default:
469
+ throw createProtocolError('native-renderer-response-type-unknown');
470
+ }
471
+ reader.finish();
472
+ return response;
473
+ }
474
+
475
+ function responseOwnerKey(requestType, owner) {
476
+ const normalized = {
477
+ boardId: Buffer.from(owner.boardId, 'utf8'),
478
+ engineGeneration: owner.engineGeneration,
479
+ boardEpoch: owner.boardEpoch,
480
+ boardRevision: owner.boardRevision,
481
+ requestSequence: owner.requestSequence
482
+ };
483
+ return normalizedOwnerKey(requestType, normalized);
484
+ }
485
+
486
+ function defaultExecutablePath({ appIsPackaged, resourcesPath, packageRoot }) {
487
+ if (appIsPackaged) {
488
+ return path.join(resourcesPath, 'native-renderer', 'windows-x64', 'mindexec_windows_host.exe');
489
+ }
490
+ return path.resolve(packageRoot, '..', 'artifacts', 'cpp', 'windows-x64-release', 'mindexec_windows_host.exe');
491
+ }
492
+
493
+ class NativeRendererOwner {
494
+ constructor(options = {}) {
495
+ this.mode = options.mode === 'mirror' ? 'mirror' : 'off';
496
+ this.appIsPackaged = options.appIsPackaged === true;
497
+ this.executablePath = path.resolve(options.executablePath || defaultExecutablePath({
498
+ appIsPackaged: this.appIsPackaged,
499
+ resourcesPath: options.resourcesPath || process.resourcesPath || '',
500
+ packageRoot: options.packageRoot || path.resolve(__dirname, '..')
501
+ }));
502
+ this.spawnProcess = options.spawnProcess || spawn;
503
+ this.onStatus = typeof options.onStatus === 'function' ? options.onStatus : () => undefined;
504
+ this.log = typeof options.log === 'function' ? options.log : () => undefined;
505
+ this.readyTimeoutMs = options.readyTimeoutMs || READY_TIMEOUT_MS;
506
+ this.responseTimeoutMs = options.responseTimeoutMs || RESPONSE_TIMEOUT_MS;
507
+ this.stopTimeoutMs = options.stopTimeoutMs || STOP_TIMEOUT_MS;
508
+ this.forceExitTimeoutMs = options.forceExitTimeoutMs || FORCE_EXIT_TIMEOUT_MS;
509
+
510
+ this.child = null;
511
+ this.startPromise = null;
512
+ this.stopPromise = null;
513
+ this.ready = false;
514
+ this.backend = '';
515
+ this.engineGeneration = 0;
516
+ this.acceptedFrames = 0;
517
+ this.rejectedFrames = 0;
518
+ this.lastError = '';
519
+ this.stdoutBuffer = Buffer.alloc(0);
520
+ this.pending = new Map();
521
+ this.readyResolver = null;
522
+ this.readyRejecter = null;
523
+ this.intentionalStop = false;
524
+ this.lifecycleState = LIFECYCLE_STATE.stopped;
525
+ }
526
+
527
+ getStatus() {
528
+ return {
529
+ mode: this.mode,
530
+ ready: this.ready,
531
+ backend: this.backend,
532
+ engineGeneration: this.engineGeneration,
533
+ acceptedFrames: this.acceptedFrames,
534
+ rejectedFrames: this.rejectedFrames,
535
+ lastError: this.lastError,
536
+ processId: this.child?.exitCode === null ? Number(this.child.pid || 0) : 0
537
+ };
538
+ }
539
+
540
+ isRunning() {
541
+ return Boolean(this.child && this.child.exitCode === null);
542
+ }
543
+
544
+ emitStatus() {
545
+ this.onStatus(this.getStatus());
546
+ }
547
+
548
+ validatePackageFiles() {
549
+ if (!this.appIsPackaged) return '';
550
+ const packageDirectory = path.dirname(this.executablePath);
551
+ const requiredPaths = [
552
+ path.join(packageDirectory, 'native-renderer-manifest.json'),
553
+ path.join(packageDirectory, 'shaders', 'dxbc', 'vs_canvas_shape.sc.bin'),
554
+ path.join(packageDirectory, 'shaders', 'dxbc', 'fs_canvas_shape.sc.bin')
555
+ ];
556
+ const missingPath = requiredPaths.find(filePath => !fs.existsSync(filePath));
557
+ if (missingPath) {
558
+ return `native-renderer-package-file-missing:${missingPath}`;
559
+ }
560
+ try {
561
+ const manifest = JSON.parse(fs.readFileSync(requiredPaths[0], 'utf8'));
562
+ const manifestFiles = new Set(
563
+ Array.isArray(manifest.files) ? manifest.files.map(entry => String(entry?.path || '')) : []
564
+ );
565
+ if (
566
+ manifest.schema !== 'mindexec-native-renderer-package/v1' ||
567
+ manifest.platform !== 'windows-x64' ||
568
+ Number(manifest.protocol) !== PROTOCOL_VERSION ||
569
+ !manifestFiles.has('mindexec_windows_host.exe') ||
570
+ !manifestFiles.has('shaders/dxbc/vs_canvas_shape.sc.bin') ||
571
+ !manifestFiles.has('shaders/dxbc/fs_canvas_shape.sc.bin')
572
+ ) {
573
+ return 'native-renderer-package-manifest-invalid';
574
+ }
575
+ } catch (error) {
576
+ return `native-renderer-package-manifest-invalid:${error?.message || error}`;
577
+ }
578
+ return '';
579
+ }
580
+
581
+ async start() {
582
+ if (this.mode !== 'mirror') {
583
+ return this.getStatus();
584
+ }
585
+ if (this.lifecycleState === LIFECYCLE_STATE.stopping || this.stopPromise) {
586
+ return this.getStatus();
587
+ }
588
+ if (this.ready && this.isRunning()) {
589
+ return this.getStatus();
590
+ }
591
+ if (this.startPromise) {
592
+ return this.startPromise;
593
+ }
594
+ if (this.isRunning()) {
595
+ this.lastError ||= 'native-renderer-child-not-ready';
596
+ this.emitStatus();
597
+ return this.getStatus();
598
+ }
599
+ this.lifecycleState = LIFECYCLE_STATE.starting;
600
+ this.startPromise = this.startCore().finally(() => {
601
+ this.startPromise = null;
602
+ if (this.lifecycleState === LIFECYCLE_STATE.starting) {
603
+ this.lifecycleState = this.ready
604
+ ? LIFECYCLE_STATE.ready
605
+ : (this.isRunning() ? LIFECYCLE_STATE.failed : LIFECYCLE_STATE.stopped);
606
+ }
607
+ });
608
+ return this.startPromise;
609
+ }
610
+
611
+ async startCore() {
612
+ if (process.platform !== 'win32' && !this.spawnProcess.__allowNonWindowsForTest) {
613
+ this.lastError = 'native-renderer-windows-only';
614
+ this.emitStatus();
615
+ return this.getStatus();
616
+ }
617
+ if (!fs.existsSync(this.executablePath)) {
618
+ this.lastError = `native-renderer-executable-missing:${this.executablePath}`;
619
+ this.emitStatus();
620
+ return this.getStatus();
621
+ }
622
+ const packageError = this.validatePackageFiles();
623
+ if (packageError) {
624
+ this.lastError = packageError;
625
+ this.emitStatus();
626
+ return this.getStatus();
627
+ }
628
+
629
+ this.intentionalStop = false;
630
+ this.ready = false;
631
+ this.backend = '';
632
+ this.lastError = '';
633
+ this.stdoutBuffer = Buffer.alloc(0);
634
+ this.engineGeneration += 1;
635
+ this.acceptedFrames = 0;
636
+ this.rejectedFrames = 0;
637
+
638
+ let child;
639
+ try {
640
+ child = this.spawnProcess(this.executablePath, ['--mirror-stdio'], {
641
+ cwd: path.dirname(this.executablePath),
642
+ env: { ...process.env },
643
+ stdio: ['pipe', 'pipe', 'pipe'],
644
+ windowsHide: false
645
+ });
646
+ } catch (error) {
647
+ this.lastError = `native-renderer-spawn-failed:${error?.message || error}`;
648
+ this.lifecycleState = LIFECYCLE_STATE.failed;
649
+ this.emitStatus();
650
+ return this.getStatus();
651
+ }
652
+ this.child = child;
653
+ child.stdout?.on('data', chunk => this.handleStdout(chunk));
654
+ child.stderr?.on('data', chunk => this.log(`[native-renderer:error] ${String(chunk).trimEnd()}`));
655
+ child.on('error', error => this.handleProcessError(error));
656
+ child.on('exit', (code, signal) => this.handleProcessExit(child, code, signal));
657
+ this.log(`[native-renderer:start] pid=${String(child.pid)} generation=${this.engineGeneration}`);
658
+
659
+ const readyResult = await new Promise(resolve => {
660
+ const timer = setTimeout(() => {
661
+ this.readyResolver = null;
662
+ this.lastError = 'native-renderer-ready-timeout';
663
+ resolve(false);
664
+ }, this.readyTimeoutMs);
665
+ timer.unref?.();
666
+ this.readyResolver = () => {
667
+ clearTimeout(timer);
668
+ this.readyResolver = null;
669
+ resolve(true);
670
+ };
671
+ this.readyRejecter = reason => {
672
+ clearTimeout(timer);
673
+ this.readyResolver = null;
674
+ this.readyRejecter = null;
675
+ this.lastError = reason;
676
+ resolve(false);
677
+ };
678
+ });
679
+
680
+ if (!readyResult && this.child === child && child.exitCode === null) {
681
+ try {
682
+ child.kill('SIGKILL');
683
+ } catch {
684
+ // Process may have exited between the state check and kill.
685
+ }
686
+ }
687
+ this.emitStatus();
688
+ return this.getStatus();
689
+ }
690
+
691
+ handleStdout(chunk) {
692
+ this.stdoutBuffer = this.stdoutBuffer.length === 0
693
+ ? Buffer.from(chunk)
694
+ : Buffer.concat([this.stdoutBuffer, chunk]);
695
+ try {
696
+ while (this.stdoutBuffer.length >= HEADER_BYTES) {
697
+ const magic = this.stdoutBuffer.readUInt32LE(0);
698
+ const version = this.stdoutBuffer.readUInt16LE(4);
699
+ const responseType = this.stdoutBuffer.readUInt16LE(6);
700
+ const payloadLength = this.stdoutBuffer.readUInt32LE(8);
701
+ if (magic !== PROTOCOL_MAGIC || version !== PROTOCOL_VERSION || payloadLength > MAX_PAYLOAD_BYTES) {
702
+ throw createProtocolError('native-renderer-response-header-invalid');
703
+ }
704
+ const frameLength = HEADER_BYTES + payloadLength;
705
+ if (this.stdoutBuffer.length < frameLength) {
706
+ return;
707
+ }
708
+ const payload = this.stdoutBuffer.subarray(HEADER_BYTES, frameLength);
709
+ this.stdoutBuffer = this.stdoutBuffer.subarray(frameLength);
710
+ this.handleResponse(decodeResponse(responseType, payload));
711
+ }
712
+ } catch (error) {
713
+ this.handleProtocolError(error);
714
+ }
715
+ }
716
+
717
+ handleResponse(response) {
718
+ if (response.type === 'ready') {
719
+ if (this.lifecycleState === LIFECYCLE_STATE.stopping) {
720
+ this.readyRejecter?.('native-renderer-stopping');
721
+ return;
722
+ }
723
+ this.ready = true;
724
+ this.backend = response.backend;
725
+ this.lastError = '';
726
+ this.lifecycleState = LIFECYCLE_STATE.ready;
727
+ const resolveReady = this.readyResolver;
728
+ this.readyResolver = null;
729
+ this.readyRejecter = null;
730
+ resolveReady?.();
731
+ this.emitStatus();
732
+ return;
733
+ }
734
+ if (response.type === 'shutdown') {
735
+ this.ready = false;
736
+ return;
737
+ }
738
+ if (!response.owner) {
739
+ if (response.requestType === REQUEST_TYPE.snapshot || response.requestType === REQUEST_TYPE.camera) {
740
+ this.rejectedFrames += 1;
741
+ }
742
+ this.resolveOldestPending(response.requestType, {
743
+ accepted: false,
744
+ reason: response.reason || 'native-renderer-rejected-without-owner'
745
+ });
746
+ this.emitStatus();
747
+ return;
748
+ }
749
+
750
+ const key = responseOwnerKey(response.requestType, response.owner);
751
+ const pending = this.pending.get(key);
752
+ if (!pending) {
753
+ this.log(`[native-renderer:orphan-response] type=${response.requestType} key=${key}`);
754
+ return;
755
+ }
756
+ this.pending.delete(key);
757
+ clearTimeout(pending.timer);
758
+ if (response.type === 'accepted') {
759
+ if (response.requestType === REQUEST_TYPE.snapshot || response.requestType === REQUEST_TYPE.camera) {
760
+ this.acceptedFrames += 1;
761
+ }
762
+ pending.resolve({ accepted: true });
763
+ return;
764
+ }
765
+ if (response.requestType === REQUEST_TYPE.snapshot || response.requestType === REQUEST_TYPE.camera) {
766
+ this.rejectedFrames += 1;
767
+ }
768
+ pending.resolve({ accepted: false, reason: response.reason || 'native-renderer-rejected' });
769
+ this.emitStatus();
770
+ }
771
+
772
+ resolveOldestPending(requestType, result) {
773
+ for (const [key, pending] of this.pending) {
774
+ if (pending.requestType !== requestType) continue;
775
+ this.pending.delete(key);
776
+ clearTimeout(pending.timer);
777
+ pending.resolve(result);
778
+ return;
779
+ }
780
+ }
781
+
782
+ handleProtocolError(error) {
783
+ this.lastError = error?.message || String(error);
784
+ this.ready = false;
785
+ if (this.lifecycleState !== LIFECYCLE_STATE.stopping) {
786
+ this.lifecycleState = LIFECYCLE_STATE.failed;
787
+ }
788
+ this.failPending(this.lastError);
789
+ this.readyRejecter?.(this.lastError);
790
+ this.emitStatus();
791
+ const child = this.child;
792
+ if (child && child.exitCode === null) {
793
+ try {
794
+ child.kill('SIGKILL');
795
+ } catch {
796
+ // Process may already be exiting.
797
+ }
798
+ }
799
+ }
800
+
801
+ handleProcessError(error) {
802
+ this.lastError = `native-renderer-process-error:${error?.message || error}`;
803
+ this.ready = false;
804
+ if (this.lifecycleState !== LIFECYCLE_STATE.stopping) {
805
+ this.lifecycleState = LIFECYCLE_STATE.failed;
806
+ }
807
+ this.failPending(this.lastError);
808
+ this.readyRejecter?.(this.lastError);
809
+ this.emitStatus();
810
+ }
811
+
812
+ handleProcessExit(child, code, signal) {
813
+ if (this.child !== child) return;
814
+ this.child = null;
815
+ this.ready = false;
816
+ this.backend = '';
817
+ this.lifecycleState = this.intentionalStop
818
+ ? LIFECYCLE_STATE.stopped
819
+ : LIFECYCLE_STATE.failed;
820
+ const reason = `native-renderer-exit:${String(code)}:${String(signal)}`;
821
+ this.failPending(reason);
822
+ this.readyRejecter?.(reason);
823
+ if (!this.intentionalStop && !this.lastError) {
824
+ this.lastError = reason;
825
+ }
826
+ this.log(`[native-renderer:exit] code=${String(code)} signal=${String(signal)}`);
827
+ this.emitStatus();
828
+ }
829
+
830
+ failPending(reason) {
831
+ for (const pending of this.pending.values()) {
832
+ clearTimeout(pending.timer);
833
+ if (pending.requestType === REQUEST_TYPE.snapshot || pending.requestType === REQUEST_TYPE.camera) {
834
+ this.rejectedFrames += 1;
835
+ }
836
+ pending.resolve({ accepted: false, reason });
837
+ }
838
+ this.pending.clear();
839
+ }
840
+
841
+ ownerGenerationMatches(owner) {
842
+ try {
843
+ return toUint64(owner?.engineGeneration, 'engine-generation') === BigInt(this.engineGeneration);
844
+ } catch {
845
+ return false;
846
+ }
847
+ }
848
+
849
+ rejectBeforeSend(requestType, reason) {
850
+ if (requestType === REQUEST_TYPE.snapshot || requestType === REQUEST_TYPE.camera) {
851
+ this.rejectedFrames += 1;
852
+ }
853
+ this.emitStatus();
854
+ return { ...this.getStatus(), accepted: false, reason };
855
+ }
856
+
857
+ async sendSnapshot(scene) {
858
+ return this.sendOwned(REQUEST_TYPE.snapshot, scene?.owner, () => encodeSnapshotMessage(scene));
859
+ }
860
+
861
+ async sendCamera(frame) {
862
+ return this.sendOwned(REQUEST_TYPE.camera, frame?.owner, () => encodeCameraMessage(frame));
863
+ }
864
+
865
+ async detach(owner) {
866
+ return this.sendOwned(REQUEST_TYPE.detach, owner, () => encodeDetachMessage(owner));
867
+ }
868
+
869
+ async sendOwned(requestType, ownerValue, encode) {
870
+ if (this.mode !== 'mirror') {
871
+ return this.rejectBeforeSend(requestType, 'native-renderer-mode-off');
872
+ }
873
+ if (this.lifecycleState === LIFECYCLE_STATE.stopping || this.stopPromise) {
874
+ return this.rejectBeforeSend(requestType, 'native-renderer-stopping');
875
+ }
876
+ await this.start();
877
+ if (this.lifecycleState === LIFECYCLE_STATE.stopping || this.stopPromise) {
878
+ return this.rejectBeforeSend(requestType, 'native-renderer-stopping');
879
+ }
880
+ if (!this.ready || !this.isRunning()) {
881
+ return this.rejectBeforeSend(requestType, this.lastError || 'native-renderer-not-ready');
882
+ }
883
+ if (!this.ownerGenerationMatches(ownerValue)) {
884
+ return this.rejectBeforeSend(requestType, 'native-renderer-engine-generation-mismatch');
885
+ }
886
+ if (this.pending.size >= MAX_PENDING_REQUESTS) {
887
+ return this.rejectBeforeSend(requestType, 'native-renderer-pending-limit');
888
+ }
889
+
890
+ let encoded;
891
+ try {
892
+ encoded = encode();
893
+ } catch (error) {
894
+ return this.rejectBeforeSend(requestType, error?.message || String(error));
895
+ }
896
+ const key = normalizedOwnerKey(requestType, encoded.owner);
897
+ if (this.pending.has(key)) {
898
+ return this.rejectBeforeSend(requestType, 'native-renderer-request-duplicate');
899
+ }
900
+ const child = this.child;
901
+ if (!child?.stdin || child.exitCode !== null) {
902
+ return this.rejectBeforeSend(requestType, 'native-renderer-not-ready');
903
+ }
904
+
905
+ const result = await new Promise(resolve => {
906
+ const timer = setTimeout(() => {
907
+ if (!this.pending.delete(key)) return;
908
+ if (requestType === REQUEST_TYPE.snapshot || requestType === REQUEST_TYPE.camera) {
909
+ this.rejectedFrames += 1;
910
+ }
911
+ resolve({ accepted: false, reason: 'native-renderer-response-timeout' });
912
+ this.emitStatus();
913
+ }, this.responseTimeoutMs);
914
+ timer.unref?.();
915
+ this.pending.set(key, { requestType, resolve, timer });
916
+ child.stdin.write(encoded.frame, error => {
917
+ if (!error || !this.pending.delete(key)) return;
918
+ clearTimeout(timer);
919
+ if (requestType === REQUEST_TYPE.snapshot || requestType === REQUEST_TYPE.camera) {
920
+ this.rejectedFrames += 1;
921
+ }
922
+ resolve({ accepted: false, reason: `native-renderer-write-failed:${error.message}` });
923
+ this.emitStatus();
924
+ });
925
+ });
926
+ return { ...this.getStatus(), ...result };
927
+ }
928
+
929
+ async stop() {
930
+ if (this.stopPromise) return this.stopPromise;
931
+ this.stopPromise = this.stopCore().finally(() => {
932
+ this.stopPromise = null;
933
+ });
934
+ return this.stopPromise;
935
+ }
936
+
937
+ async stopCore() {
938
+ const child = this.child;
939
+ this.intentionalStop = true;
940
+ this.ready = false;
941
+ this.lifecycleState = LIFECYCLE_STATE.stopping;
942
+ this.readyRejecter?.('native-renderer-stopping');
943
+ this.failPending('native-renderer-stopping');
944
+ if (!child || child.exitCode !== null) {
945
+ this.child = null;
946
+ this.backend = '';
947
+ this.lifecycleState = LIFECYCLE_STATE.stopped;
948
+ this.emitStatus();
949
+ return this.getStatus();
950
+ }
951
+
952
+ await new Promise((resolve, reject) => {
953
+ let settled = false;
954
+ let forceExitTimer = null;
955
+ const cleanup = () => {
956
+ clearTimeout(gracefulTimer);
957
+ if (forceExitTimer) clearTimeout(forceExitTimer);
958
+ child.removeListener('exit', confirmExit);
959
+ };
960
+ const confirmExit = () => {
961
+ if (settled) return;
962
+ settled = true;
963
+ cleanup();
964
+ resolve();
965
+ };
966
+ const rejectWithoutExit = () => {
967
+ if (settled) return;
968
+ settled = true;
969
+ cleanup();
970
+ this.lastError = 'native-renderer-force-exit-timeout';
971
+ this.lifecycleState = LIFECYCLE_STATE.stopFailed;
972
+ this.emitStatus();
973
+ reject(createProtocolError(this.lastError));
974
+ };
975
+ const gracefulTimer = setTimeout(() => {
976
+ if (child.exitCode === null) {
977
+ try {
978
+ child.kill('SIGKILL');
979
+ } catch {
980
+ // Process may have exited between the check and kill.
981
+ }
982
+ }
983
+ if (child.exitCode !== null) {
984
+ confirmExit();
985
+ return;
986
+ }
987
+ forceExitTimer = setTimeout(rejectWithoutExit, this.forceExitTimeoutMs);
988
+ }, this.stopTimeoutMs);
989
+ child.once('exit', confirmExit);
990
+ if (child.exitCode !== null) {
991
+ confirmExit();
992
+ return;
993
+ }
994
+ try {
995
+ child.stdin?.write(encodeShutdownMessage(), error => {
996
+ if (error && child.exitCode === null) {
997
+ try {
998
+ child.kill('SIGTERM');
999
+ } catch {
1000
+ // The exit listener or the bounded force-exit timeout owns completion.
1001
+ }
1002
+ }
1003
+ });
1004
+ } catch {
1005
+ try {
1006
+ child.kill('SIGTERM');
1007
+ } catch {
1008
+ // The exit listener or the bounded force-exit timeout owns completion.
1009
+ }
1010
+ }
1011
+ });
1012
+ if (this.child === child || child.exitCode === null) {
1013
+ this.lastError = 'native-renderer-exit-not-confirmed';
1014
+ this.lifecycleState = LIFECYCLE_STATE.stopFailed;
1015
+ this.emitStatus();
1016
+ throw createProtocolError(this.lastError);
1017
+ }
1018
+ this.backend = '';
1019
+ this.failPending('native-renderer-stopped');
1020
+ this.lifecycleState = LIFECYCLE_STATE.stopped;
1021
+ this.emitStatus();
1022
+ return this.getStatus();
1023
+ }
1024
+ }
1025
+
1026
+ function createNativeRendererOwner(options) {
1027
+ return new NativeRendererOwner(options);
1028
+ }
1029
+
1030
+ module.exports = {
1031
+ HEADER_BYTES,
1032
+ MAX_PAYLOAD_BYTES,
1033
+ PROTOCOL_MAGIC,
1034
+ PROTOCOL_VERSION,
1035
+ REQUEST_TYPE,
1036
+ RESPONSE_TYPE,
1037
+ createNativeRendererOwner,
1038
+ decodeResponse,
1039
+ encodeCameraMessage,
1040
+ encodeDetachMessage,
1041
+ encodeShutdownMessage,
1042
+ encodeSnapshotMessage,
1043
+ framePayload
1044
+ };