@forgeax/engine-remote 0.0.0-dev.8d955ade1c79

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 (77) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +269 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/cli-defaults.unit.test.d.ts +2 -0
  5. package/dist/__tests__/cli-defaults.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/console.unit.test.d.ts +2 -0
  7. package/dist/__tests__/console.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  9. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/execute-browser-safe.unit.test.d.ts +2 -0
  11. package/dist/__tests__/execute-browser-safe.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/execute-profiler-root.browser.test.d.ts +2 -0
  13. package/dist/__tests__/execute-profiler-root.browser.test.d.ts.map +1 -0
  14. package/dist/__tests__/execute.async.test.d.ts +2 -0
  15. package/dist/__tests__/execute.async.test.d.ts.map +1 -0
  16. package/dist/__tests__/execution-report-root.unit.test.d.ts +2 -0
  17. package/dist/__tests__/execution-report-root.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/introspect-profiler.unit.test.d.ts +2 -0
  19. package/dist/__tests__/introspect-profiler.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/method-roster-profiler.test.d.ts +2 -0
  21. package/dist/__tests__/method-roster-profiler.test.d.ts.map +1 -0
  22. package/dist/__tests__/rhi-capture-remote.integration.test.d.ts +2 -0
  23. package/dist/__tests__/rhi-capture-remote.integration.test.d.ts.map +1 -0
  24. package/dist/__tests__/server.unit.test.d.ts +2 -0
  25. package/dist/__tests__/server.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/simulation-inspect.integration.test.d.ts +2 -0
  27. package/dist/__tests__/simulation-inspect.integration.test.d.ts.map +1 -0
  28. package/dist/cli.d.ts +15 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.mjs +336 -0
  31. package/dist/cli.mjs.map +1 -0
  32. package/dist/defineSubcommand.d.ts +40 -0
  33. package/dist/defineSubcommand.d.ts.map +1 -0
  34. package/dist/error-messages.d.ts +3 -0
  35. package/dist/error-messages.d.ts.map +1 -0
  36. package/dist/errors.d.ts +82 -0
  37. package/dist/errors.d.ts.map +1 -0
  38. package/dist/errors.mjs +37 -0
  39. package/dist/errors.mjs.map +1 -0
  40. package/dist/execute.d.ts +32 -0
  41. package/dist/execute.d.ts.map +1 -0
  42. package/dist/execute.mjs +95 -0
  43. package/dist/execute.mjs.map +1 -0
  44. package/dist/index.d.ts +2 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.mjs +30 -0
  47. package/dist/index.mjs.map +1 -0
  48. package/dist/introspect.d.ts +23 -0
  49. package/dist/introspect.d.ts.map +1 -0
  50. package/dist/introspect.mjs +170 -0
  51. package/dist/introspect.mjs.map +1 -0
  52. package/dist/server.d.ts +35 -0
  53. package/dist/server.d.ts.map +1 -0
  54. package/dist/server.mjs +435 -0
  55. package/dist/server.mjs.map +1 -0
  56. package/package.json +81 -0
  57. package/src/__tests__/cli-defaults.unit.test.ts +69 -0
  58. package/src/__tests__/console.unit.test.ts +679 -0
  59. package/src/__tests__/errors.unit.test.ts +88 -0
  60. package/src/__tests__/execute-browser-safe.unit.test.ts +43 -0
  61. package/src/__tests__/execute-profiler-root.browser.test.ts +41 -0
  62. package/src/__tests__/execute.async.test.ts +373 -0
  63. package/src/__tests__/execution-report-root.unit.test.ts +38 -0
  64. package/src/__tests__/introspect-profiler.unit.test.ts +55 -0
  65. package/src/__tests__/method-roster-profiler.test.ts +80 -0
  66. package/src/__tests__/rhi-capture-remote.integration.test.ts +58 -0
  67. package/src/__tests__/server.unit.test.ts +613 -0
  68. package/src/__tests__/simulation-inspect.integration.test.ts +67 -0
  69. package/src/__tests__/vm-async-eval-verify.mjs +149 -0
  70. package/src/cli.ts +333 -0
  71. package/src/defineSubcommand.ts +169 -0
  72. package/src/error-messages.ts +9 -0
  73. package/src/errors.ts +143 -0
  74. package/src/execute.ts +155 -0
  75. package/src/index.ts +20 -0
  76. package/src/introspect.ts +218 -0
  77. package/src/server.ts +285 -0
