@forgeax/engine-remote 0.1.3 → 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.
Files changed (42) hide show
  1. package/README.md +3 -3
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/cli-defaults.unit.test.d.ts +2 -0
  4. package/dist/__tests__/cli-defaults.unit.test.d.ts.map +1 -0
  5. package/dist/__tests__/simulation-inspect.integration.test.d.ts +2 -0
  6. package/dist/__tests__/simulation-inspect.integration.test.d.ts.map +1 -0
  7. package/dist/cli.d.ts +15 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.mjs +336 -0
  10. package/dist/cli.mjs.map +1 -0
  11. package/dist/defineSubcommand.d.ts +40 -0
  12. package/dist/defineSubcommand.d.ts.map +1 -0
  13. package/dist/execute.d.ts +1 -0
  14. package/dist/execute.d.ts.map +1 -1
  15. package/dist/execute.mjs +2 -0
  16. package/dist/execute.mjs.map +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/introspect.d.ts +2 -0
  19. package/dist/introspect.d.ts.map +1 -1
  20. package/dist/introspect.mjs +5 -8
  21. package/dist/introspect.mjs.map +1 -1
  22. package/dist/server.d.ts +2 -2
  23. package/dist/server.d.ts.map +1 -1
  24. package/dist/server.mjs +20 -58
  25. package/dist/server.mjs.map +1 -1
  26. package/package.json +6 -3
  27. package/src/__tests__/cli-defaults.unit.test.ts +69 -0
  28. package/src/__tests__/console.unit.test.ts +676 -9
  29. package/src/__tests__/errors.unit.test.ts +3 -17
  30. package/src/__tests__/execute.async.test.ts +17 -13
  31. package/src/__tests__/server.unit.test.ts +19 -262
  32. package/src/__tests__/simulation-inspect.integration.test.ts +67 -0
  33. package/src/__tests__/vm-async-eval-verify.mjs +1 -1
  34. package/src/cli.ts +333 -0
  35. package/src/defineSubcommand.ts +169 -0
  36. package/src/execute.ts +4 -1
  37. package/src/index.ts +5 -4
  38. package/src/introspect.ts +10 -15
  39. package/src/server.ts +14 -60
  40. package/dist/__tests__/asset-runtime-inspection.unit.test.d.ts +0 -2
  41. package/dist/__tests__/asset-runtime-inspection.unit.test.d.ts.map +0 -1
  42. package/src/__tests__/asset-runtime-inspection.unit.test.ts +0 -16
