@forgeax/engine-remote 0.1.4 → 0.1.6
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 +3 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/cli-defaults.unit.test.d.ts +2 -0
- package/dist/__tests__/cli-defaults.unit.test.d.ts.map +1 -0
- package/dist/__tests__/simulation-inspect.integration.test.d.ts +2 -0
- package/dist/__tests__/simulation-inspect.integration.test.d.ts.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.mjs +336 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/defineSubcommand.d.ts +40 -0
- package/dist/defineSubcommand.d.ts.map +1 -0
- package/dist/execute.d.ts +1 -0
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.mjs +2 -0
- package/dist/execute.mjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/introspect.d.ts +2 -0
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.mjs +5 -8
- package/dist/introspect.mjs.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.mjs +20 -58
- package/dist/server.mjs.map +1 -1
- package/package.json +6 -3
- package/src/__tests__/cli-defaults.unit.test.ts +69 -0
- package/src/__tests__/console.unit.test.ts +676 -9
- package/src/__tests__/errors.unit.test.ts +3 -17
- package/src/__tests__/execute.async.test.ts +17 -13
- package/src/__tests__/server.unit.test.ts +19 -262
- package/src/__tests__/simulation-inspect.integration.test.ts +67 -0
- package/src/__tests__/vm-async-eval-verify.mjs +1 -1
- package/src/cli.ts +333 -0
- package/src/defineSubcommand.ts +169 -0
- package/src/execute.ts +3 -0
- package/src/index.ts +5 -4
- package/src/introspect.ts +10 -15
- package/src/server.ts +14 -60
- package/dist/__tests__/asset-runtime-inspection.unit.test.d.ts +0 -2
- package/dist/__tests__/asset-runtime-inspection.unit.test.d.ts.map +0 -1
- package/src/__tests__/asset-runtime-inspection.unit.test.ts +0 -16
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// w11: RemoteErrorCode completeness unit test (5-member closed union).
|
|
2
|
-
// Verifies that the error-code family has exactly
|
|
2
|
+
// Verifies that the error-code family has exactly 4 members after
|
|
3
3
|
// deleting inspector-write-denied + script-timeout in w7.
|
|
4
4
|
|
|
5
5
|
import { describe, expect, it } from 'vitest';
|
|
@@ -39,7 +39,7 @@ describe('RemoteErrorCode closed union — 5-member completeness', () => {
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
describe('RemoteError construction + toJSON', () => {
|
|
42
|
-
it('constructs with
|
|
42
|
+
it('constructs with all 4 fields', () => {
|
|
43
43
|
const err = new RemoteError({
|
|
44
44
|
code: 'script-syntax-error',
|
|
45
45
|
expected: 'valid JS',
|
|
@@ -51,7 +51,7 @@ describe('RemoteError construction + toJSON', () => {
|
|
|
51
51
|
expect(err.message).toContain('[RemoteError script-syntax-error]');
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
it('toJSON() returns
|
|
54
|
+
it('toJSON() returns 4-field plain object', () => {
|
|
55
55
|
const err = new RemoteError({
|
|
56
56
|
code: 'server-not-running',
|
|
57
57
|
expected: 'server reachable',
|
|
@@ -85,18 +85,4 @@ describe('RemoteError construction + toJSON', () => {
|
|
|
85
85
|
expect(err.code).toBe('script-runtime-error');
|
|
86
86
|
expect(err.expected).toBe('script executes');
|
|
87
87
|
});
|
|
88
|
-
|
|
89
|
-
it('toJSON retains only the bounded result-shape detail', () => {
|
|
90
|
-
const err = new RemoteError({
|
|
91
|
-
code: 'eval-result-not-serializable',
|
|
92
|
-
expected: 'eval result is JSON-serializable',
|
|
93
|
-
hint: 'return a JSON-safe value',
|
|
94
|
-
detail: { code: 'eval-result-not-serializable', shape: 'cyclic-object' },
|
|
95
|
-
});
|
|
96
|
-
expect(err.toJSON()).toMatchObject({
|
|
97
|
-
code: 'eval-result-not-serializable',
|
|
98
|
-
detail: { code: 'eval-result-not-serializable', shape: 'cyclic-object' },
|
|
99
|
-
});
|
|
100
|
-
expect(JSON.stringify(err)).not.toContain('privateMarker');
|
|
101
|
-
});
|
|
102
88
|
});
|
|
@@ -18,7 +18,21 @@ import { executeScript } from '../execute';
|
|
|
18
18
|
|
|
19
19
|
function makeMockWorld() {
|
|
20
20
|
const state: unknown[] = [];
|
|
21
|
+
// Archetype shape retained for scripts that inspect the mock World internals:
|
|
22
|
+
// - columns: Map<compId, Map<fieldName, { length: number }>>
|
|
23
|
+
// The Entity component (id=0) has field 'self' with Uint32Array length.
|
|
24
|
+
function mkArchetype(id: number) {
|
|
25
|
+
const selfField = new Map([['self', { length: 3 }]]);
|
|
26
|
+
const columns = new Map([[0, selfField]]);
|
|
27
|
+
return { id, componentIds: [0], columns };
|
|
28
|
+
}
|
|
21
29
|
return {
|
|
30
|
+
_getGraph() {
|
|
31
|
+
return {
|
|
32
|
+
generation: 1,
|
|
33
|
+
archetypes: [mkArchetype(0), mkArchetype(1)],
|
|
34
|
+
};
|
|
35
|
+
},
|
|
22
36
|
inspect(): { entityCount: number } {
|
|
23
37
|
return { entityCount: state.length + 5 };
|
|
24
38
|
},
|
|
@@ -41,17 +55,7 @@ const mockRenderer = {
|
|
|
41
55
|
/* no-op */
|
|
42
56
|
},
|
|
43
57
|
};
|
|
44
|
-
const mockAssets = {
|
|
45
|
-
load(guid: string, expectedKind: string) {
|
|
46
|
-
return Promise.resolve({ ok: true, value: { guid, kind: expectedKind } });
|
|
47
|
-
},
|
|
48
|
-
snapshot() {
|
|
49
|
-
return { scopeId: 'test-scope', generation: 1, entries: [] };
|
|
50
|
-
},
|
|
51
|
-
subscribe() {
|
|
52
|
-
return () => {};
|
|
53
|
-
},
|
|
54
|
-
};
|
|
58
|
+
const mockAssets = { HANDLE_CUBE: 1, HANDLE_TRIANGLE: 2 };
|
|
55
59
|
|
|
56
60
|
function makeCtx() {
|
|
57
61
|
return {
|
|
@@ -89,9 +93,9 @@ describe('executeScript async - read (w1)', () => {
|
|
|
89
93
|
}
|
|
90
94
|
});
|
|
91
95
|
|
|
92
|
-
it('reads
|
|
96
|
+
it('reads asset handle constants', async () => {
|
|
93
97
|
const ctx = makeCtx();
|
|
94
|
-
const result = await executeScript('assets.
|
|
98
|
+
const result = await executeScript('assets.HANDLE_CUBE', ctx);
|
|
95
99
|
expect(result.ok).toBe(true);
|
|
96
100
|
if (result.ok) {
|
|
97
101
|
expect(result.value).toBe(1);
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
// source as ancestorTitles[0]. Top-level imports merged + deduped.
|
|
9
9
|
|
|
10
10
|
import { createProfiler } from '@forgeax/engine-profiler';
|
|
11
|
-
import { defaultConnect } from '@forgeax/engine-types/inspector-client';
|
|
12
11
|
import { describe, expect, it } from 'vitest';
|
|
13
12
|
import { WebSocket } from 'ws';
|
|
14
13
|
import { RemoteError } from '../errors';
|
|
@@ -37,7 +36,6 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
37
36
|
expected: string;
|
|
38
37
|
hint: string;
|
|
39
38
|
message?: string;
|
|
40
|
-
detail?: { code: string; shape: string };
|
|
41
39
|
};
|
|
42
40
|
};
|
|
43
41
|
id: number | string | null;
|
|
@@ -68,22 +66,6 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
68
66
|
});
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
async function sendRaw(ws: WebSocket, raw: string): Promise<JsonRpcResponse> {
|
|
72
|
-
return new Promise<JsonRpcResponse>((resolve, reject) => {
|
|
73
|
-
const handler = (message: WebSocket.RawData): void => {
|
|
74
|
-
ws.off('message', handler);
|
|
75
|
-
try {
|
|
76
|
-
const parsed = JSON.parse(message.toString()) as JsonRpcResponse;
|
|
77
|
-
resolve(parsed);
|
|
78
|
-
} catch (e) {
|
|
79
|
-
reject(e);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
ws.on('message', handler);
|
|
83
|
-
ws.send(raw);
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
69
|
async function withServer(
|
|
88
70
|
fn: (handle: ConsoleHandle) => Promise<void>,
|
|
89
71
|
opts: {
|
|
@@ -92,7 +74,6 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
92
74
|
renderer?: unknown;
|
|
93
75
|
assets?: unknown;
|
|
94
76
|
profiler?: unknown;
|
|
95
|
-
importModule?: (specifier: string) => Promise<unknown>;
|
|
96
77
|
} = {},
|
|
97
78
|
): Promise<void> {
|
|
98
79
|
const startResult = await startServer({
|
|
@@ -102,7 +83,6 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
102
83
|
renderer: opts.renderer,
|
|
103
84
|
assets: opts.assets,
|
|
104
85
|
profiler: opts.profiler,
|
|
105
|
-
...(opts.importModule === undefined ? {} : { importModule: opts.importModule }),
|
|
106
86
|
});
|
|
107
87
|
if (!startResult.ok) {
|
|
108
88
|
throw startResult.error;
|
|
@@ -123,39 +103,9 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
123
103
|
expect(typeof handle.close).toBe('function');
|
|
124
104
|
});
|
|
125
105
|
});
|
|
126
|
-
|
|
127
|
-
it('close is idempotent', async () => {
|
|
128
|
-
await withServer(async (handle) => {
|
|
129
|
-
await handle.close();
|
|
130
|
-
await handle.close();
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
106
|
});
|
|
134
107
|
|
|
135
108
|
describe('JSON-RPC envelope shape', () => {
|
|
136
|
-
it('uses the host import resolver without coupling remote to engine packages', async () => {
|
|
137
|
-
await withServer(
|
|
138
|
-
async (handle) => {
|
|
139
|
-
const ws = await connect(handle.port);
|
|
140
|
-
const resp = await send(ws, {
|
|
141
|
-
jsonrpc: '2.0',
|
|
142
|
-
method: 'eval',
|
|
143
|
-
params: {
|
|
144
|
-
script: "const module = await _import('virtual:host-module'); return module.value",
|
|
145
|
-
},
|
|
146
|
-
id: 11,
|
|
147
|
-
});
|
|
148
|
-
expect(resp.result).toBe('host-resolved');
|
|
149
|
-
expect(resp.error).toBeUndefined();
|
|
150
|
-
ws.close();
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
importModule: async (specifier) =>
|
|
154
|
-
specifier === 'virtual:host-module' ? { value: 'host-resolved' } : {},
|
|
155
|
-
},
|
|
156
|
-
);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
109
|
it('response carries jsonrpc + id and either result or error (mutually exclusive)', async () => {
|
|
160
110
|
await withServer(async (handle) => {
|
|
161
111
|
const ws = await connect(handle.port);
|
|
@@ -208,58 +158,6 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
208
158
|
ws.close();
|
|
209
159
|
});
|
|
210
160
|
});
|
|
211
|
-
|
|
212
|
-
it('null envelope returns -32600 without root execution and preserves the connection', async () => {
|
|
213
|
-
let worldReads = 0;
|
|
214
|
-
const world = new Proxy(
|
|
215
|
-
{},
|
|
216
|
-
{
|
|
217
|
-
get(_target, property) {
|
|
218
|
-
worldReads += 1;
|
|
219
|
-
throw new Error(`unexpected world read: ${String(property)}`);
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
await withServer(
|
|
225
|
-
async (handle) => {
|
|
226
|
-
const ws = await connect(handle.port);
|
|
227
|
-
let closeCount = 0;
|
|
228
|
-
ws.on('close', () => {
|
|
229
|
-
closeCount += 1;
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
const invalid = await sendRaw(ws, 'null');
|
|
233
|
-
expect(invalid).toEqual({
|
|
234
|
-
jsonrpc: '2.0',
|
|
235
|
-
id: null,
|
|
236
|
-
error: { code: -32600, message: 'Invalid Request' },
|
|
237
|
-
});
|
|
238
|
-
expect(worldReads).toBe(0);
|
|
239
|
-
|
|
240
|
-
const introspect = await send(ws, { jsonrpc: '2.0', method: 'introspect', id: 1 });
|
|
241
|
-
expect(introspect.jsonrpc).toBe('2.0');
|
|
242
|
-
expect(introspect.id).toBe(1);
|
|
243
|
-
expect(introspect.result).toBeDefined();
|
|
244
|
-
|
|
245
|
-
const evaluated = await send(ws, {
|
|
246
|
-
jsonrpc: '2.0',
|
|
247
|
-
method: 'eval',
|
|
248
|
-
params: { script: 'return 7' },
|
|
249
|
-
id: 2,
|
|
250
|
-
});
|
|
251
|
-
expect(evaluated).toEqual({ jsonrpc: '2.0', id: 2, result: 7 });
|
|
252
|
-
|
|
253
|
-
const repeated = await sendRaw(ws, 'null');
|
|
254
|
-
expect(repeated).toEqual(invalid);
|
|
255
|
-
expect(worldReads).toBe(0);
|
|
256
|
-
expect(ws.readyState).toBe(ws.OPEN);
|
|
257
|
-
expect(closeCount).toBe(0);
|
|
258
|
-
ws.close();
|
|
259
|
-
},
|
|
260
|
-
{ world },
|
|
261
|
-
);
|
|
262
|
-
});
|
|
263
161
|
});
|
|
264
162
|
|
|
265
163
|
describe('introspect() OpenRPC L2 subset', () => {
|
|
@@ -497,14 +395,18 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
497
395
|
});
|
|
498
396
|
|
|
499
397
|
it('renderer read returns real field value', async () => {
|
|
500
|
-
const stubRenderer = {
|
|
398
|
+
const stubRenderer = {
|
|
399
|
+
backend: 'webgpu',
|
|
400
|
+
isReady: true,
|
|
401
|
+
inspect: () => ({ capabilities: { backendKind: 'webgpu' } }),
|
|
402
|
+
};
|
|
501
403
|
await withServer(
|
|
502
404
|
async (handle) => {
|
|
503
405
|
const ws = await connect(handle.port);
|
|
504
406
|
const resp = await send(ws, {
|
|
505
407
|
jsonrpc: '2.0',
|
|
506
408
|
method: 'eval',
|
|
507
|
-
params: { script: 'renderer.
|
|
409
|
+
params: { script: 'renderer.inspect().capabilities.backendKind' },
|
|
508
410
|
id: 21,
|
|
509
411
|
});
|
|
510
412
|
expect(resp.result).toBe('webgpu');
|
|
@@ -545,157 +447,6 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
545
447
|
});
|
|
546
448
|
});
|
|
547
449
|
|
|
548
|
-
it('successful non-JSON results return a structured error and preserve the socket', async () => {
|
|
549
|
-
const world = {
|
|
550
|
-
count: 0,
|
|
551
|
-
bump() {
|
|
552
|
-
this.count += 1;
|
|
553
|
-
},
|
|
554
|
-
inspect() {
|
|
555
|
-
return { count: this.count, stable: true };
|
|
556
|
-
},
|
|
557
|
-
};
|
|
558
|
-
const renderer = {
|
|
559
|
-
inspect() {
|
|
560
|
-
return { renderer: 'stable' };
|
|
561
|
-
},
|
|
562
|
-
};
|
|
563
|
-
await withServer(
|
|
564
|
-
async (handle) => {
|
|
565
|
-
const ws = await connect(handle.port);
|
|
566
|
-
const cyclic = await send(ws, {
|
|
567
|
-
jsonrpc: '2.0',
|
|
568
|
-
method: 'eval',
|
|
569
|
-
params: {
|
|
570
|
-
script:
|
|
571
|
-
"world.bump(); const value = { privateMarker: 'must-not-cross-wire' }; value.self = value; return value;",
|
|
572
|
-
},
|
|
573
|
-
id: 'cyclic-result',
|
|
574
|
-
});
|
|
575
|
-
expect(cyclic).toMatchObject({
|
|
576
|
-
jsonrpc: '2.0',
|
|
577
|
-
id: 'cyclic-result',
|
|
578
|
-
error: {
|
|
579
|
-
code: -32005,
|
|
580
|
-
message: 'Eval result not serializable',
|
|
581
|
-
data: {
|
|
582
|
-
code: 'eval-result-not-serializable',
|
|
583
|
-
expected: 'eval result is JSON-serializable',
|
|
584
|
-
hint: 'return a JSON-safe value; BigInt and cyclic objects are unsupported over JSON-RPC',
|
|
585
|
-
detail: {
|
|
586
|
-
code: 'eval-result-not-serializable',
|
|
587
|
-
shape: 'cyclic-object',
|
|
588
|
-
},
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
|
-
});
|
|
592
|
-
expect(JSON.stringify(cyclic)).not.toContain('privateMarker');
|
|
593
|
-
|
|
594
|
-
const afterCyclic = await send(ws, {
|
|
595
|
-
jsonrpc: '2.0',
|
|
596
|
-
method: 'eval',
|
|
597
|
-
params: { script: 'world.inspect()' },
|
|
598
|
-
id: 'after-cyclic',
|
|
599
|
-
});
|
|
600
|
-
expect(afterCyclic).toEqual({
|
|
601
|
-
jsonrpc: '2.0',
|
|
602
|
-
id: 'after-cyclic',
|
|
603
|
-
result: { count: 1, stable: true },
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
let notificationReceived = false;
|
|
607
|
-
const notificationHandler = () => {
|
|
608
|
-
notificationReceived = true;
|
|
609
|
-
};
|
|
610
|
-
ws.on('message', notificationHandler);
|
|
611
|
-
ws.send(
|
|
612
|
-
JSON.stringify({
|
|
613
|
-
jsonrpc: '2.0',
|
|
614
|
-
method: 'eval',
|
|
615
|
-
params: { script: 'world.bump(); return 7' },
|
|
616
|
-
}),
|
|
617
|
-
);
|
|
618
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
619
|
-
ws.off('message', notificationHandler);
|
|
620
|
-
expect(notificationReceived).toBe(false);
|
|
621
|
-
|
|
622
|
-
const afterNotification = await send(ws, {
|
|
623
|
-
jsonrpc: '2.0',
|
|
624
|
-
method: 'eval',
|
|
625
|
-
params: { script: 'renderer.inspect()' },
|
|
626
|
-
id: 'after-notification',
|
|
627
|
-
});
|
|
628
|
-
expect(afterNotification.result).toEqual({ renderer: 'stable' });
|
|
629
|
-
|
|
630
|
-
const bigint = await send(ws, {
|
|
631
|
-
jsonrpc: '2.0',
|
|
632
|
-
method: 'eval',
|
|
633
|
-
params: { script: 'world.bump(); return 1n' },
|
|
634
|
-
id: 'bigint-result',
|
|
635
|
-
});
|
|
636
|
-
expect(bigint).toMatchObject({
|
|
637
|
-
jsonrpc: '2.0',
|
|
638
|
-
id: 'bigint-result',
|
|
639
|
-
error: {
|
|
640
|
-
code: -32005,
|
|
641
|
-
message: 'Eval result not serializable',
|
|
642
|
-
data: {
|
|
643
|
-
code: 'eval-result-not-serializable',
|
|
644
|
-
detail: { code: 'eval-result-not-serializable', shape: 'bigint' },
|
|
645
|
-
},
|
|
646
|
-
},
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
const afterBigInt = await send(ws, {
|
|
650
|
-
jsonrpc: '2.0',
|
|
651
|
-
method: 'eval',
|
|
652
|
-
params: { script: 'world.inspect()' },
|
|
653
|
-
id: 'after-bigint',
|
|
654
|
-
});
|
|
655
|
-
expect(afterBigInt).toEqual({
|
|
656
|
-
jsonrpc: '2.0',
|
|
657
|
-
id: 'after-bigint',
|
|
658
|
-
result: { count: 3, stable: true },
|
|
659
|
-
});
|
|
660
|
-
ws.close();
|
|
661
|
-
},
|
|
662
|
-
{ world, renderer },
|
|
663
|
-
);
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
it('shared inspector client preserves structured serialization errors and recovers', async () => {
|
|
667
|
-
const world = {
|
|
668
|
-
count: 0,
|
|
669
|
-
bump() {
|
|
670
|
-
this.count += 1;
|
|
671
|
-
},
|
|
672
|
-
inspect() {
|
|
673
|
-
return { count: this.count, stable: true };
|
|
674
|
-
},
|
|
675
|
-
};
|
|
676
|
-
await withServer(
|
|
677
|
-
async (handle) => {
|
|
678
|
-
const connected = await defaultConnect(`ws://127.0.0.1:${handle.port}/inspector`);
|
|
679
|
-
expect(connected.ok).toBe(true);
|
|
680
|
-
if (!connected.ok) return;
|
|
681
|
-
const client = connected.value;
|
|
682
|
-
await expect(
|
|
683
|
-
client.eval(
|
|
684
|
-
"world.bump(); const value = { secret: 'hidden' }; value.self = value; return value",
|
|
685
|
-
),
|
|
686
|
-
).rejects.toMatchObject({
|
|
687
|
-
code: 'eval-result-not-serializable',
|
|
688
|
-
expected: 'eval result is JSON-serializable',
|
|
689
|
-
detail: { code: 'eval-result-not-serializable', shape: 'cyclic-object' },
|
|
690
|
-
});
|
|
691
|
-
await expect(client.eval('world.inspect()')).resolves.toEqual({ count: 1, stable: true });
|
|
692
|
-
await client.dispose();
|
|
693
|
-
await client.dispose();
|
|
694
|
-
},
|
|
695
|
-
{ world },
|
|
696
|
-
);
|
|
697
|
-
});
|
|
698
|
-
|
|
699
450
|
it('JSON-RPC error envelopes preserve exact human messages', async () => {
|
|
700
451
|
await withServer(async (handle) => {
|
|
701
452
|
const ws = await connect(handle.port);
|
|
@@ -771,10 +522,10 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
771
522
|
);
|
|
772
523
|
});
|
|
773
524
|
|
|
774
|
-
it('assets.
|
|
525
|
+
it('assets.register() succeeds (full-access, no sandbox)', async () => {
|
|
775
526
|
const stubAssets = {
|
|
776
|
-
|
|
777
|
-
return
|
|
527
|
+
register(_a: unknown): number {
|
|
528
|
+
return 42;
|
|
778
529
|
},
|
|
779
530
|
};
|
|
780
531
|
await withServer(
|
|
@@ -783,10 +534,10 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
783
534
|
const resp = await send(ws, {
|
|
784
535
|
jsonrpc: '2.0',
|
|
785
536
|
method: 'eval',
|
|
786
|
-
params: { script: '
|
|
537
|
+
params: { script: 'assets.register({ kind: "mesh" })' },
|
|
787
538
|
id: 51,
|
|
788
539
|
});
|
|
789
|
-
expect(resp.result).
|
|
540
|
+
expect(resp.result).toBe(42);
|
|
790
541
|
ws.close();
|
|
791
542
|
},
|
|
792
543
|
{ world: {}, assets: stubAssets },
|
|
@@ -800,13 +551,19 @@ import { type ComponentIntrospectionDescriptor, type ConsoleHandle, startServer
|
|
|
800
551
|
const resp = await send(ws, {
|
|
801
552
|
jsonrpc: '2.0',
|
|
802
553
|
method: 'eval',
|
|
803
|
-
params: { script: 'renderer.
|
|
554
|
+
params: { script: 'renderer.inspect().capabilities.backendKind' },
|
|
804
555
|
id: 52,
|
|
805
556
|
});
|
|
806
557
|
expect(resp.result).toBe('webgpu');
|
|
807
558
|
ws.close();
|
|
808
559
|
},
|
|
809
|
-
{
|
|
560
|
+
{
|
|
561
|
+
world: {},
|
|
562
|
+
renderer: {
|
|
563
|
+
backend: 'webgpu',
|
|
564
|
+
inspect: () => ({ capabilities: { backendKind: 'webgpu' } }),
|
|
565
|
+
},
|
|
566
|
+
},
|
|
810
567
|
);
|
|
811
568
|
});
|
|
812
569
|
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { buildIntrospectDoc } from '../introspect';
|
|
4
|
+
import { startServer } from '../server';
|
|
5
|
+
|
|
6
|
+
describe('remote simulation inspection projection', () => {
|
|
7
|
+
it('projects the existing simulation read root without adding remote actions', () => {
|
|
8
|
+
const doc = buildIntrospectDoc('127.0.0.1', 5732, {
|
|
9
|
+
world: {},
|
|
10
|
+
renderer: {},
|
|
11
|
+
assets: {},
|
|
12
|
+
simulation: {
|
|
13
|
+
inspect: () => ({
|
|
14
|
+
formatVersion: 1,
|
|
15
|
+
recordOwner: '@forgeax/engine-ecs',
|
|
16
|
+
schemaOwner: '@forgeax/engine-ecs',
|
|
17
|
+
participants: [],
|
|
18
|
+
trace: { recordTick: 0, sampleCount: 0 },
|
|
19
|
+
report: { verdict: 'match', entries: [] },
|
|
20
|
+
}),
|
|
21
|
+
},
|
|
22
|
+
}) as {
|
|
23
|
+
roots: Record<string, { type: string; description: string }>;
|
|
24
|
+
methods: readonly { name: string }[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
expect(doc.roots.simulation).toMatchObject({
|
|
28
|
+
available: true,
|
|
29
|
+
type: 'SimulationInspection',
|
|
30
|
+
});
|
|
31
|
+
expect(doc.methods.map((method) => method.name)).toEqual(['eval', 'introspect']);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('keeps simulation inspection on the existing eval transport', async () => {
|
|
35
|
+
const started = await startServer({
|
|
36
|
+
port: 0,
|
|
37
|
+
host: '127.0.0.1',
|
|
38
|
+
world: {},
|
|
39
|
+
simulation: {
|
|
40
|
+
inspect: () => ({
|
|
41
|
+
formatVersion: 1,
|
|
42
|
+
recordOwner: '@forgeax/engine-ecs',
|
|
43
|
+
schemaOwner: '@forgeax/engine-ecs',
|
|
44
|
+
participants: [],
|
|
45
|
+
trace: { recordTick: 0, sampleCount: 0 },
|
|
46
|
+
report: { verdict: 'match', entries: [] },
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
expect(started.ok).toBe(true);
|
|
51
|
+
if (!started.ok) return;
|
|
52
|
+
try {
|
|
53
|
+
const { defaultConnect } = await import('@forgeax/engine-types/inspector-client');
|
|
54
|
+
const connected = await defaultConnect(`ws://127.0.0.1:${started.value.port}/inspector`);
|
|
55
|
+
expect(connected.ok).toBe(true);
|
|
56
|
+
if (!connected.ok) return;
|
|
57
|
+
const value = await connected.value.eval('simulation.inspect()');
|
|
58
|
+
expect(value).toMatchObject({
|
|
59
|
+
formatVersion: 1,
|
|
60
|
+
recordOwner: '@forgeax/engine-ecs',
|
|
61
|
+
});
|
|
62
|
+
await connected.value.dispose();
|
|
63
|
+
} finally {
|
|
64
|
+
await started.value.close();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -137,7 +137,7 @@ if (routeAfail && routeBpass) {
|
|
|
137
137
|
console.log(' - w5: implement executeScript via new Function');
|
|
138
138
|
console.log(' - w5: remove scriptTimeoutMs & timeout logic (dead)');
|
|
139
139
|
console.log(' - w7: delete script-timeout from RemoteErrorCode');
|
|
140
|
-
console.log(' - Final error set:
|
|
140
|
+
console.log(' - Final error set: 4 members (no script-timeout)');
|
|
141
141
|
exitCode = 0;
|
|
142
142
|
} else {
|
|
143
143
|
console.log('UNEXPECTED RESULT:');
|