@@ -0,0 +1,170 @@
1
+ // src/error-messages.ts
2
+ var REMOTE_ERROR_MESSAGES = {
3
+ "script-syntax-error": "Script syntax error",
4
+ "script-runtime-error": "Script runtime error",
5
+ "server-startup-failed": "Server startup failed",
6
+ "server-not-running": "Server not reachable",
7
+ "eval-result-not-serializable": "Eval result not serializable"
8
+ };
9
+
10
+ // src/errors.ts
11
+ var REMOTE_ERROR_CODE_TO_JSONRPC = {
12
+ "script-syntax-error": -32001,
13
+ "script-runtime-error": -32002,
14
+ "server-startup-failed": -32003,
15
+ "server-not-running": -32004,
16
+ "eval-result-not-serializable": -32005
17
+ };
18
+
19
+ // src/introspect.ts
20
+ function isProfilerRoot(value) {
21
+ if (value === null || typeof value !== "object") return false;
22
+ const root = value;
23
+ return typeof root.startCapture === "function" && typeof root.latestCapture === "function" && typeof root.activeSession === "function" && root.phaseCatalog !== void 0;
24
+ }
25
+ function isExecutionRoot(value) {
26
+ return value !== null && typeof value === "object" && typeof value.report === "function";
27
+ }
28
+ function profilerPhaseCatalog(value) {
29
+ if (value === null || typeof value !== "object") return void 0;
30
+ const catalog = value.phaseCatalog;
31
+ return catalog === void 0 ? void 0 : catalog;
32
+ }
33
+ function projectRoot(name, value) {
34
+ const descriptions = {
35
+ world: { type: "World", description: "The host World instance." },
36
+ renderer: { type: "Renderer", description: "The host Renderer instance." },
37
+ assets: { type: "AssetRegistry", description: "The host AssetRegistry instance." },
38
+ rhiCapture: {
39
+ type: "RhiCapture",
40
+ description: "The opt-in RHI frame capture capability."
41
+ },
42
+ profiler: {
43
+ type: "Profiler",
44
+ description: "The opt-in CPU profiler for bounded App and Render capture."
45
+ },
46
+ execution: {
47
+ type: "ExecutionReportProvider",
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."
53
+ }
54
+ };
55
+ const descriptor = descriptions[name] ?? { type: "unknown", description: "A live eval root." };
56
+ return {
57
+ available: true,
58
+ ...descriptor,
59
+ ...name === "profiler" ? {
60
+ capability: "cpu-profile-v1",
61
+ operations: {
62
+ startCapture: "profiler.startCapture({ frameLimit, eventLimit })",
63
+ latestCapture: "profiler.latestCapture() after the host reaches the frame boundary"
64
+ },
65
+ ...profilerPhaseCatalog(value) === void 0 ? {} : { phaseCatalog: profilerPhaseCatalog(value) }
66
+ } : name === "execution" ? {
67
+ capability: "execution-report-v1"
68
+ } : name === "rhiCapture" ? {
69
+ capability: "rhi-capture-v1"
70
+ } : {}
71
+ };
72
+ }
73
+ function projectRoots(roots) {
74
+ const projected = {};
75
+ for (const [name, value] of Object.entries(roots)) {
76
+ if (value !== void 0 && (name !== "profiler" || isProfilerRoot(value)) && (name !== "execution" || isExecutionRoot(value))) {
77
+ projected[name] = projectRoot(name, value);
78
+ }
79
+ }
80
+ return projected;
81
+ }
82
+ function profilerCapability(roots) {
83
+ if (roots.profiler !== void 0) {
84
+ return {
85
+ enabled: true,
86
+ capability: "cpu-profile-v1",
87
+ limits: {
88
+ frameLimit: "positive-safe-integer",
89
+ eventLimit: "positive-safe-integer"
90
+ }
91
+ };
92
+ }
93
+ return {
94
+ enabled: false,
95
+ code: "profiler-not-enabled",
96
+ expected: "an explicitly opted-in profiler root",
97
+ hint: "Pass profiler: createProfiler() to createApp or startServer in development, then retry.",
98
+ detail: { enabled: false },
99
+ limits: {
100
+ frameLimit: "positive-safe-integer",
101
+ eventLimit: "positive-safe-integer"
102
+ }
103
+ };
104
+ }
105
+ function buildErrorProjection() {
106
+ const errors = {};
107
+ for (const [code, numericCode] of Object.entries(REMOTE_ERROR_CODE_TO_JSONRPC)) {
108
+ errors[code] = { code: numericCode, message: REMOTE_ERROR_MESSAGES[code] };
109
+ }
110
+ return errors;
111
+ }
112
+ function buildIntrospectDoc(host, port, roots) {
113
+ const projectedRoots = projectRoots(roots);
114
+ const schemas = {
115
+ World: { type: "object", description: "The host World instance." },
116
+ Renderer: { type: "object", description: "The host Renderer instance." },
117
+ Assets: { type: "object", description: "The host AssetRegistry instance." }
118
+ };
119
+ for (const [name, root] of Object.entries(projectedRoots)) {
120
+ schemas[root.type] = { type: "object", description: root.description };
121
+ if (name === "profiler") {
122
+ schemas.ProfilerCapture = {
123
+ type: "object",
124
+ description: "A bounded ProfileCapture v1 artifact returned through eval."
125
+ };
126
+ }
127
+ }
128
+ for (const descriptor of roots.introspection ?? []) {
129
+ schemas[descriptor.name] = descriptor;
130
+ }
131
+ return {
132
+ openrpc: "1.3.2",
133
+ info: {
134
+ title: "@forgeax/engine-remote remote eval",
135
+ version: "0.0.0",
136
+ description: "Remote eval server. Methods: eval / introspect. Errors map to JSON-RPC -32001..-32006."
137
+ },
138
+ servers: [{ name: "in-process", url: `ws://${host}:${port}/inspector` }],
139
+ methods: [
140
+ {
141
+ name: "eval",
142
+ summary: "Evaluate a JavaScript script against live eval roots.",
143
+ params: [
144
+ {
145
+ name: "script",
146
+ required: true,
147
+ schema: { type: "string" }
148
+ }
149
+ ],
150
+ result: { name: "value", schema: { type: "object" } }
151
+ },
152
+ {
153
+ name: "introspect",
154
+ summary: "Return this OpenRPC L2 subset document.",
155
+ params: [],
156
+ result: { name: "document", schema: { type: "object" } }
157
+ }
158
+ ],
159
+ roots: projectedRoots,
160
+ capabilities: { profiler: profilerCapability(projectedRoots) },
161
+ components: {
162
+ schemas,
163
+ errors: buildErrorProjection()
164
+ }
165
+ };
166
+ }
167
+
168
+ export { buildIntrospectDoc, isExecutionRoot, isProfilerRoot };
169
+ //# sourceMappingURL=introspect.mjs.map
170
+ //# sourceMappingURL=introspect.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error-messages.ts","../src/errors.ts","../src/introspect.ts"],"names":[],"mappings":"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","file":"introspect.mjs","sourcesContent":["import type { RemoteErrorCode } from './errors';\n\nexport const REMOTE_ERROR_MESSAGES: Readonly<Record<RemoteErrorCode, string>> = {\n 'script-syntax-error': 'Script syntax error',\n 'script-runtime-error': 'Script runtime error',\n 'server-startup-failed': 'Server startup failed',\n 'server-not-running': 'Server not reachable',\n 'eval-result-not-serializable': 'Eval result not serializable',\n};\n","// @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","import { REMOTE_ERROR_MESSAGES } from './error-messages';\nimport { REMOTE_ERROR_CODE_TO_JSONRPC, type RemoteErrorCode } from './errors';\n\nexport interface ComponentIntrospectionDescriptor {\n readonly name: string;\n readonly schema: Readonly<Record<string, string>>;\n readonly fields: Readonly<Record<string, unknown>>;\n readonly meta: Readonly<Record<string, unknown>>;\n}\n\nexport interface RemoteRootValues {\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 /** Read-only World-owned simulation summary; no restore/replay operation. */\n readonly simulation?: unknown;\n readonly introspection?: readonly ComponentIntrospectionDescriptor[];\n}\n\ntype RootProjection = {\n readonly available: true;\n readonly type: string;\n readonly description: string;\n readonly capability?: string;\n readonly phaseCatalog?: unknown;\n readonly operations?: {\n readonly startCapture: string;\n readonly latestCapture: string;\n };\n};\n\nexport function isProfilerRoot(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const root = value as {\n startCapture?: unknown;\n latestCapture?: unknown;\n activeSession?: unknown;\n phaseCatalog?: unknown;\n };\n return (\n typeof root.startCapture === 'function' &&\n typeof root.latestCapture === 'function' &&\n typeof root.activeSession === 'function' &&\n root.phaseCatalog !== undefined\n );\n}\n\nexport function isExecutionRoot(value: unknown): value is { report(): unknown } {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as { report?: unknown }).report === 'function'\n );\n}\n\nfunction profilerPhaseCatalog(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return undefined;\n const catalog = (value as { phaseCatalog?: unknown }).phaseCatalog;\n return catalog === undefined ? undefined : catalog;\n}\n\nfunction projectRoot(name: string, value: unknown): RootProjection {\n const descriptions: Record<string, { type: string; description: string }> = {\n world: { type: 'World', description: 'The host World instance.' },\n renderer: { type: 'Renderer', description: 'The host Renderer instance.' },\n assets: { type: 'AssetRegistry', description: 'The host AssetRegistry instance.' },\n rhiCapture: {\n type: 'RhiCapture',\n description: 'The opt-in RHI frame capture capability.',\n },\n profiler: {\n type: 'Profiler',\n description: 'The opt-in CPU profiler for bounded App and Render capture.',\n },\n execution: {\n type: 'ExecutionReportProvider',\n description: 'The host execution report provider for tier, health, performance, and fault.',\n },\n simulation: {\n type: 'SimulationInspection',\n description:\n 'A read-only World-owned simulation record, participant, trace, and report summary.',\n },\n };\n const descriptor = descriptions[name] ?? { type: 'unknown', description: 'A live eval root.' };\n return {\n available: true,\n ...descriptor,\n ...(name === 'profiler'\n ? {\n capability: 'cpu-profile-v1',\n operations: {\n startCapture: 'profiler.startCapture({ frameLimit, eventLimit })',\n latestCapture: 'profiler.latestCapture() after the host reaches the frame boundary',\n },\n ...(profilerPhaseCatalog(value) === undefined\n ? {}\n : { phaseCatalog: profilerPhaseCatalog(value) }),\n }\n : name === 'execution'\n ? {\n capability: 'execution-report-v1',\n }\n : name === 'rhiCapture'\n ? {\n capability: 'rhi-capture-v1',\n }\n : {}),\n };\n}\n\nfunction projectRoots(roots: RemoteRootValues): Record<string, RootProjection> {\n const projected: Record<string, RootProjection> = {};\n for (const [name, value] of Object.entries(roots)) {\n if (\n value !== undefined &&\n (name !== 'profiler' || isProfilerRoot(value)) &&\n (name !== 'execution' || isExecutionRoot(value))\n ) {\n projected[name] = projectRoot(name, value);\n }\n }\n return projected;\n}\n\nfunction profilerCapability(roots: Record<string, RootProjection>): Record<string, unknown> {\n if (roots.profiler !== undefined) {\n return {\n enabled: true,\n capability: 'cpu-profile-v1',\n limits: {\n frameLimit: 'positive-safe-integer',\n eventLimit: 'positive-safe-integer',\n },\n };\n }\n return {\n enabled: false,\n code: 'profiler-not-enabled',\n expected: 'an explicitly opted-in profiler root',\n hint: 'Pass profiler: createProfiler() to createApp or startServer in development, then retry.',\n detail: { enabled: false },\n limits: {\n frameLimit: 'positive-safe-integer',\n eventLimit: 'positive-safe-integer',\n },\n };\n}\n\nfunction buildErrorProjection(): Record<string, { code: number; message: string }> {\n const errors: Record<string, { code: number; message: string }> = {};\n for (const [code, numericCode] of Object.entries(REMOTE_ERROR_CODE_TO_JSONRPC) as Array<\n [RemoteErrorCode, number]\n >) {\n errors[code] = { code: numericCode, message: REMOTE_ERROR_MESSAGES[code] };\n }\n return errors;\n}\n\nexport function buildIntrospectDoc(host: string, port: number, roots: RemoteRootValues): unknown {\n const projectedRoots = projectRoots(roots);\n const schemas: Record<string, unknown> = {\n World: { type: 'object', description: 'The host World instance.' },\n Renderer: { type: 'object', description: 'The host Renderer instance.' },\n Assets: { type: 'object', description: 'The host AssetRegistry instance.' },\n };\n for (const [name, root] of Object.entries(projectedRoots)) {\n schemas[root.type] = { type: 'object', description: root.description };\n if (name === 'profiler') {\n schemas.ProfilerCapture = {\n type: 'object',\n description: 'A bounded ProfileCapture v1 artifact returned through eval.',\n };\n }\n }\n for (const descriptor of roots.introspection ?? []) {\n schemas[descriptor.name] = descriptor;\n }\n return {\n openrpc: '1.3.2',\n info: {\n title: '@forgeax/engine-remote remote eval',\n version: '0.0.0',\n description:\n 'Remote eval server. Methods: eval / introspect. Errors map to JSON-RPC -32001..-32006.',\n },\n servers: [{ name: 'in-process', url: `ws://${host}:${port}/inspector` }],\n methods: [\n {\n name: 'eval',\n summary: 'Evaluate a JavaScript script against live eval roots.',\n params: [\n {\n name: 'script',\n required: true,\n schema: { type: 'string' },\n },\n ],\n result: { name: 'value', schema: { type: 'object' } },\n },\n {\n name: 'introspect',\n summary: 'Return this OpenRPC L2 subset document.',\n params: [],\n result: { name: 'document', schema: { type: 'object' } },\n },\n ],\n roots: projectedRoots,\n capabilities: { profiler: profilerCapability(projectedRoots) },\n components: {\n schemas,\n errors: buildErrorProjection(),\n },\n };\n}\n"]}
@@ -0,0 +1,35 @@
1
+ import { type Result } from '@forgeax/engine-types';
2
+ import { RemoteError } from './errors';
3
+ import { type ComponentIntrospectionDescriptor } from './introspect';
4
+ export type { ComponentIntrospectionDescriptor } from './introspect';
5
+ export type { Result };
6
+ export type ConsoleHandle = {
7
+ readonly port: number;
8
+ readonly close: () => Promise<void>;
9
+ };
10
+ export type StartServerOptions = {
11
+ readonly port: number;
12
+ readonly host?: string;
13
+ readonly world: unknown;
14
+ readonly renderer?: unknown;
15
+ readonly assets?: unknown;
16
+ /** JSON-safe host reflection; the remote package does not know component owners. */
17
+ readonly introspection?: readonly ComponentIntrospectionDescriptor[];
18
+ /** Explicit CPU profiler capability; omitted unless the host opts in. */
19
+ readonly profiler?: unknown;
20
+ /** Structural report provider; remote never imports the App owner. */
21
+ readonly execution?: unknown;
22
+ /** Read-only World-owned simulation inspection root. */
23
+ readonly simulation?: unknown;
24
+ /**
25
+ * Host-owned RHI capture capability for eval-scope injection (plan-strategy D-4).
26
+ * It is exposed as the single `rhiCapture` eval root.
27
+ * Undefined when FORGEAX_ENGINE_RHI_DEBUG !== '1'.
28
+ */
29
+ readonly rhiCapture?: unknown;
30
+ };
31
+ /**
32
+ * Start the remote eval WebSocket server.
33
+ */
34
+ export declare function startServer(opts: StartServerOptions): Promise<Result<ConsoleHandle, RemoteError>>;
35
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAYA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAI7D,OAAO,EAAgC,WAAW,EAAE,MAAM,UAAU,CAAC;AAErE,OAAO,EAEL,KAAK,gCAAgC,EAGtC,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,gCAAgC,EAAE,MAAM,cAAc,CAAC;AAIrE,YAAY,EAAE,MAAM,EAAE,CAAC;AAEvB,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,gCAAgC,EAAE,CAAC;IACrE,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC;AAgIF;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,CAiGjG"}
@@ -0,0 +1,435 @@
1
+ import { err, ok } from '@forgeax/engine-types';
2
+ import { WebSocketServer } from 'ws';
3
+
4
+ // src/server.ts
5
+
6
+ // src/error-messages.ts
7
+ var REMOTE_ERROR_MESSAGES = {
8
+ "script-syntax-error": "Script syntax error",
9
+ "script-runtime-error": "Script runtime error",
10
+ "server-startup-failed": "Server startup failed",
11
+ "server-not-running": "Server not reachable",
12
+ "eval-result-not-serializable": "Eval result not serializable"
13
+ };
14
+
15
+ // src/errors.ts
16
+ var RemoteError = class extends Error {
17
+ code;
18
+ expected;
19
+ hint;
20
+ detail;
21
+ constructor(args) {
22
+ super(`[RemoteError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);
23
+ this.name = "RemoteError";
24
+ this.code = args.code;
25
+ this.expected = args.expected;
26
+ this.hint = args.hint;
27
+ if (args.detail !== void 0) {
28
+ this.detail = args.detail;
29
+ }
30
+ }
31
+ toJSON() {
32
+ const json = {
33
+ code: this.code,
34
+ expected: this.expected,
35
+ hint: this.hint,
36
+ message: this.message
37
+ };
38
+ return this.detail === void 0 ? json : { ...json, detail: this.detail };
39
+ }
40
+ };
41
+ var REMOTE_ERROR_CODE_TO_JSONRPC = {
42
+ "script-syntax-error": -32001,
43
+ "script-runtime-error": -32002,
44
+ "server-startup-failed": -32003,
45
+ "server-not-running": -32004,
46
+ "eval-result-not-serializable": -32005
47
+ };
48
+
49
+ // src/execute.ts
50
+ var AsyncFunction = Object.getPrototypeOf(async () => {
51
+ }).constructor;
52
+ var _import = async (specifier) => import(specifier);
53
+ function compile(script) {
54
+ const params = [
55
+ "world",
56
+ "renderer",
57
+ "assets",
58
+ "rhiCapture",
59
+ "simulation",
60
+ "profiler",
61
+ "execution",
62
+ "_import"
63
+ ];
64
+ try {
65
+ const expr = script.replace(/[\s;]+$/, "");
66
+ return new AsyncFunction(...params, `return (${expr}
67
+ )`);
68
+ } catch (e) {
69
+ if (!(e instanceof SyntaxError)) throw e;
70
+ return new AsyncFunction(...params, script);
71
+ }
72
+ }
73
+ async function executeScript(script, ctx) {
74
+ try {
75
+ const fn = compile(script);
76
+ const value = await fn(
77
+ ctx.world,
78
+ ctx.renderer,
79
+ ctx.assets,
80
+ ctx.rhiCapture,
81
+ ctx.simulation,
82
+ ctx.profiler,
83
+ ctx.execution,
84
+ ctx.importModule ?? _import
85
+ );
86
+ return { ok: true, value };
87
+ } catch (e) {
88
+ if (e instanceof RemoteError) {
89
+ return { ok: false, error: e };
90
+ }
91
+ if (e instanceof SyntaxError) {
92
+ const msg = e.message;
93
+ return {
94
+ ok: false,
95
+ error: new RemoteError({
96
+ code: "script-syntax-error",
97
+ expected: "script body is valid JavaScript",
98
+ hint: `check syntax near: ${msg}; fix and resubmit`
99
+ })
100
+ };
101
+ }
102
+ const rawMessage = e instanceof Error ? e.message : String(e);
103
+ return {
104
+ ok: false,
105
+ error: new RemoteError({
106
+ code: "script-runtime-error",
107
+ expected: "script executes without throwing",
108
+ hint: `inspect error; verify symbol availability; eval has full access to world/renderer/assets (errMessage: ${rawMessage})`
109
+ })
110
+ };
111
+ }
112
+ }
113
+
114
+ // src/introspect.ts
115
+ function isProfilerRoot(value) {
116
+ if (value === null || typeof value !== "object") return false;
117
+ const root = value;
118
+ return typeof root.startCapture === "function" && typeof root.latestCapture === "function" && typeof root.activeSession === "function" && root.phaseCatalog !== void 0;
119
+ }
120
+ function isExecutionRoot(value) {
121
+ return value !== null && typeof value === "object" && typeof value.report === "function";
122
+ }
123
+ function profilerPhaseCatalog(value) {
124
+ if (value === null || typeof value !== "object") return void 0;
125
+ const catalog = value.phaseCatalog;
126
+ return catalog === void 0 ? void 0 : catalog;
127
+ }
128
+ function projectRoot(name, value) {
129
+ const descriptions = {
130
+ world: { type: "World", description: "The host World instance." },
131
+ renderer: { type: "Renderer", description: "The host Renderer instance." },
132
+ assets: { type: "AssetRegistry", description: "The host AssetRegistry instance." },
133
+ rhiCapture: {
134
+ type: "RhiCapture",
135
+ description: "The opt-in RHI frame capture capability."
136
+ },
137
+ profiler: {
138
+ type: "Profiler",
139
+ description: "The opt-in CPU profiler for bounded App and Render capture."
140
+ },
141
+ execution: {
142
+ type: "ExecutionReportProvider",
143
+ description: "The host execution report provider for tier, health, performance, and fault."
144
+ },
145
+ simulation: {
146
+ type: "SimulationInspection",
147
+ description: "A read-only World-owned simulation record, participant, trace, and report summary."
148
+ }
149
+ };
150
+ const descriptor = descriptions[name] ?? { type: "unknown", description: "A live eval root." };
151
+ return {
152
+ available: true,
153
+ ...descriptor,
154
+ ...name === "profiler" ? {
155
+ capability: "cpu-profile-v1",
156
+ operations: {
157
+ startCapture: "profiler.startCapture({ frameLimit, eventLimit })",
158
+ latestCapture: "profiler.latestCapture() after the host reaches the frame boundary"
159
+ },
160
+ ...profilerPhaseCatalog(value) === void 0 ? {} : { phaseCatalog: profilerPhaseCatalog(value) }
161
+ } : name === "execution" ? {
162
+ capability: "execution-report-v1"
163
+ } : name === "rhiCapture" ? {
164
+ capability: "rhi-capture-v1"
165
+ } : {}
166
+ };
167
+ }
168
+ function projectRoots(roots) {
169
+ const projected = {};
170
+ for (const [name, value] of Object.entries(roots)) {
171
+ if (value !== void 0 && (name !== "profiler" || isProfilerRoot(value)) && (name !== "execution" || isExecutionRoot(value))) {
172
+ projected[name] = projectRoot(name, value);
173
+ }
174
+ }
175
+ return projected;
176
+ }
177
+ function profilerCapability(roots) {
178
+ if (roots.profiler !== void 0) {
179
+ return {
180
+ enabled: true,
181
+ capability: "cpu-profile-v1",
182
+ limits: {
183
+ frameLimit: "positive-safe-integer",
184
+ eventLimit: "positive-safe-integer"
185
+ }
186
+ };
187
+ }
188
+ return {
189
+ enabled: false,
190
+ code: "profiler-not-enabled",
191
+ expected: "an explicitly opted-in profiler root",
192
+ hint: "Pass profiler: createProfiler() to createApp or startServer in development, then retry.",
193
+ detail: { enabled: false },
194
+ limits: {
195
+ frameLimit: "positive-safe-integer",
196
+ eventLimit: "positive-safe-integer"
197
+ }
198
+ };
199
+ }
200
+ function buildErrorProjection() {
201
+ const errors = {};
202
+ for (const [code, numericCode] of Object.entries(REMOTE_ERROR_CODE_TO_JSONRPC)) {
203
+ errors[code] = { code: numericCode, message: REMOTE_ERROR_MESSAGES[code] };
204
+ }
205
+ return errors;
206
+ }
207
+ function buildIntrospectDoc(host, port, roots) {
208
+ const projectedRoots = projectRoots(roots);
209
+ const schemas = {
210
+ World: { type: "object", description: "The host World instance." },
211
+ Renderer: { type: "object", description: "The host Renderer instance." },
212
+ Assets: { type: "object", description: "The host AssetRegistry instance." }
213
+ };
214
+ for (const [name, root] of Object.entries(projectedRoots)) {
215
+ schemas[root.type] = { type: "object", description: root.description };
216
+ if (name === "profiler") {
217
+ schemas.ProfilerCapture = {
218
+ type: "object",
219
+ description: "A bounded ProfileCapture v1 artifact returned through eval."
220
+ };
221
+ }
222
+ }
223
+ for (const descriptor of roots.introspection ?? []) {
224
+ schemas[descriptor.name] = descriptor;
225
+ }
226
+ return {
227
+ openrpc: "1.3.2",
228
+ info: {
229
+ title: "@forgeax/engine-remote remote eval",
230
+ version: "0.0.0",
231
+ description: "Remote eval server. Methods: eval / introspect. Errors map to JSON-RPC -32001..-32006."
232
+ },
233
+ servers: [{ name: "in-process", url: `ws://${host}:${port}/inspector` }],
234
+ methods: [
235
+ {
236
+ name: "eval",
237
+ summary: "Evaluate a JavaScript script against live eval roots.",
238
+ params: [
239
+ {
240
+ name: "script",
241
+ required: true,
242
+ schema: { type: "string" }
243
+ }
244
+ ],
245
+ result: { name: "value", schema: { type: "object" } }
246
+ },
247
+ {
248
+ name: "introspect",
249
+ summary: "Return this OpenRPC L2 subset document.",
250
+ params: [],
251
+ result: { name: "document", schema: { type: "object" } }
252
+ }
253
+ ],
254
+ roots: projectedRoots,
255
+ capabilities: { profiler: profilerCapability(projectedRoots) },
256
+ components: {
257
+ schemas,
258
+ errors: buildErrorProjection()
259
+ }
260
+ };
261
+ }
262
+
263
+ // src/server.ts
264
+ var REMOTE_TO_JSONRPC = REMOTE_ERROR_CODE_TO_JSONRPC;
265
+ function inspectorErrorToJsonRpc(e) {
266
+ const detail = e.detail;
267
+ const data = {
268
+ code: e.code,
269
+ expected: e.expected,
270
+ hint: e.hint,
271
+ message: e.message
272
+ };
273
+ if (detail !== void 0) data.detail = detail;
274
+ return {
275
+ code: REMOTE_TO_JSONRPC[e.code],
276
+ message: REMOTE_ERROR_MESSAGES[e.code],
277
+ data
278
+ };
279
+ }
280
+ function respondError(id, code, message, data) {
281
+ const error = { code, message };
282
+ return { jsonrpc: "2.0", id, error };
283
+ }
284
+ function respondOk(id, result) {
285
+ return { jsonrpc: "2.0", id, result };
286
+ }
287
+ function isValidId(v) {
288
+ return typeof v === "number" || typeof v === "string" || v === null;
289
+ }
290
+ async function handleEnvelope(raw, ctx) {
291
+ let parsed;
292
+ try {
293
+ parsed = JSON.parse(raw);
294
+ } catch {
295
+ return respondError(null, -32700, "Parse error");
296
+ }
297
+ if (typeof parsed.method !== "string") {
298
+ const id2 = isValidId(parsed.id) ? parsed.id : null;
299
+ return respondError(id2, -32600, "Invalid Request");
300
+ }
301
+ const isNotification = !("id" in parsed);
302
+ const id = isValidId(parsed.id) ? parsed.id : null;
303
+ let response;
304
+ if (parsed.method === "introspect") {
305
+ response = respondOk(
306
+ id,
307
+ buildIntrospectDoc(ctx.host, ctx.port, {
308
+ world: ctx.world,
309
+ renderer: ctx.renderer,
310
+ assets: ctx.assets,
311
+ ...ctx.rhiCapture !== void 0 ? { rhiCapture: ctx.rhiCapture } : {},
312
+ ...ctx.profiler !== void 0 ? { profiler: ctx.profiler } : {},
313
+ ...ctx.execution !== void 0 ? { execution: ctx.execution } : {},
314
+ ...ctx.simulation !== void 0 ? { simulation: ctx.simulation } : {},
315
+ ...ctx.introspection.length > 0 ? { introspection: ctx.introspection } : {}
316
+ })
317
+ );
318
+ } else if (parsed.method === "eval") {
319
+ const params = parsed.params;
320
+ const script = params?.script;
321
+ if (typeof script !== "string") {
322
+ response = respondError(id, -32602, "Invalid params: eval requires { script: string }");
323
+ } else {
324
+ const result = await executeScript(script, {
325
+ world: ctx.world,
326
+ renderer: ctx.renderer,
327
+ assets: ctx.assets,
328
+ rhiCapture: ctx.rhiCapture,
329
+ profiler: ctx.profiler,
330
+ execution: ctx.execution,
331
+ simulation: ctx.simulation
332
+ });
333
+ if (result.ok) {
334
+ response = respondOk(id, result.value);
335
+ } else {
336
+ response = { jsonrpc: "2.0", id, error: inspectorErrorToJsonRpc(result.error) };
337
+ }
338
+ }
339
+ } else {
340
+ response = respondError(id, -32601, `Method not found: ${parsed.method}`);
341
+ }
342
+ return isNotification ? null : response;
343
+ }
344
+ function startServer(opts) {
345
+ return new Promise((resolve) => {
346
+ const host = opts.host ?? "127.0.0.1";
347
+ if (host !== "127.0.0.1" && host !== "localhost") {
348
+ console.warn(
349
+ `[@forgeax/engine-remote] WARNING: binding on non-loopback host '${host}'. P0 trusts localhost only.`
350
+ );
351
+ }
352
+ const wss = new WebSocketServer({
353
+ host,
354
+ port: opts.port,
355
+ path: "/inspector",
356
+ maxPayload: 1 << 20,
357
+ perMessageDeflate: false
358
+ });
359
+ const world = opts.world;
360
+ const renderer = opts.renderer ?? {};
361
+ const assets = opts.assets ?? {};
362
+ const rhiCapture = opts.rhiCapture;
363
+ const introspection = opts.introspection ?? [];
364
+ const profiler = isProfilerRoot(opts.profiler) ? opts.profiler : void 0;
365
+ const execution = isExecutionRoot(opts.execution) ? opts.execution : void 0;
366
+ const simulation = opts.simulation;
367
+ let settled = false;
368
+ let boundPort = opts.port;
369
+ wss.on("error", (e) => {
370
+ if (settled) {
371
+ console.error("[@forgeax/engine-remote] post-startup error:", e);
372
+ return;
373
+ }
374
+ settled = true;
375
+ const errno = e.code ?? "unknown";
376
+ resolve(
377
+ err(
378
+ new RemoteError({
379
+ code: "server-startup-failed",
380
+ expected: "server starts successfully on requested port",
381
+ hint: errno === "EADDRINUSE" ? `port ${opts.port} is already in use; lsof -i :${opts.port} or pick a different port` : `listen failed with errno ${errno}; check host '${host}' on this machine; port=${opts.port}`
382
+ })
383
+ )
384
+ );
385
+ });
386
+ wss.on("connection", (ws) => {
387
+ ws.on("message", (raw) => {
388
+ const text = typeof raw === "string" ? raw : raw.toString();
389
+ handleEnvelope(text, {
390
+ world,
391
+ renderer,
392
+ assets,
393
+ rhiCapture,
394
+ introspection,
395
+ profiler,
396
+ execution,
397
+ simulation,
398
+ host,
399
+ port: boundPort
400
+ }).then((response) => {
401
+ if (response !== null && ws.readyState === ws.OPEN) {
402
+ ws.send(JSON.stringify(response));
403
+ }
404
+ }).catch((e) => {
405
+ if (ws.readyState === ws.OPEN) {
406
+ ws.send(JSON.stringify(respondError(null, -32603, `Internal error: ${String(e)}`)));
407
+ }
408
+ });
409
+ });
410
+ });
411
+ wss.on("listening", () => {
412
+ if (settled) {
413
+ return;
414
+ }
415
+ settled = true;
416
+ const address = wss.address();
417
+ const port = typeof address === "object" && address !== null ? address.port : opts.port;
418
+ boundPort = port;
419
+ const handle = {
420
+ port,
421
+ close: () => new Promise((closeResolve) => {
422
+ for (const client of wss.clients) {
423
+ client.terminate();
424
+ }
425
+ wss.close(() => closeResolve());
426
+ })
427
+ };
428
+ resolve(ok(handle));
429
+ });
430
+ });
431
+ }
432
+
433
+ export { startServer };
434
+ //# sourceMappingURL=server.mjs.map
435
+ //# sourceMappingURL=server.mjs.map