package/dist/cli.mjs ADDED
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env node
2
+ import { realpath, readFile } from 'fs/promises';
3
+ import { fileURLToPath } from 'url';
4
+ import { INSPECTOR_DEFAULT_PORT, INSPECTOR_DEFAULT_HOST, defaultConnect } from '@forgeax/engine-types/inspector-client';
5
+ export { defaultConnect } from '@forgeax/engine-types/inspector-client';
6
+
7
+ // src/defineSubcommand.ts
8
+ var GAP = 4;
9
+ function defineSubcommand(spec) {
10
+ if (typeof spec.name !== "string" || spec.name.length === 0) {
11
+ throw new Error("defineSubcommand: spec.name must be a non-empty string");
12
+ }
13
+ return spec;
14
+ }
15
+ function resolvePath(root, path) {
16
+ let current = root;
17
+ const consumed = [];
18
+ for (const segment of path) {
19
+ const next = current.subcommands?.find((s) => s.name === segment);
20
+ if (next === void 0) break;
21
+ current = next;
22
+ consumed.push(segment);
23
+ }
24
+ return { spec: current, path: consumed };
25
+ }
26
+ function section(title, items) {
27
+ if (items.length === 0) return [];
28
+ let maxLen = 0;
29
+ for (const [label] of items) {
30
+ if (label.length > maxLen) maxLen = label.length;
31
+ }
32
+ const lines = [];
33
+ lines.push(`${title}:`);
34
+ for (const [label, body] of items) {
35
+ const pad = " ".repeat(maxLen + GAP - label.length);
36
+ lines.push(` ${label}${pad}${body}`);
37
+ }
38
+ lines.push("");
39
+ return lines;
40
+ }
41
+ function renderHelp(root, path) {
42
+ const { spec, path: consumed } = resolvePath(root, path);
43
+ const fullPath = [root.name, ...consumed].join(" ");
44
+ const out = [];
45
+ out.push(`${fullPath} - ${spec.description}`);
46
+ out.push("");
47
+ const usagePieces = [fullPath];
48
+ if (spec.subcommands && spec.subcommands.length > 0) {
49
+ usagePieces.push("<subcommand>");
50
+ } else {
51
+ usagePieces.push("[options]");
52
+ }
53
+ out.push("Usage:");
54
+ out.push(` ${usagePieces.join(" ")}`);
55
+ out.push("");
56
+ if (spec.subcommands && spec.subcommands.length > 0) {
57
+ out.push(
58
+ ...section(
59
+ consumed.length === 0 ? "Sub-commands" : "Sub-targets",
60
+ spec.subcommands.map((s) => [s.name, s.description])
61
+ )
62
+ );
63
+ }
64
+ if (spec.options && spec.options.length > 0) {
65
+ out.push(
66
+ ...section(
67
+ "Options",
68
+ spec.options.map((o) => [o.flag, o.description])
69
+ )
70
+ );
71
+ }
72
+ if (spec.examples && spec.examples.length > 0) {
73
+ const exampleItems = spec.examples.map(
74
+ (e) => [e.usage, e.description ?? ""]
75
+ );
76
+ out.push(...section("Examples", exampleItems));
77
+ }
78
+ if (spec.extraNotes && spec.extraNotes.length > 0) {
79
+ out.push("Notes:");
80
+ for (const note of spec.extraNotes) {
81
+ out.push(` ${note}`);
82
+ }
83
+ out.push("");
84
+ }
85
+ return `${out.join("\n").trimEnd()}
86
+ `;
87
+ }
88
+
89
+ // src/cli.ts
90
+ var FORGEAX_CLI_SPEC = defineSubcommand({
91
+ name: "forgeax-engine-remote",
92
+ description: "remote eval CLI - drive a running forgeax engine via JSON-RPC over WS",
93
+ options: [
94
+ {
95
+ flag: "--port <n>",
96
+ description: `Inspector WebSocket port (default ${INSPECTOR_DEFAULT_PORT}; monitor uses 5731)`
97
+ },
98
+ { flag: "--host <s>", description: `Host name (default ${INSPECTOR_DEFAULT_HOST})` },
99
+ { flag: "--help, -h", description: "Show this help and exit 0" }
100
+ ],
101
+ subcommands: [
102
+ defineSubcommand({
103
+ name: "script",
104
+ description: "eval a script file against the live world/renderer/assets",
105
+ options: [{ flag: "--help, -h", description: "Show this help and exit 0" }],
106
+ examples: [
107
+ {
108
+ usage: "forgeax-engine-remote script ./inspect.mjs",
109
+ description: "eval a local script file"
110
+ }
111
+ ]
112
+ }),
113
+ defineSubcommand({
114
+ name: "eval",
115
+ description: "evaluate an inline expression against the world",
116
+ options: [{ flag: "--help, -h", description: "Show this help and exit 0" }],
117
+ examples: [
118
+ {
119
+ usage: 'forgeax-engine-remote eval "world.inspect().entityCount"',
120
+ description: "inline read of world.inspect()"
121
+ }
122
+ ]
123
+ })
124
+ ],
125
+ extraNotes: [
126
+ "eval is full read/write access to the live world/renderer/assets/debugAdapter; the only security boundary is whether the host started the server.",
127
+ "Plugin discovery via PATH-prefix removed in M2 (routing layer deletion).",
128
+ "See also: packages/remote/README.md (eval API, live roots, security model) + AI User Charter.",
129
+ "Simulation inspection is read-only through eval; restore and replay are not Remote or CLI actions."
130
+ ]
131
+ });
132
+ function renderTopLevelHelp() {
133
+ const lines = [];
134
+ lines.push(`${FORGEAX_CLI_SPEC.name} - ${FORGEAX_CLI_SPEC.description}`);
135
+ lines.push("");
136
+ lines.push("Usage:");
137
+ lines.push(` ${FORGEAX_CLI_SPEC.name} <subcommand> [args]`);
138
+ lines.push("");
139
+ lines.push("Built-in commands:");
140
+ const builtIns = FORGEAX_CLI_SPEC.subcommands ?? [];
141
+ const builtInWidth = builtIns.reduce((m, s) => Math.max(m, s.name.length), 0);
142
+ for (const s of builtIns) {
143
+ const pad = " ".repeat(builtInWidth - s.name.length + 4);
144
+ lines.push(` ${s.name}${pad}${s.description}`);
145
+ }
146
+ lines.push("");
147
+ if (FORGEAX_CLI_SPEC.options && FORGEAX_CLI_SPEC.options.length > 0) {
148
+ lines.push("Options:");
149
+ const optWidth = FORGEAX_CLI_SPEC.options.reduce((m, o) => Math.max(m, o.flag.length), 0);
150
+ for (const o of FORGEAX_CLI_SPEC.options) {
151
+ const pad = " ".repeat(optWidth - o.flag.length + 4);
152
+ lines.push(` ${o.flag}${pad}${o.description}`);
153
+ }
154
+ lines.push("");
155
+ }
156
+ if (FORGEAX_CLI_SPEC.extraNotes && FORGEAX_CLI_SPEC.extraNotes.length > 0) {
157
+ lines.push("Notes:");
158
+ for (const note of FORGEAX_CLI_SPEC.extraNotes) {
159
+ lines.push(` ${note}`);
160
+ }
161
+ lines.push("");
162
+ }
163
+ return `${lines.join("\n").trimEnd()}
164
+ `;
165
+ }
166
+ var defaultFileReader = async (path) => {
167
+ return await readFile(path, "utf8");
168
+ };
169
+ async function dispatch(opts) {
170
+ const { argv, stdoutWrite, stderrWrite, connect } = opts;
171
+ const fileReader = opts.fileReader ?? defaultFileReader;
172
+ const [, , subcommand, ...rest] = argv;
173
+ if (subcommand === void 0 || subcommand === "--help" || subcommand === "-h") {
174
+ stdoutWrite(renderTopLevelHelp());
175
+ return 0;
176
+ }
177
+ let port = INSPECTOR_DEFAULT_PORT;
178
+ let host = INSPECTOR_DEFAULT_HOST;
179
+ const filteredRest = [];
180
+ for (let i = 0; i < rest.length; i++) {
181
+ const arg = rest[i];
182
+ if (arg === "--port") {
183
+ const next = rest[i + 1];
184
+ if (typeof next === "string") {
185
+ const parsed = Number(next);
186
+ if (!Number.isNaN(parsed) && parsed > 0) {
187
+ port = parsed;
188
+ i++;
189
+ continue;
190
+ }
191
+ }
192
+ }
193
+ if (arg === "--host") {
194
+ const next = rest[i + 1];
195
+ if (typeof next === "string") {
196
+ host = next;
197
+ i++;
198
+ continue;
199
+ }
200
+ }
201
+ if (typeof arg === "string") filteredRest.push(arg);
202
+ }
203
+ switch (subcommand) {
204
+ case "script":
205
+ return runScript(filteredRest, {
206
+ stdoutWrite,
207
+ stderrWrite,
208
+ connect,
209
+ port,
210
+ host,
211
+ fileReader
212
+ });
213
+ case "eval":
214
+ return runEval(filteredRest, { stdoutWrite, stderrWrite, connect, port, host });
215
+ default: {
216
+ stderrWrite(
217
+ `forgeax: unknown subcommand '${subcommand}'
218
+ expected: subcommand is one of: script, eval
219
+ hint: run 'forgeax-engine-remote --help' for usage
220
+ detail: '${subcommand}' is not a built-in subcommand (plugin discovery removed in M2)
221
+ `
222
+ );
223
+ return 1;
224
+ }
225
+ }
226
+ }
227
+ function inspectorErrorToStderr(e) {
228
+ return [`forgeax: ${e.code}`, ` expected: ${e.expected}`, ` hint: ${e.hint}`].join("\n");
229
+ }
230
+ async function runScript(rest, ctx) {
231
+ const [file] = rest;
232
+ if (file === "--help" || file === "-h") {
233
+ ctx.stdoutWrite(renderHelp(FORGEAX_CLI_SPEC, ["script"]));
234
+ return 0;
235
+ }
236
+ if (typeof file !== "string") {
237
+ ctx.stderrWrite(
238
+ [
239
+ "forgeax: script requires a <file> positional argument",
240
+ " expected: forgeax-engine-remote script <path-to-js-file>",
241
+ " hint: e.g. 'forgeax-engine-remote script ./inspect.mjs'"
242
+ ].join("\n")
243
+ );
244
+ return 1;
245
+ }
246
+ let body;
247
+ try {
248
+ body = await ctx.fileReader(file);
249
+ } catch (e) {
250
+ const message = e instanceof Error ? e.message : String(e);
251
+ ctx.stderrWrite(
252
+ [
253
+ `forgeax: script file unreadable: ${file}`,
254
+ " expected: file exists and is readable",
255
+ ` hint: check path; underlying error: ${message}`
256
+ ].join("\n")
257
+ );
258
+ return 1;
259
+ }
260
+ return invokeExecute(body, ctx);
261
+ }
262
+ async function runEval(rest, ctx) {
263
+ const [script] = rest;
264
+ if (script === "--help" || script === "-h") {
265
+ ctx.stdoutWrite(renderHelp(FORGEAX_CLI_SPEC, ["eval"]));
266
+ return 0;
267
+ }
268
+ if (typeof script !== "string") {
269
+ ctx.stderrWrite(
270
+ [
271
+ "forgeax: eval requires an inline <script> positional argument",
272
+ ' expected: forgeax-engine-remote eval "<expression>"',
273
+ ` hint: e.g. 'forgeax-engine-remote eval "world.inspect().entityCount"'`
274
+ ].join("\n")
275
+ );
276
+ return 1;
277
+ }
278
+ return invokeExecute(script, ctx);
279
+ }
280
+ async function invokeExecute(script, ctx) {
281
+ const url = `ws://${ctx.host}:${ctx.port}/inspector`;
282
+ const connectResult = await ctx.connect(url);
283
+ if (!connectResult.ok) {
284
+ ctx.stderrWrite(inspectorErrorToStderr(connectResult.error));
285
+ return 1;
286
+ }
287
+ const client = connectResult.value;
288
+ try {
289
+ const result = await client.eval(script);
290
+ ctx.stdoutWrite(typeof result === "string" ? result : JSON.stringify(result, null, 2));
291
+ return 0;
292
+ } catch (e) {
293
+ if (isRemoteError(e)) {
294
+ ctx.stderrWrite(inspectorErrorToStderr(e));
295
+ return 1;
296
+ }
297
+ const message = e instanceof Error ? e.message : String(e);
298
+ ctx.stderrWrite(
299
+ [
300
+ "forgeax: execute failed",
301
+ " expected: server-side execute() resolves Result.ok",
302
+ ` hint: underlying: ${message}`
303
+ ].join("\n")
304
+ );
305
+ return 1;
306
+ } finally {
307
+ await client.dispose();
308
+ }
309
+ }
310
+ function isRemoteError(e) {
311
+ return typeof e === "object" && e !== null && typeof e.code === "string" && typeof e.expected === "string" && typeof e.hint === "string";
312
+ }
313
+ var isBinEntry = await (async () => {
314
+ const argv1 = process.argv[1];
315
+ if (typeof argv1 !== "string") return false;
316
+ const argv1Real = await realpath(argv1).catch(() => argv1);
317
+ const selfReal = await realpath(fileURLToPath(import.meta.url)).catch(
318
+ () => fileURLToPath(import.meta.url)
319
+ );
320
+ return argv1Real === selfReal;
321
+ })();
322
+ if (isBinEntry) {
323
+ const exitCode = await dispatch({
324
+ argv: process.argv,
325
+ stdoutWrite: (line) => process.stdout.write(`${line}
326
+ `),
327
+ stderrWrite: (line) => process.stderr.write(`${line}
328
+ `),
329
+ connect: defaultConnect
330
+ });
331
+ process.exit(exitCode);
332
+ }
333
+
334
+ export { FORGEAX_CLI_SPEC, dispatch };
335
+ //# sourceMappingURL=cli.mjs.map
336
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/defineSubcommand.ts","../src/cli.ts"],"names":[],"mappings":"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","file":"cli.mjs","sourcesContent":["// @forgeax/engine-remote/src/defineSubcommand - sade utils.js form (~94-line\n// flat-dictionary + section primitive) DSL for the `forgeax-engine-console`\n// CLI help renderer. Plan-strategy D-4 + D-7 lock-in:\n//\n// - Single file, package-internal (NOT in package.json#exports).\n// `cli.ts` is the sole consumer; tsup inlines it into dist/cli.mjs.\n// - 94-line ceiling is a pattern target, not a hard cap; we trade a few\n// extra lines for explicit JSDoc that AI users read at edit time.\n// - Three render layers driven by `path` slicing:\n// path = [] -> top-level help\n// path = ['inspect'] -> subcommand help\n// path = ['inspect', 'entities'] -> sub-target help\n// - List-width pin: `maxLen + GAP=4` padding (R-4 mitigation; snapshot\n// test packages/console/src/__tests__/cli-help.test.ts guards drift).\n//\n// charter: proposition 1 (progressive disclosure — `path` is the navigator,\n// not a hidden config) + proposition 3 (machine-readable spec >>> hand-rolled\n// strings) + proposition 4 (explicit failure — render is total: any unknown\n// path returns the closest valid layer rather than throwing).\n\nconst GAP = 4;\n\n/** Single option entry: `--with <Name>`, `--port <number>` ... */\nexport interface OptionSpec {\n readonly flag: string;\n readonly description: string;\n readonly multiple?: boolean;\n readonly defaultValue?: string;\n}\n\n/** Single example block: usage line + free description. */\nexport interface ExampleSpec {\n readonly usage: string;\n readonly description?: string;\n}\n\n/** Subcommand spec — recursive (subcommands map to nested specs). */\nexport interface SubcommandSpec {\n readonly name: string;\n readonly description: string;\n readonly options?: ReadonlyArray<OptionSpec>;\n readonly subcommands?: ReadonlyArray<SubcommandSpec>;\n readonly examples?: ReadonlyArray<ExampleSpec>;\n readonly extraNotes?: ReadonlyArray<string>;\n}\n\n/**\n * Single-input wrapping helper: turns a sade-style descriptor into a\n * SubcommandSpec POD. Today the function is a near-identity (the spec is\n * already structurally a POD), but the wrapper preserves a single intercept\n * point for future validation (charter proposition 4: explicit failure on\n * malformed input — we can throw here rather than silently render garbage).\n */\nexport function defineSubcommand(spec: SubcommandSpec): SubcommandSpec {\n if (typeof spec.name !== 'string' || spec.name.length === 0) {\n throw new Error('defineSubcommand: spec.name must be a non-empty string');\n }\n return spec;\n}\n\n/**\n * Look up the descendant spec at `path`. Returns the closest matching\n * ancestor when the path is partially unknown so renderHelp degrades to the\n * deepest valid layer rather than throwing (charter proposition 4 explicit\n * failure: a wrong path is recoverable; a thrown render is not).\n */\nfunction resolvePath(\n root: SubcommandSpec,\n path: readonly string[],\n): { readonly spec: SubcommandSpec; readonly path: readonly string[] } {\n let current = root;\n const consumed: string[] = [];\n for (const segment of path) {\n const next = current.subcommands?.find((s) => s.name === segment);\n if (next === undefined) break;\n current = next;\n consumed.push(segment);\n }\n return { spec: current, path: consumed };\n}\n\n/**\n * Render a single section with `key` left-padded to `maxLen + GAP` columns.\n * `items` carries `[label, body]` pairs; both halves are flat strings.\n *\n * Skips emission entirely when `items` is empty so the rendered help body\n * has no orphan section headers (UX nit: AI users grep section headers as\n * anchors — emitting a header followed by nothing fools the grep).\n */\nfunction section(title: string, items: ReadonlyArray<readonly [string, string]>): string[] {\n if (items.length === 0) return [];\n let maxLen = 0;\n for (const [label] of items) {\n if (label.length > maxLen) maxLen = label.length;\n }\n const lines: string[] = [];\n lines.push(`${title}:`);\n for (const [label, body] of items) {\n const pad = ' '.repeat(maxLen + GAP - label.length);\n lines.push(` ${label}${pad}${body}`);\n }\n lines.push('');\n return lines;\n}\n\n/**\n * Render the help body for `path` against `root`. Always returns a non-empty\n * string ending in a single newline so callers can pipe to stdout without\n * post-processing.\n *\n * Layer 1 (root): title + Usage + Sub-commands + Options + extraNotes\n * Layer 2 (subcommand): title + Usage + Sub-targets (if any) + Options + Examples + extraNotes\n * Layer 3 (sub-target): title + Usage + Options + Examples + extraNotes\n */\nexport function renderHelp(root: SubcommandSpec, path: readonly string[]): string {\n const { spec, path: consumed } = resolvePath(root, path);\n const fullPath = [root.name, ...consumed].join(' ');\n const out: string[] = [];\n out.push(`${fullPath} - ${spec.description}`);\n out.push('');\n\n // Usage line — synthesised from `path` + the leaf's surface.\n const usagePieces: string[] = [fullPath];\n if (spec.subcommands && spec.subcommands.length > 0) {\n usagePieces.push('<subcommand>');\n } else {\n // Leaf nodes use a generic <args> token; concrete shape lives in\n // `examples` so the help body stays declarative rather than guessed.\n usagePieces.push('[options]');\n }\n out.push('Usage:');\n out.push(` ${usagePieces.join(' ')}`);\n out.push('');\n\n if (spec.subcommands && spec.subcommands.length > 0) {\n out.push(\n ...section(\n consumed.length === 0 ? 'Sub-commands' : 'Sub-targets',\n spec.subcommands.map((s) => [s.name, s.description] as const),\n ),\n );\n }\n\n if (spec.options && spec.options.length > 0) {\n out.push(\n ...section(\n 'Options',\n spec.options.map((o) => [o.flag, o.description] as const),\n ),\n );\n }\n\n if (spec.examples && spec.examples.length > 0) {\n const exampleItems: Array<readonly [string, string]> = spec.examples.map(\n (e) => [e.usage, e.description ?? ''] as const,\n );\n out.push(...section('Examples', exampleItems));\n }\n\n if (spec.extraNotes && spec.extraNotes.length > 0) {\n out.push('Notes:');\n for (const note of spec.extraNotes) {\n out.push(` ${note}`);\n }\n out.push('');\n }\n\n return `${out.join('\\n').trimEnd()}\\n`;\n}\n","#!/usr/bin/env node\n// @forgeax/engine-remote/src/cli - forgeax CLI binary entry (feat-20260517 D-3\n// + D-4: inspect-subcommand removed; only built-in `script` / `eval`\n// remain. M2 w8: plugin discovery (discoverPlugins) deleted alongside\n// routing layer removal. defaultConnect SSOT lives in\n// `@forgeax/engine-types/inspector-client` and is re-exported here\n// for the legacy import surface).\n//\n// Two-subcommand built-in dispatch:\n// - forgeax-engine-remote script <file>\n// - forgeax-engine-remote eval <inline-script>\n//\n// WebSocket client (D-3 / w18): the in-cli ~80-line `defaultConnect`\n// implementation was extracted to `@forgeax/engine-types/inspector-client`\n// so the engine-remote base CLI and the engine-ecs `cli-ecs` plugin bin\n// (M3) share one client recipe. The Result-form `eval(script)` /\n// `dispose()` surface replaces the legacy `request(method,params)` /\n// `close()` shape that lived inside cli.ts.\n//\n// Argparse via stdlib `node:util.parseArgs` (no commander / sade / cac\n// dep). Help body is produced by the package-internal `defineSubcommand`\n// DSL (plan-strategy D-4 + D-7).\n\nimport { readFile, realpath } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport type { RemoteError as RemoteErrorShape } from '@forgeax/engine-types';\nimport {\n type ConnectFn,\n defaultConnect,\n INSPECTOR_DEFAULT_HOST,\n INSPECTOR_DEFAULT_PORT,\n type InspectorClient,\n} from '@forgeax/engine-types/inspector-client';\nimport { defineSubcommand, renderHelp, type SubcommandSpec } from './defineSubcommand';\n\nexport type { ConnectFn, InspectorClient };\nexport { defaultConnect };\n\n// w8: LEGACY_INSPECT_TARGETS removed alongside plugin discovery deletion.\n// ─── Subcommand spec tree (sade utils.js form) ───────────────────────────────\n\nexport const FORGEAX_CLI_SPEC: SubcommandSpec = defineSubcommand({\n name: 'forgeax-engine-remote',\n description: 'remote eval CLI - drive a running forgeax engine via JSON-RPC over WS',\n options: [\n {\n flag: '--port <n>',\n description: `Inspector WebSocket port (default ${INSPECTOR_DEFAULT_PORT}; monitor uses 5731)`,\n },\n { flag: '--host <s>', description: `Host name (default ${INSPECTOR_DEFAULT_HOST})` },\n { flag: '--help, -h', description: 'Show this help and exit 0' },\n ],\n subcommands: [\n defineSubcommand({\n name: 'script',\n description: 'eval a script file against the live world/renderer/assets',\n options: [{ flag: '--help, -h', description: 'Show this help and exit 0' }],\n examples: [\n {\n usage: 'forgeax-engine-remote script ./inspect.mjs',\n description: 'eval a local script file',\n },\n ],\n }),\n defineSubcommand({\n name: 'eval',\n description: 'evaluate an inline expression against the world',\n options: [{ flag: '--help, -h', description: 'Show this help and exit 0' }],\n examples: [\n {\n usage: 'forgeax-engine-remote eval \"world.inspect().entityCount\"',\n description: 'inline read of world.inspect()',\n },\n ],\n }),\n ],\n extraNotes: [\n 'eval is full read/write access to the live world/renderer/assets/debugAdapter; the only security boundary is whether the host started the server.',\n 'Plugin discovery via PATH-prefix removed in M2 (routing layer deletion).',\n 'See also: packages/remote/README.md (eval API, live roots, security model) + AI User Charter.',\n 'Simulation inspection is read-only through eval; restore and replay are not Remote or CLI actions.',\n ],\n});\n\n// w8: Plugin discovery (discoverPlugins) removed alongside routing layer deletion.\n// renderTopLevelHelp no longer takes plugins — only built-in commands displayed.\n\nfunction renderTopLevelHelp(): string {\n const lines: string[] = [];\n lines.push(`${FORGEAX_CLI_SPEC.name} - ${FORGEAX_CLI_SPEC.description}`);\n lines.push('');\n lines.push('Usage:');\n lines.push(` ${FORGEAX_CLI_SPEC.name} <subcommand> [args]`);\n lines.push('');\n\n lines.push('Built-in commands:');\n const builtIns = FORGEAX_CLI_SPEC.subcommands ?? [];\n const builtInWidth = builtIns.reduce((m, s) => Math.max(m, s.name.length), 0);\n for (const s of builtIns) {\n const pad = ' '.repeat(builtInWidth - s.name.length + 4);\n lines.push(` ${s.name}${pad}${s.description}`);\n }\n lines.push('');\n\n if (FORGEAX_CLI_SPEC.options && FORGEAX_CLI_SPEC.options.length > 0) {\n lines.push('Options:');\n const optWidth = FORGEAX_CLI_SPEC.options.reduce((m, o) => Math.max(m, o.flag.length), 0);\n for (const o of FORGEAX_CLI_SPEC.options) {\n const pad = ' '.repeat(optWidth - o.flag.length + 4);\n lines.push(` ${o.flag}${pad}${o.description}`);\n }\n lines.push('');\n }\n\n if (FORGEAX_CLI_SPEC.extraNotes && FORGEAX_CLI_SPEC.extraNotes.length > 0) {\n lines.push('Notes:');\n for (const note of FORGEAX_CLI_SPEC.extraNotes) {\n lines.push(` ${note}`);\n }\n lines.push('');\n }\n\n return `${lines.join('\\n').trimEnd()}\\n`;\n}\n\n// w8: renderConsoleStartupFailed (plugin-discovery error rendering) removed.\n// For unknown subcommands, a simple stderr fallback is used inline.\n\n// --- Dispatch (test-injectable) ---\n\nexport interface DispatchOptions {\n readonly argv: readonly string[];\n readonly stdoutWrite: (line: string) => void;\n readonly stderrWrite: (line: string) => void;\n readonly connect: ConnectFn;\n readonly fileReader?: (path: string) => Promise<string>;\n}\n\nconst defaultFileReader = async (path: string): Promise<string> => {\n return await readFile(path, 'utf8');\n};\n\nexport async function dispatch(opts: DispatchOptions): Promise<number> {\n const { argv, stdoutWrite, stderrWrite, connect } = opts;\n const fileReader = opts.fileReader ?? defaultFileReader;\n const [, , subcommand, ...rest] = argv;\n\n if (subcommand === undefined || subcommand === '--help' || subcommand === '-h') {\n stdoutWrite(renderTopLevelHelp());\n return 0;\n }\n\n let port = INSPECTOR_DEFAULT_PORT;\n let host = INSPECTOR_DEFAULT_HOST;\n const filteredRest: string[] = [];\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n if (arg === '--port') {\n const next = rest[i + 1];\n if (typeof next === 'string') {\n const parsed = Number(next);\n if (!Number.isNaN(parsed) && parsed > 0) {\n port = parsed;\n i++;\n continue;\n }\n }\n }\n if (arg === '--host') {\n const next = rest[i + 1];\n if (typeof next === 'string') {\n host = next;\n i++;\n continue;\n }\n }\n if (typeof arg === 'string') filteredRest.push(arg);\n }\n\n switch (subcommand) {\n case 'script':\n return runScript(filteredRest, {\n stdoutWrite,\n stderrWrite,\n connect,\n port,\n host,\n fileReader,\n });\n case 'eval':\n return runEval(filteredRest, { stdoutWrite, stderrWrite, connect, port, host });\n default: {\n // CLI argument error (not a RemoteErrorCode — that closed union is the\n // wire/eval failure vocabulary, not a usage-error channel). Plain\n // usage message mirrors the script/eval missing-arg errors above.\n stderrWrite(\n `forgeax: unknown subcommand '${subcommand}'\\n expected: subcommand is one of: script, eval\\n hint: run 'forgeax-engine-remote --help' for usage\\n detail: '${subcommand}' is not a built-in subcommand (plugin discovery removed in M2)\\n`,\n );\n return 1;\n }\n }\n}\n\ninterface RunCtx {\n readonly stdoutWrite: (line: string) => void;\n readonly stderrWrite: (line: string) => void;\n readonly connect: ConnectFn;\n readonly port: number;\n readonly host: string;\n}\n\ninterface RunScriptCtx extends RunCtx {\n readonly fileReader: (path: string) => Promise<string>;\n}\n\nfunction inspectorErrorToStderr(e: RemoteErrorShape): string {\n return [`forgeax: ${e.code}`, ` expected: ${e.expected}`, ` hint: ${e.hint}`].join('\\n');\n}\n\nasync function runScript(rest: string[], ctx: RunScriptCtx): Promise<number> {\n const [file] = rest;\n if (file === '--help' || file === '-h') {\n ctx.stdoutWrite(renderHelp(FORGEAX_CLI_SPEC, ['script']));\n return 0;\n }\n if (typeof file !== 'string') {\n ctx.stderrWrite(\n [\n 'forgeax: script requires a <file> positional argument',\n ' expected: forgeax-engine-remote script <path-to-js-file>',\n \" hint: e.g. 'forgeax-engine-remote script ./inspect.mjs'\",\n ].join('\\n'),\n );\n return 1;\n }\n let body: string;\n try {\n body = await ctx.fileReader(file);\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n ctx.stderrWrite(\n [\n `forgeax: script file unreadable: ${file}`,\n ' expected: file exists and is readable',\n ` hint: check path; underlying error: ${message}`,\n ].join('\\n'),\n );\n return 1;\n }\n return invokeExecute(body, ctx);\n}\n\nasync function runEval(rest: string[], ctx: RunCtx): Promise<number> {\n const [script] = rest;\n if (script === '--help' || script === '-h') {\n ctx.stdoutWrite(renderHelp(FORGEAX_CLI_SPEC, ['eval']));\n return 0;\n }\n if (typeof script !== 'string') {\n ctx.stderrWrite(\n [\n 'forgeax: eval requires an inline <script> positional argument',\n ' expected: forgeax-engine-remote eval \"<expression>\"',\n ' hint: e.g. \\'forgeax-engine-remote eval \"world.inspect().entityCount\"\\'',\n ].join('\\n'),\n );\n return 1;\n }\n return invokeExecute(script, ctx);\n}\n\nasync function invokeExecute(script: string, ctx: RunCtx): Promise<number> {\n const url = `ws://${ctx.host}:${ctx.port}/inspector`;\n const connectResult = await ctx.connect(url);\n if (!connectResult.ok) {\n ctx.stderrWrite(inspectorErrorToStderr(connectResult.error));\n return 1;\n }\n const client = connectResult.value;\n try {\n const result = await client.eval(script);\n ctx.stdoutWrite(typeof result === 'string' ? result : JSON.stringify(result, null, 2));\n return 0;\n } catch (e) {\n if (isRemoteError(e)) {\n ctx.stderrWrite(inspectorErrorToStderr(e));\n return 1;\n }\n const message = e instanceof Error ? e.message : String(e);\n ctx.stderrWrite(\n [\n 'forgeax: execute failed',\n ' expected: server-side execute() resolves Result.ok',\n ` hint: underlying: ${message}`,\n ].join('\\n'),\n );\n return 1;\n } finally {\n await client.dispose();\n }\n}\n\nfunction isRemoteError(e: unknown): e is RemoteErrorShape {\n return (\n typeof e === 'object' &&\n e !== null &&\n typeof (e as { code?: unknown }).code === 'string' &&\n typeof (e as { expected?: unknown }).expected === 'string' &&\n typeof (e as { hint?: unknown }).hint === 'string'\n );\n}\n\n// ─── Bin entry — only runs when this module is the process entry ────────────\n\nconst isBinEntry = await (async () => {\n const argv1 = process.argv[1];\n if (typeof argv1 !== 'string') return false;\n const argv1Real = await realpath(argv1).catch(() => argv1);\n const selfReal = await realpath(fileURLToPath(import.meta.url)).catch(() =>\n fileURLToPath(import.meta.url),\n );\n return argv1Real === selfReal;\n})();\n\nif (isBinEntry) {\n const exitCode = await dispatch({\n argv: process.argv,\n stdoutWrite: (line: string) => process.stdout.write(`${line}\\n`),\n stderrWrite: (line: string) => process.stderr.write(`${line}\\n`),\n connect: defaultConnect,\n });\n process.exit(exitCode);\n}\n"]}
@@ -0,0 +1,40 @@
1
+ /** Single option entry: `--with <Name>`, `--port <number>` ... */
2
+ export interface OptionSpec {
3
+ readonly flag: string;
4
+ readonly description: string;
5
+ readonly multiple?: boolean;
6
+ readonly defaultValue?: string;
7
+ }
8
+ /** Single example block: usage line + free description. */
9
+ export interface ExampleSpec {
10
+ readonly usage: string;
11
+ readonly description?: string;
12
+ }
13
+ /** Subcommand spec — recursive (subcommands map to nested specs). */
14
+ export interface SubcommandSpec {
15
+ readonly name: string;
16
+ readonly description: string;
17
+ readonly options?: ReadonlyArray<OptionSpec>;
18
+ readonly subcommands?: ReadonlyArray<SubcommandSpec>;
19
+ readonly examples?: ReadonlyArray<ExampleSpec>;
20
+ readonly extraNotes?: ReadonlyArray<string>;
21
+ }
22
+ /**
23
+ * Single-input wrapping helper: turns a sade-style descriptor into a
24
+ * SubcommandSpec POD. Today the function is a near-identity (the spec is
25
+ * already structurally a POD), but the wrapper preserves a single intercept
26
+ * point for future validation (charter proposition 4: explicit failure on
27
+ * malformed input — we can throw here rather than silently render garbage).
28
+ */
29
+ export declare function defineSubcommand(spec: SubcommandSpec): SubcommandSpec;
30
+ /**
31
+ * Render the help body for `path` against `root`. Always returns a non-empty
32
+ * string ending in a single newline so callers can pipe to stdout without
33
+ * post-processing.
34
+ *
35
+ * Layer 1 (root): title + Usage + Sub-commands + Options + extraNotes
36
+ * Layer 2 (subcommand): title + Usage + Sub-targets (if any) + Options + Examples + extraNotes
37
+ * Layer 3 (sub-target): title + Usage + Options + Examples + extraNotes
38
+ */
39
+ export declare function renderHelp(root: SubcommandSpec, path: readonly string[]): string;
40
+ //# sourceMappingURL=defineSubcommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defineSubcommand.d.ts","sourceRoot":"","sources":["../src/defineSubcommand.ts"],"names":[],"mappings":"AAsBA,kEAAkE;AAClE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAC7C,QAAQ,CAAC,WAAW,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc,CAKrE;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAsDhF"}
package/dist/execute.d.ts CHANGED
@@ -4,6 +4,7 @@ export type ExecuteContext = {
4
4
  readonly renderer: unknown;
5
5
  readonly assets: unknown;
6
6
  readonly rhiCapture?: unknown;
7
+ readonly simulation?: unknown;
7
8
  readonly profiler?: unknown;
8
9
  readonly execution?: unknown;
9
10
  readonly importModule?: (specifier: string) => Promise<unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../src/execute.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAMvC,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACjE,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC;AAyC7F;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA8C/F"}
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../src/execute.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAMvC,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACjE,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC;AA0C7F;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA+C/F"}
package/dist/execute.mjs CHANGED
@@ -35,6 +35,7 @@ function compile(script) {
35
35
  "renderer",
36
36
  "assets",
37
37
  "rhiCapture",
38
+ "simulation",
38
39
  "profiler",
39
40
  "execution",
40
41
  "_import"
@@ -56,6 +57,7 @@ async function executeScript(script, ctx) {
56
57
  ctx.renderer,
57
58
  ctx.assets,
58
59
  ctx.rhiCapture,
60
+ ctx.simulation,
59
61
  ctx.profiler,
60
62
  ctx.execution,
61
63
  ctx.importModule ?? _import
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/execute.ts"],"names":[],"mappings":"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","file":"execute.mjs","sourcesContent":["// @forgeax/engine-remote/src/errors - RemoteError runtime class + re-export of\n// the closed `RemoteErrorCode` union; 5 members (feat-20260629-inspector-two-layer-model D-5).\n//\n// SSOT split: the **type alias** `RemoteErrorCode`\n// + **structural interface** `RemoteError` live in `@forgeax/engine-types`\n// (parallel to the existing `ShaderErrorCode` placement). This file owns\n// the **runtime class** (`extends Error` + `toJSON()`) only; the class\n// `implements` the type-side interface so the two sides cannot drift\n// (architecture-principles #1 SSOT).\n//\n// Shape (mirrors @forgeax/engine-rhi/src/errors.ts RhiError 4-field surface for\n// charter proposition 5 consistent abstraction):\n// - `RemoteErrorCode` = closed union 5 members (re-exported from types).\n// tsc strict-mode guards exhaustive switch completeness (charter\n// proposition 4); AI users consume via `switch (err.code) { case '...': ... }`\n// with NO default branch.\n// - `RemoteError` class extends Error with three readonly fields .code /\n// .expected / .hint (AGENTS.md \"Errors are structured\"). The constructor\n// auto-composes a human-readable .message (`[RemoteError <code>]\n// expected: <expected>; hint: <hint>`). The class implements the\n// `RemoteError` interface from `@forgeax/engine-types` so callers may\n// alternately type against the structural shape.\n// - `toJSON()` opts into JSON.stringify serialisation so the JSON-RPC 2.0\n// `error.data` payload carries .code / .expected / .hint / .message\n// verbatim through the WebSocket transport.\n\nimport type {\n RemoteErrorCode,\n RemoteErrorDetail,\n RemoteError as RemoteErrorShape,\n} from '@forgeax/engine-types';\n\n// Re-export the type-side alias verbatim so existing\n// `import { type RemoteErrorCode } from '@forgeax/engine-remote'` call sites\n// keep working (charter proposition 1 progressive disclosure — single\n// entry point for AI users).\nexport type { RemoteErrorCode };\n\n/**\n * Structured remote error. Four core fields plus bounded detail, mirroring `@forgeax/engine-rhi`\n * `RhiError` (charter proposition 5 consistent abstraction; AGENTS.md\n * \"Errors are structured\"). The class `implements RemoteErrorShape`\n * (the structural interface re-exported from `@forgeax/engine-types`) so the type\n * SSOT and the runtime class cannot drift.\n *\n * - `.code` closed union member (L1 key signal; switch-able).\n * - `.expected` expected-state description (L2 detail; ai-user-charter\n * proposition 4 requires expected-state copy).\n * - `.hint` actionable recovery guidance (L2 detail; charter\n * proposition 3 machine-readable hint > prose).\n * - `.message` auto-composed `[RemoteError <code>] expected: <expected>;\n * hint: <hint>` so human stack traces still surface the\n * triple. AI users prefer property access (charter\n * proposition 4: no string parsing).\n *\n * Per-code `.expected` + `.hint` templates (requirements §10.2 SSOT):\n *\n * | code | `.expected` | `.hint` |\n * |:--|:--|:--|\n * | `'script-syntax-error'` | `'script body is valid JavaScript'` | `'check syntax position in errMessage; fix and resubmit'` |\n * | `'script-runtime-error'` | `'script executes without throwing'` | `'inspect error; verify symbol availability; eval has full access to world/renderer/assets'` |\n * | `'server-startup-failed'` | `'server starts successfully on requested port'` | `'check if port is already in use (default 5732); pass different port; or kill existing process holding the port'` |\n * | `'server-not-running'` | `'server is reachable at ws://localhost:<port>'` | `'start the demo first; verify app.remote is wired; pass --port to override default 5732'` |\n * | `'eval-result-not-serializable'` | `'eval result is JSON-serializable'` | `'return a JSON-safe value; BigInt and cyclic objects are unsupported over JSON-RPC'` |\n *\n * JSON-RPC 2.0 transport contract: `toJSON()` returns the structured plain\n * object carried verbatim via `error.data` on the WebSocket envelope. The\n * JSON-RPC server-error `.code` numeric segment -32001 ~ -32005 maps 1:1\n * to the 5 members\n * at the dispatch layer.\n *\n * @example AI-user exhaustive switch on the 5 remote-domain alternatives (no default fallback)\n * ```ts\n * import { RemoteError, type RemoteErrorCode } from '@forgeax/engine-remote';\n *\n * function recover(code: RemoteErrorCode): string {\n * switch (code) {\n * case 'script-syntax-error': return 'fix script body syntax and resubmit';\n * case 'script-runtime-error': return 'inspect stack trace; verify symbol availability';\n * case 'server-startup-failed': return 'pick a different port or free port 5732';\n * case 'server-not-running': return 'start demo dev or wire app.remote';\n * case 'eval-result-not-serializable': return 'return a JSON-safe eval result';\n * }\n * }\n * ```\n */\nexport class RemoteError extends Error implements RemoteErrorShape {\n readonly code: RemoteErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: RemoteErrorDetail;\n\n constructor(args: {\n code: RemoteErrorCode;\n expected: string;\n hint: string;\n detail?: RemoteErrorDetail;\n }) {\n super(`[RemoteError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'RemoteError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n\n toJSON(): {\n readonly code: RemoteErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly message: string;\n readonly detail?: RemoteErrorDetail;\n } {\n const json = {\n code: this.code,\n expected: this.expected,\n hint: this.hint,\n message: this.message,\n };\n return this.detail === undefined ? json : { ...json, detail: this.detail };\n }\n}\n\n/**\n * SSOT mapping `RemoteErrorCode` -> JSON-RPC `error.code` numeric segment\n * (feat-20260629-inspector-two-layer-model D-5). The 5 remote P0\n * members occupy the closed segment `-32001..-32005`.\n *\n * `server.ts` consumes this map at the JSON-RPC envelope edge so the wire\n * always carries the lock-in numeric and a future drift in either direction\n * raises a TypeScript completeness error (the `Record<RemoteErrorCode,\n * number>` type guard requires every closed-union member to have a\n * numeric).\n */\nexport const REMOTE_ERROR_CODE_TO_JSONRPC: Readonly<Record<RemoteErrorCode, number>> = {\n 'script-syntax-error': -32001,\n 'script-runtime-error': -32002,\n 'server-startup-failed': -32003,\n 'server-not-running': -32004,\n 'eval-result-not-serializable': -32005,\n};\n","// @forgeax/engine-remote/src/execute — async host-realm eval.\n//\n// D-1 route B (2026-06-29): vm.runInContext does not honor\n// importModuleDynamically for Script execution. Host realm compilation via\n// the AsyncFunction constructor resolves `await import` naturally, as long\n// as the import function from the calling module scope is injected.\n//\n// CONTRACT (the one an AI user holds): the script IS the body of an async\n// function with `world` / `renderer` / `assets` / `rhiCapture` / `simulation` / `_import`\n// in scope. So all of these Just Work, un-wrapped:\n// - a bare expression: `renderer.backend` -> auto-returned\n// - top-level await: `await _import('@forgeax/engine-ecs')`\n// - top-level return: `return world.inspect().entityCount`\n// - multi-statement + return: `const m = await _import(...); return m.x`\n// (the historical `(async () => { ... })()` IIFE form still works too — its\n// returned Promise is awaited.)\n//\n// Implementation (mirrors a REPL, two construction-time-checked attempts):\n// 1. expression mode: compile `return (<script>)` — auto-returns a lone\n// expression (incl. an await-expression), preserving last-expression value.\n// 2. on SyntaxError from (1)'s CONSTRUCTION: statement mode — compile\n// `<script>` as the async body directly, legalizing top-level return +\n// await + arbitrary statements.\n// Only a construction-time SyntaxError advances attempt 1 -> 2, so user code is\n// compiled once and executed at most once (no double side effects). We never use\n// `eval`: indirect eval ran the body as a global program, which is precisely\n// what banned top-level return/await and produced the doc-vs-reality gap.\n//\n// try/catch maps errors:\n// SyntaxError (both attempts fail to compile) -> 'script-syntax-error'\n// RemoteError (re-thrown) -> verbatim\n// anything else -> 'script-runtime-error'\n//\n// The sandbox is dismantled — eval is full-access, no wrapReadOnly.\n// Timeout is removed (route B has no interrupt mechanism; see R6).\n\nimport { RemoteError } from './errors';\n\n// AsyncFunction constructor (not a global binding). An async body is what\n// legalizes top-level `await` AND top-level `return` simultaneously.\nconst AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as FunctionConstructor;\n\nexport type ExecuteContext = {\n readonly world: unknown;\n readonly renderer: unknown;\n readonly assets: unknown;\n readonly rhiCapture?: unknown;\n readonly profiler?: unknown;\n readonly execution?: unknown;\n readonly importModule?: (specifier: string) => Promise<unknown>;\n};\n\nexport type ExecuteResult = { ok: true; value: unknown } | { ok: false; error: RemoteError };\n\n// Capture import at module load time. This is the host realm's dynamic\n// import() — when injected into new Function, it resolves module\n// specifiers relative to the module that called executeScript. The remote\n// package stays package-neutral: a host may inject a capability projection\n// through ExecuteContext.importModule, but the transport does not own any\n// engine package vocabulary.\nconst _import = async (specifier: string): Promise<unknown> => import(specifier);\n\n// Compile the script as an async function body. Tries expression mode first\n// (auto-return a lone expression), falling back to statement mode on a\n// construction-time SyntaxError. Returns the compiled fn, or throws the\n// statement-mode SyntaxError if BOTH modes fail to parse.\nfunction compile(script: string): FunctionConstructor['prototype'] {\n const params = [\n 'world',\n 'renderer',\n 'assets',\n 'rhiCapture',\n 'profiler',\n 'execution',\n '_import',\n ] as const;\n try {\n // Expression mode: `return (<expr>)` auto-returns a lone expression\n // (including an await-expression), preserving last-expression-value.\n // Trailing semicolons/whitespace are trimmed so `renderer.backend;`\n // still returns its value (the old indirect-eval completion-value\n // behavior) instead of parsing as a statement that returns undefined.\n const expr = script.replace(/[\\s;]+$/, '');\n return new AsyncFunction(...params, `return (${expr}\\n)`);\n } catch (e) {\n if (!(e instanceof SyntaxError)) throw e;\n // Statement mode: the script IS the async body — legalizes top-level\n // return + await + arbitrary statements. If this also fails to parse,\n // its SyntaxError is the authoritative one to surface.\n return new AsyncFunction(...params, script);\n }\n}\n\n/**\n * Evaluate a JavaScript script against the host engine context.\n *\n * Route B (D-1): host-realm compilation via the AsyncFunction constructor with\n * injected _import. The script is the body of an async function; a lone\n * expression is auto-returned, and top-level `await` / `return` are legal.\n * - _import is available as a parameter for dynamic ESM imports.\n * - rhiCapture is available as a 4th eval-scope root for the RHI capture\n * capability (plan-strategy D-4).\n * - No sandbox — full access reads and writes.\n * - No timeout — host realm eval cannot be interrupted (see R6).\n */\nexport async function executeScript(script: string, ctx: ExecuteContext): Promise<ExecuteResult> {\n try {\n const fn = compile(script);\n // AsyncFunction always returns a Promise; await resolves the value and\n // surfaces any runtime throw into this catch.\n const value: unknown = await fn(\n ctx.world,\n ctx.renderer,\n ctx.assets,\n ctx.rhiCapture,\n ctx.profiler,\n ctx.execution,\n ctx.importModule ?? _import,\n );\n\n return { ok: true, value };\n } catch (e) {\n // 1. RemoteError re-thrown from within the script surfaces verbatim.\n if (e instanceof RemoteError) {\n return { ok: false, error: e };\n }\n\n // 2. SyntaxError: both compile() attempts failed to parse the script.\n if (e instanceof SyntaxError) {\n const msg = e.message;\n return {\n ok: false,\n error: new RemoteError({\n code: 'script-syntax-error',\n expected: 'script body is valid JavaScript',\n hint: `check syntax near: ${msg}; fix and resubmit`,\n }),\n };\n }\n\n // 3. Runtime error (throws during function execution).\n const rawMessage = e instanceof Error ? e.message : String(e);\n return {\n ok: false,\n error: new RemoteError({\n code: 'script-runtime-error',\n expected: 'script executes without throwing',\n hint: `inspect error; verify symbol availability; eval has full access to world/renderer/assets (errMessage: ${rawMessage})`,\n }),\n };\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/execute.ts"],"names":[],"mappings":"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","file":"execute.mjs","sourcesContent":["// @forgeax/engine-remote/src/errors - RemoteError runtime class + re-export of\n// the closed `RemoteErrorCode` union; 5 members (feat-20260629-inspector-two-layer-model D-5).\n//\n// SSOT split: the **type alias** `RemoteErrorCode`\n// + **structural interface** `RemoteError` live in `@forgeax/engine-types`\n// (parallel to the existing `ShaderErrorCode` placement). This file owns\n// the **runtime class** (`extends Error` + `toJSON()`) only; the class\n// `implements` the type-side interface so the two sides cannot drift\n// (architecture-principles #1 SSOT).\n//\n// Shape (mirrors @forgeax/engine-rhi/src/errors.ts RhiError 4-field surface for\n// charter proposition 5 consistent abstraction):\n// - `RemoteErrorCode` = closed union 5 members (re-exported from types).\n// tsc strict-mode guards exhaustive switch completeness (charter\n// proposition 4); AI users consume via `switch (err.code) { case '...': ... }`\n// with NO default branch.\n// - `RemoteError` class extends Error with three readonly fields .code /\n// .expected / .hint (AGENTS.md \"Errors are structured\"). The constructor\n// auto-composes a human-readable .message (`[RemoteError <code>]\n// expected: <expected>; hint: <hint>`). The class implements the\n// `RemoteError` interface from `@forgeax/engine-types` so callers may\n// alternately type against the structural shape.\n// - `toJSON()` opts into JSON.stringify serialisation so the JSON-RPC 2.0\n// `error.data` payload carries .code / .expected / .hint / .message\n// verbatim through the WebSocket transport.\n\nimport type {\n RemoteErrorCode,\n RemoteErrorDetail,\n RemoteError as RemoteErrorShape,\n} from '@forgeax/engine-types';\n\n// Re-export the type-side alias verbatim so existing\n// `import { type RemoteErrorCode } from '@forgeax/engine-remote'` call sites\n// keep working (charter proposition 1 progressive disclosure — single\n// entry point for AI users).\nexport type { RemoteErrorCode };\n\n/**\n * Structured remote error. Four core fields plus bounded detail, mirroring `@forgeax/engine-rhi`\n * `RhiError` (charter proposition 5 consistent abstraction; AGENTS.md\n * \"Errors are structured\"). The class `implements RemoteErrorShape`\n * (the structural interface re-exported from `@forgeax/engine-types`) so the type\n * SSOT and the runtime class cannot drift.\n *\n * - `.code` closed union member (L1 key signal; switch-able).\n * - `.expected` expected-state description (L2 detail; ai-user-charter\n * proposition 4 requires expected-state copy).\n * - `.hint` actionable recovery guidance (L2 detail; charter\n * proposition 3 machine-readable hint > prose).\n * - `.message` auto-composed `[RemoteError <code>] expected: <expected>;\n * hint: <hint>` so human stack traces still surface the\n * triple. AI users prefer property access (charter\n * proposition 4: no string parsing).\n *\n * Per-code `.expected` + `.hint` templates (requirements §10.2 SSOT):\n *\n * | code | `.expected` | `.hint` |\n * |:--|:--|:--|\n * | `'script-syntax-error'` | `'script body is valid JavaScript'` | `'check syntax position in errMessage; fix and resubmit'` |\n * | `'script-runtime-error'` | `'script executes without throwing'` | `'inspect error; verify symbol availability; eval has full access to world/renderer/assets'` |\n * | `'server-startup-failed'` | `'server starts successfully on requested port'` | `'check if port is already in use (default 5732); pass different port; or kill existing process holding the port'` |\n * | `'server-not-running'` | `'server is reachable at ws://localhost:<port>'` | `'start the demo first; verify app.remote is wired; pass --port to override default 5732'` |\n * | `'eval-result-not-serializable'` | `'eval result is JSON-serializable'` | `'return a JSON-safe value; BigInt and cyclic objects are unsupported over JSON-RPC'` |\n *\n * JSON-RPC 2.0 transport contract: `toJSON()` returns the structured plain\n * object carried verbatim via `error.data` on the WebSocket envelope. The\n * JSON-RPC server-error `.code` numeric segment -32001 ~ -32005 maps 1:1\n * to the 5 members\n * at the dispatch layer.\n *\n * @example AI-user exhaustive switch on the 5 remote-domain alternatives (no default fallback)\n * ```ts\n * import { RemoteError, type RemoteErrorCode } from '@forgeax/engine-remote';\n *\n * function recover(code: RemoteErrorCode): string {\n * switch (code) {\n * case 'script-syntax-error': return 'fix script body syntax and resubmit';\n * case 'script-runtime-error': return 'inspect stack trace; verify symbol availability';\n * case 'server-startup-failed': return 'pick a different port or free port 5732';\n * case 'server-not-running': return 'start demo dev or wire app.remote';\n * case 'eval-result-not-serializable': return 'return a JSON-safe eval result';\n * }\n * }\n * ```\n */\nexport class RemoteError extends Error implements RemoteErrorShape {\n readonly code: RemoteErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: RemoteErrorDetail;\n\n constructor(args: {\n code: RemoteErrorCode;\n expected: string;\n hint: string;\n detail?: RemoteErrorDetail;\n }) {\n super(`[RemoteError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'RemoteError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n\n toJSON(): {\n readonly code: RemoteErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly message: string;\n readonly detail?: RemoteErrorDetail;\n } {\n const json = {\n code: this.code,\n expected: this.expected,\n hint: this.hint,\n message: this.message,\n };\n return this.detail === undefined ? json : { ...json, detail: this.detail };\n }\n}\n\n/**\n * SSOT mapping `RemoteErrorCode` -> JSON-RPC `error.code` numeric segment\n * (feat-20260629-inspector-two-layer-model D-5). The 5 remote P0\n * members occupy the closed segment `-32001..-32005`.\n *\n * `server.ts` consumes this map at the JSON-RPC envelope edge so the wire\n * always carries the lock-in numeric and a future drift in either direction\n * raises a TypeScript completeness error (the `Record<RemoteErrorCode,\n * number>` type guard requires every closed-union member to have a\n * numeric).\n */\nexport const REMOTE_ERROR_CODE_TO_JSONRPC: Readonly<Record<RemoteErrorCode, number>> = {\n 'script-syntax-error': -32001,\n 'script-runtime-error': -32002,\n 'server-startup-failed': -32003,\n 'server-not-running': -32004,\n 'eval-result-not-serializable': -32005,\n};\n","// @forgeax/engine-remote/src/execute — async host-realm eval.\n//\n// D-1 route B (2026-06-29): vm.runInContext does not honor\n// importModuleDynamically for Script execution. Host realm compilation via\n// the AsyncFunction constructor resolves `await import` naturally, as long\n// as the import function from the calling module scope is injected.\n//\n// CONTRACT (the one an AI user holds): the script IS the body of an async\n// function with `world` / `renderer` / `assets` / `rhiCapture` / `simulation` / `_import`\n// in scope. So all of these Just Work, un-wrapped:\n// - a bare expression: `renderer.backend` -> auto-returned\n// - top-level await: `await _import('engine-module')`\n// - top-level return: `return world.inspect().entityCount`\n// - multi-statement + return: `const m = await _import(...); return m.x`\n// (the historical `(async () => { ... })()` IIFE form still works too — its\n// returned Promise is awaited.)\n//\n// Implementation (mirrors a REPL, two construction-time-checked attempts):\n// 1. expression mode: compile `return (<script>)` — auto-returns a lone\n// expression (incl. an await-expression), preserving last-expression value.\n// 2. on SyntaxError from (1)'s CONSTRUCTION: statement mode — compile\n// `<script>` as the async body directly, legalizing top-level return +\n// await + arbitrary statements.\n// Only a construction-time SyntaxError advances attempt 1 -> 2, so user code is\n// compiled once and executed at most once (no double side effects). We never use\n// `eval`: indirect eval ran the body as a global program, which is precisely\n// what banned top-level return/await and produced the doc-vs-reality gap.\n//\n// try/catch maps errors:\n// SyntaxError (both attempts fail to compile) -> 'script-syntax-error'\n// RemoteError (re-thrown) -> verbatim\n// anything else -> 'script-runtime-error'\n//\n// The sandbox is dismantled — eval is full-access, no wrapReadOnly.\n// Timeout is removed (route B has no interrupt mechanism; see R6).\n\nimport { RemoteError } from './errors';\n\n// AsyncFunction constructor (not a global binding). An async body is what\n// legalizes top-level `await` AND top-level `return` simultaneously.\nconst AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as FunctionConstructor;\n\nexport type ExecuteContext = {\n readonly world: unknown;\n readonly renderer: unknown;\n readonly assets: unknown;\n readonly rhiCapture?: unknown;\n readonly simulation?: unknown;\n readonly profiler?: unknown;\n readonly execution?: unknown;\n readonly importModule?: (specifier: string) => Promise<unknown>;\n};\n\nexport type ExecuteResult = { ok: true; value: unknown } | { ok: false; error: RemoteError };\n\n// Capture import at module load time. This is the host realm's dynamic\n// import() — when injected into new Function, it resolves module\n// specifiers relative to the module that called executeScript. The remote\n// package stays package-neutral: a host may inject a capability projection\n// through ExecuteContext.importModule, but the transport does not own any\n// engine package vocabulary.\nconst _import = async (specifier: string): Promise<unknown> => import(specifier);\n\n// Compile the script as an async function body. Tries expression mode first\n// (auto-return a lone expression), falling back to statement mode on a\n// construction-time SyntaxError. Returns the compiled fn, or throws the\n// statement-mode SyntaxError if BOTH modes fail to parse.\nfunction compile(script: string): FunctionConstructor['prototype'] {\n const params = [\n 'world',\n 'renderer',\n 'assets',\n 'rhiCapture',\n 'simulation',\n 'profiler',\n 'execution',\n '_import',\n ] as const;\n try {\n // Expression mode: `return (<expr>)` auto-returns a lone expression\n // (including an await-expression), preserving last-expression-value.\n // Trailing semicolons/whitespace are trimmed so `renderer.backend;`\n // still returns its value (the old indirect-eval completion-value\n // behavior) instead of parsing as a statement that returns undefined.\n const expr = script.replace(/[\\s;]+$/, '');\n return new AsyncFunction(...params, `return (${expr}\\n)`);\n } catch (e) {\n if (!(e instanceof SyntaxError)) throw e;\n // Statement mode: the script IS the async body — legalizes top-level\n // return + await + arbitrary statements. If this also fails to parse,\n // its SyntaxError is the authoritative one to surface.\n return new AsyncFunction(...params, script);\n }\n}\n\n/**\n * Evaluate a JavaScript script against the host engine context.\n *\n * Route B (D-1): host-realm compilation via the AsyncFunction constructor with\n * injected _import. The script is the body of an async function; a lone\n * expression is auto-returned, and top-level `await` / `return` are legal.\n * - _import is available as a parameter for dynamic ESM imports.\n * - rhiCapture is available as a 4th eval-scope root for the RHI capture\n * capability (plan-strategy D-4).\n * - No sandbox — full access reads and writes.\n * - No timeout — host realm eval cannot be interrupted (see R6).\n */\nexport async function executeScript(script: string, ctx: ExecuteContext): Promise<ExecuteResult> {\n try {\n const fn = compile(script);\n // AsyncFunction always returns a Promise; await resolves the value and\n // surfaces any runtime throw into this catch.\n const value: unknown = await fn(\n ctx.world,\n ctx.renderer,\n ctx.assets,\n ctx.rhiCapture,\n ctx.simulation,\n ctx.profiler,\n ctx.execution,\n ctx.importModule ?? _import,\n );\n\n return { ok: true, value };\n } catch (e) {\n // 1. RemoteError re-thrown from within the script surfaces verbatim.\n if (e instanceof RemoteError) {\n return { ok: false, error: e };\n }\n\n // 2. SyntaxError: both compile() attempts failed to parse the script.\n if (e instanceof SyntaxError) {\n const msg = e.message;\n return {\n ok: false,\n error: new RemoteError({\n code: 'script-syntax-error',\n expected: 'script body is valid JavaScript',\n hint: `check syntax near: ${msg}; fix and resubmit`,\n }),\n };\n }\n\n // 3. Runtime error (throws during function execution).\n const rawMessage = e instanceof Error ? e.message : String(e);\n return {\n ok: false,\n error: new RemoteError({\n code: 'script-runtime-error',\n expected: 'script executes without throwing',\n hint: `inspect error; verify symbol availability; eval has full access to world/renderer/assets (errMessage: ${rawMessage})`,\n }),\n };\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC"}
@@ -11,6 +11,8 @@ export interface RemoteRootValues {
11
11
  readonly rhiCapture?: unknown;
12
12
  readonly profiler?: unknown;
13
13
  readonly execution?: unknown;
14
+ /** Read-only World-owned simulation summary; no restore/replay operation. */
15
+ readonly simulation?: unknown;
14
16
  readonly introspection?: readonly ComponentIntrospectionDescriptor[];
15
17
  }
16
18
  export declare function isProfilerRoot(value: unknown): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../src/introspect.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,gCAAgC,EAAE,CAAC;CACtE;AAiBD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CActD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI;IAAE,MAAM,IAAI,OAAO,CAAA;CAAE,CAM9E;AA8GD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAuD/F"}
1
+ {"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../src/introspect.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,gCAAgC,EAAE,CAAC;CACtE;AAcD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CActD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI;IAAE,MAAM,IAAI,OAAO,CAAA;CAAE,CAM9E;AA0GD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAuD/F"}
@@ -46,19 +46,16 @@ function projectRoot(name, value) {
46
46
  execution: {
47
47
  type: "ExecutionReportProvider",
48
48
  description: "The host execution report provider for tier, health, performance, and fault."
49
+ },
50
+ simulation: {
51
+ type: "SimulationInspection",
52
+ description: "A read-only World-owned simulation record, participant, trace, and report summary."
49
53
  }
50
54
  };
51
55
  const descriptor = descriptions[name] ?? { type: "unknown", description: "A live eval root." };
52
56
  return {
53
57
  available: true,
54
58
  ...descriptor,
55
- ...name === "assets" ? {
56
- operations: {
57
- load: "assets.load(guid, expectedKind)",
58
- snapshot: "assets.snapshot()",
59
- subscribe: "assets.subscribe(listener)"
60
- }
61
- } : {},
62
59
  ...name === "profiler" ? {
63
60
  capability: "cpu-profile-v1",
64
61
  operations: {
@@ -136,7 +133,7 @@ function buildIntrospectDoc(host, port, roots) {
136
133
  info: {
137
134
  title: "@forgeax/engine-remote remote eval",
138
135
  version: "0.0.0",
139
- description: "Remote eval server. Methods: eval / introspect. Errors map to JSON-RPC -32001..-32005."
136
+ description: "Remote eval server. Methods: eval / introspect. Errors map to JSON-RPC -32001..-32006."
140
137
  },
141
138
  servers: [{ name: "in-process", url: `ws://${host}:${port}/inspector` }],
142
139
  methods: [