@jgengine/editor 0.11.0 → 0.13.0

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 (171) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +12 -0
  3. package/dist/AssetBrowser.d.ts +33 -1
  4. package/dist/AssetBrowser.js +64 -2
  5. package/dist/AssetDropZone.d.ts +9 -0
  6. package/dist/AssetDropZone.js +65 -0
  7. package/dist/CatalogsPanel.d.ts +4 -2
  8. package/dist/CatalogsPanel.js +162 -9
  9. package/dist/CityPreview.d.ts +11 -0
  10. package/dist/CityPreview.js +45 -0
  11. package/dist/CollectionsPanel.js +5 -5
  12. package/dist/DebugDraw.d.ts +3 -1
  13. package/dist/DebugDraw.js +37 -19
  14. package/dist/EditorApp.d.ts +15 -1
  15. package/dist/EditorApp.js +147 -35
  16. package/dist/EditorChrome.d.ts +27 -6
  17. package/dist/EditorChrome.js +314 -130
  18. package/dist/InspectorPanel.d.ts +17 -7
  19. package/dist/InspectorPanel.js +173 -27
  20. package/dist/LightingPanel.d.ts +11 -0
  21. package/dist/LightingPanel.js +99 -0
  22. package/dist/MaterialsWorkspacePanel.d.ts +12 -0
  23. package/dist/MaterialsWorkspacePanel.js +86 -0
  24. package/dist/ParentPickerMenu.d.ts +13 -0
  25. package/dist/ParentPickerMenu.js +33 -0
  26. package/dist/PerfProbe.js +33 -0
  27. package/dist/PrefabsPanel.js +5 -5
  28. package/dist/RuntimePlayBridge.d.ts +5 -3
  29. package/dist/RuntimePlayBridge.js +6 -13
  30. package/dist/ScatterPreview.d.ts +3 -1
  31. package/dist/ScatterPreview.js +10 -7
  32. package/dist/SchemaInspector.js +21 -8
  33. package/dist/ScriptingPanel.d.ts +15 -0
  34. package/dist/ScriptingPanel.js +46 -0
  35. package/dist/SelectionGizmo.js +114 -30
  36. package/dist/StandaloneEditor.d.ts +16 -9
  37. package/dist/StandaloneEditor.js +19 -20
  38. package/dist/TerrainPanel.d.ts +3 -2
  39. package/dist/TerrainPanel.js +16 -5
  40. package/dist/TerrainSculpt.js +98 -59
  41. package/dist/agent/AgentPanel.d.ts +5 -2
  42. package/dist/agent/AgentPanel.js +11 -6
  43. package/dist/agent/toolBridge.js +5 -0
  44. package/dist/assetImport.d.ts +36 -0
  45. package/dist/assetImport.js +66 -0
  46. package/dist/authoredComponentMeta.d.ts +25 -0
  47. package/dist/authoredComponentMeta.js +65 -0
  48. package/dist/gizmoPivot.d.ts +4 -0
  49. package/dist/gizmoPivot.js +34 -0
  50. package/dist/handlers/catalogs.d.ts +3 -0
  51. package/dist/handlers/catalogs.js +137 -0
  52. package/dist/handlers/collections.d.ts +3 -0
  53. package/dist/handlers/collections.js +57 -0
  54. package/dist/handlers/context.d.ts +36 -0
  55. package/dist/handlers/context.js +1 -0
  56. package/dist/handlers/document.d.ts +3 -0
  57. package/dist/handlers/document.js +350 -0
  58. package/dist/handlers/grids.d.ts +3 -0
  59. package/dist/handlers/grids.js +142 -0
  60. package/dist/handlers/hierarchy.d.ts +3 -0
  61. package/dist/handlers/hierarchy.js +55 -0
  62. package/dist/handlers/index.d.ts +8 -0
  63. package/dist/handlers/index.js +25 -0
  64. package/dist/handlers/minimap.d.ts +9 -0
  65. package/dist/handlers/minimap.js +32 -0
  66. package/dist/handlers/runtime.d.ts +3 -0
  67. package/dist/handlers/runtime.js +191 -0
  68. package/dist/handlers/streets.d.ts +3 -0
  69. package/dist/handlers/streets.js +159 -0
  70. package/dist/handlers/terrain.d.ts +3 -0
  71. package/dist/handlers/terrain.js +204 -0
  72. package/dist/index.d.ts +3 -2
  73. package/dist/index.js +3 -2
  74. package/dist/listDocumentTriggers.d.ts +42 -0
  75. package/dist/listDocumentTriggers.js +49 -0
  76. package/dist/materialAssignments.d.ts +63 -0
  77. package/dist/materialAssignments.js +84 -0
  78. package/dist/mcp/fieldSpec.d.ts +32 -0
  79. package/dist/mcp/fieldSpec.js +174 -0
  80. package/dist/mcp/rpcRequest.d.ts +8 -5
  81. package/dist/mcp/rpcRequest.js +72 -93
  82. package/dist/mcp/tools.js +105 -912
  83. package/dist/networkSnapshot.d.ts +82 -0
  84. package/dist/networkSnapshot.js +73 -0
  85. package/dist/parentCandidates.d.ts +12 -0
  86. package/dist/parentCandidates.js +25 -0
  87. package/dist/session.d.ts +91 -4
  88. package/dist/session.js +36 -1017
  89. package/dist/shell/AnimationPanel.d.ts +17 -0
  90. package/dist/shell/AnimationPanel.js +134 -0
  91. package/dist/shell/AssetThumbnail.d.ts +12 -0
  92. package/dist/shell/AssetThumbnail.js +23 -0
  93. package/dist/shell/BottomDock.d.ts +25 -0
  94. package/dist/shell/BottomDock.js +40 -0
  95. package/dist/shell/CommandPalette.d.ts +12 -0
  96. package/dist/shell/CommandPalette.js +56 -0
  97. package/dist/shell/ConsolePanel.d.ts +8 -0
  98. package/dist/shell/ConsolePanel.js +45 -0
  99. package/dist/shell/ContentBrowser.d.ts +21 -0
  100. package/dist/shell/ContentBrowser.js +130 -0
  101. package/dist/shell/HierarchyPanel.d.ts +26 -0
  102. package/dist/shell/HierarchyPanel.js +387 -0
  103. package/dist/shell/NetworkWorkspacePanel.d.ts +11 -0
  104. package/dist/shell/NetworkWorkspacePanel.js +49 -0
  105. package/dist/shell/PlayModeBar.d.ts +12 -0
  106. package/dist/shell/PlayModeBar.js +27 -0
  107. package/dist/shell/ProfilerPanel.d.ts +10 -0
  108. package/dist/shell/ProfilerPanel.js +41 -0
  109. package/dist/shell/SceneToolbar.d.ts +43 -0
  110. package/dist/shell/SceneToolbar.js +122 -0
  111. package/dist/shell/StatusBar.d.ts +18 -0
  112. package/dist/shell/StatusBar.js +41 -0
  113. package/dist/shell/TopAppBar.d.ts +35 -0
  114. package/dist/shell/TopAppBar.js +64 -0
  115. package/dist/shell/ViewportOverlays.d.ts +23 -0
  116. package/dist/shell/ViewportOverlays.js +136 -0
  117. package/dist/shell/WorkspaceRail.d.ts +20 -0
  118. package/dist/shell/WorkspaceRail.js +31 -0
  119. package/dist/shell/cameraTelemetry.d.ts +27 -0
  120. package/dist/shell/cameraTelemetry.js +24 -0
  121. package/dist/shell/commandRegistry.d.ts +58 -0
  122. package/dist/shell/commandRegistry.js +133 -0
  123. package/dist/shell/consoleSink.d.ts +11 -0
  124. package/dist/shell/consoleSink.js +17 -0
  125. package/dist/shell/consoleStore.d.ts +27 -0
  126. package/dist/shell/consoleStore.js +43 -0
  127. package/dist/shell/fields.d.ts +39 -0
  128. package/dist/shell/fields.js +104 -0
  129. package/dist/shell/formatSavedRelative.d.ts +2 -0
  130. package/dist/shell/formatSavedRelative.js +14 -0
  131. package/dist/shell/glbThumbnailCache.d.ts +45 -0
  132. package/dist/shell/glbThumbnailCache.js +241 -0
  133. package/dist/shell/hierarchyA11y.d.ts +21 -0
  134. package/dist/shell/hierarchyA11y.js +35 -0
  135. package/dist/shell/icons.d.ts +13 -0
  136. package/dist/shell/icons.js +85 -0
  137. package/dist/shell/layoutStore.d.ts +68 -0
  138. package/dist/shell/layoutStore.js +156 -0
  139. package/dist/shell/pathFlythrough.d.ts +37 -0
  140. package/dist/shell/pathFlythrough.js +54 -0
  141. package/dist/shell/perfHistory.d.ts +70 -0
  142. package/dist/shell/perfHistory.js +130 -0
  143. package/dist/shell/renameObject.d.ts +3 -0
  144. package/dist/shell/renameObject.js +25 -0
  145. package/dist/shell/theme.d.ts +36 -0
  146. package/dist/shell/theme.js +37 -0
  147. package/dist/shell/ui.d.ts +84 -0
  148. package/dist/shell/ui.js +90 -0
  149. package/dist/shell/useGlbThumbnail.d.ts +8 -0
  150. package/dist/shell/useGlbThumbnail.js +19 -0
  151. package/dist/uiStore.d.ts +27 -0
  152. package/dist/uiStore.js +10 -0
  153. package/dist/viewportContextMenu.d.ts +4 -2
  154. package/dist/viewportContextMenu.js +5 -1
  155. package/package.json +4 -4
  156. package/skills/jgengine/SKILL.md +8 -4
  157. package/skills/jgengine/api.md +182 -81
  158. package/skills/jgengine/capabilities.md +104 -2
  159. package/skills/jgengine/recipes/minimal-game.md +88 -0
  160. package/skills/jgengine-editor/SKILL.md +18 -3
  161. package/skills/jgengine-editor/api.md +403 -20
  162. package/skills/jgengine-editor/capabilities.md +46 -0
  163. package/skills/jgengine-editor/reference.md +87 -0
  164. package/skills/level-design/SKILL.md +54 -0
  165. package/skills/level-design/agents/openai.yaml +4 -0
  166. package/skills/level-design/references/application-playbook.md +192 -0
  167. package/skills/level-design/references/field-guide.md +337 -0
  168. package/skills/level-design/references/live-editor-loop.md +179 -0
  169. package/dist/OutlinerPanel.d.ts +0 -14
  170. package/dist/OutlinerPanel.js +0 -84
  171. package/skills/jgengine/reference-cartridge.md +0 -72
@@ -0,0 +1,174 @@
1
+ // Terse builders so the per-method table below reads as a schema, not a wall of object literals.
2
+ const s = (name, oneOf) => (oneOf === undefined ? { name, kind: "string" } : { name, kind: "string", oneOf });
3
+ const sn = (name) => ({ name, kind: "string", nullable: true });
4
+ const n = (name) => ({ name, kind: "number" });
5
+ const b = (name) => ({ name, kind: "boolean" });
6
+ const sa = (name) => ({ name, kind: "string[]" });
7
+ const o = (name) => ({ name, kind: "object" });
8
+ const oa = (name, of) => (of === undefined ? { name, kind: "object[]" } : { name, kind: "object[]", of });
9
+ const v3 = (name) => ({ name, kind: "vec3" });
10
+ const val = (name) => ({ name, kind: "value" });
11
+ /** Marks a field mandatory for MCP schema generation; presence itself is still checked by `handle`. */
12
+ const req = (spec) => ({ ...spec, required: true });
13
+ const SHAPE = ["circle", "square"];
14
+ const AXES = ["xz", "xy"];
15
+ /**
16
+ * Per-method field type table — the single source of truth for the RPC boundary. A field listed here
17
+ * is type-checked when present (see `decodeEditorBridgeRequest`); existence is left to
18
+ * `EditorHostApi.handle` (whose per-method guards return honest `ok:false` diagnostics for
19
+ * missing/unknown targets). Type-checking presented fields is what keeps a fuzzed value — a string
20
+ * where a number belongs, a scalar where an object belongs — from reaching a live session uncoerced.
21
+ * Unknown extra fields are ignored so a superset payload stays forward-compatible.
22
+ *
23
+ * The same table generates the MCP tool `inputSchema`s (see `mcp/tools.ts`), so `required`/`oneOf`/
24
+ * `of` here also shape what agents are told a tool accepts. The `Record<EditorBridgeRequest["method"],
25
+ * …>` type is the lockstep guarantee: adding a method to the union in `session.ts` fails to compile
26
+ * here until it gets a schema, and a stale entry is a compile error too.
27
+ */
28
+ export const RPC_FIELD_SCHEMAS = {
29
+ editor_status: [],
30
+ set_mode: [req(s("mode", ["edit", "walk", "play", "hud"]))],
31
+ perf_report: [],
32
+ list_layers: [],
33
+ list_catalogs: [],
34
+ get_catalog_entry: [req(s("catalogId")), req(s("entryId"))],
35
+ set_catalog_entry: [req(s("catalogId")), req(s("entryId")), req(o("patch")), s("label")],
36
+ add_catalog_entry: [req(s("catalogId")), req(s("entryId")), o("meta"), s("label")],
37
+ remove_catalog_entry: [req(s("catalogId")), req(s("entryId"))],
38
+ add_catalog: [req(s("catalogId")), s("label"), o("schema")],
39
+ remove_catalog: [req(s("catalogId"))],
40
+ set_catalog_schema: [req(s("catalogId")), req(o("schema")), s("label")],
41
+ list_selection: [],
42
+ get_marker: [req(s("id"))],
43
+ get_volume: [req(s("id"))],
44
+ set_transform: [req(s("id")), n("x"), n("y"), n("z"), n("rotationY")],
45
+ set_volume: [req(s("id")), n("radius"), n("height"), n("x"), n("y"), n("z")],
46
+ set_path: [req(s("id")), s("kind"), n("width"), s("color"), s("label"), o("meta")],
47
+ set_marker: [req(s("id")), s("kind"), s("color"), s("label"), n("rotationY"), o("meta")],
48
+ set_note: [req(s("id")), s("text"), o("meta")],
49
+ set_meta: [req(s("id")), req(o("patch"))],
50
+ apply_preset: [req(s("id")), req(s("preset"))],
51
+ select: [req(sa("ids"))],
52
+ clear_selection: [],
53
+ camera_goto: [s("id"), n("x"), n("y"), n("z")],
54
+ camera_frame: [],
55
+ scene_summary: [],
56
+ export_document: [],
57
+ import_document: [req(s("json"))],
58
+ dispatch: [req(o("command"))],
59
+ undo: [],
60
+ redo: [],
61
+ list_assets: [],
62
+ place_asset: [req(s("id")), s("kind"), n("x"), n("y"), n("z")],
63
+ create_terrain: [n("width"), n("depth"), n("cellSize"), n("centerX"), n("centerZ")],
64
+ sculpt_terrain: [req(s("mode", ["raise", "lower", "smooth", "flatten", "noise", "ramp"])), req(n("x")), req(n("z")), n("radius"), n("strength"), n("target"), n("toX"), n("toZ"), n("seed"), s("shape", SHAPE)],
65
+ terrain_summary: [],
66
+ paint_terrain: [req(s("surface")), req(n("x")), req(n("z")), n("radius"), s("shape", SHAPE)],
67
+ fill_terrain: [req(sn("surface"))],
68
+ auto_paint: [req(s("surface")), n("minSlope"), n("maxSlope"), n("minHeight"), n("maxHeight")],
69
+ terrain_materials: [],
70
+ terrain_layers: [],
71
+ set_terrain_layers: [req(oa("layers", [req(s("id")), req(s("surface")), n("roughness"), n("tiling"), b("triplanar"), s("tint"), n("opacity")]))],
72
+ blend_terrain: [req(s("surface")), req(n("x")), req(n("z")), n("radius"), n("strength"), s("shape", SHAPE)],
73
+ convert_scatter: [req(s("pathId"))],
74
+ generate_streets: [s("volumeId"), v3("center"), n("halfX"), n("halfZ"), s("seed"), s("mode", ["net", "circuit"]), s("kind"), o("params")],
75
+ bake_minimap: [n("padding"), n("resolution"), n("waterLevel")],
76
+ add_foliage: [req(oa("points", [req(n("x")), req(n("z"))])), n("density"), s("item"), s("seed"), n("minSpacing")],
77
+ scatter_summary: [],
78
+ set_parent: [req(sa("ids")), req(sn("parentId"))],
79
+ hierarchy: [],
80
+ list_prefabs: [],
81
+ create_prefab: [req(s("id")), req(s("name")), req(sa("ids"))],
82
+ insert_prefab: [req(s("prefabId")), n("x"), n("y"), n("z")],
83
+ detach_prefab_instance: [req(s("instanceId"))],
84
+ delete_prefab: [req(s("prefabId"))],
85
+ list_collections: [],
86
+ create_collection: [req(s("id")), req(s("name")), sa("memberIds")],
87
+ rename_collection: [req(s("id")), req(s("name"))],
88
+ delete_collection: [req(s("id"))],
89
+ set_collection_members: [req(s("id")), req(sa("memberIds"))],
90
+ add_to_collection: [req(s("id")), req(sa("ids"))],
91
+ remove_from_collection: [req(s("id")), req(sa("ids"))],
92
+ set_collection_flags: [req(s("id")), s("color"), b("locked"), b("visible")],
93
+ select_collection: [req(s("id"))],
94
+ set_object_flags: [req(sa("ids")), b("locked"), b("hidden")],
95
+ batch_set_properties: [req(sa("ids")), s("color"), s("label"), o("meta")],
96
+ assign_material: [req(sa("ids")), req(s("materialId"))],
97
+ list_grids: [],
98
+ get_grid_cell: [req(s("id")), req(n("col")), req(n("row"))],
99
+ add_grid_layer: [req(s("id")), req(s("kind")), req(n("cols")), req(n("rows")), s("label"), n("cellSize"), v3("origin"), s("axes", AXES), s("empty"), oa("palette")],
100
+ remove_grid_layer: [req(s("id"))],
101
+ set_grid_layer: [req(s("id")), s("label"), s("kind"), b("visible"), s("empty"), n("cellSize"), v3("origin"), s("axes", AXES), oa("palette")],
102
+ paint_grid_cells: [req(s("id")), req(oa("cells", [req(n("col")), req(n("row")), req(s("value"))]))],
103
+ fill_grid_rect: [req(s("id")), req(n("col0")), req(n("row0")), req(n("col1")), req(n("row1")), req(s("value"))],
104
+ flood_fill_grid: [req(s("id")), req(n("col")), req(n("row")), req(s("value"))],
105
+ resize_grid_layer: [req(s("id")), req(n("cols")), req(n("rows"))],
106
+ import_grid: [req(s("id")), req(s("kind")), req(s("format", ["ascii", "csv"])), req(s("text")), s("empty"), n("cellSize"), v3("origin"), o("glyphMap"), oa("palette")],
107
+ push_document_patch: [req(o("patch")), b("force")],
108
+ pull_document_patches: [n("sinceRevision")],
109
+ document_revision: [b("includeDocument")],
110
+ push_runtime_delta: [n("at"), oa("entities"), sa("removeIds"), o("tunables")],
111
+ pull_runtime_deltas: [n("sinceSeq"), b("includeSnapshot")],
112
+ runtime_snapshot: [],
113
+ runtime_summary: [],
114
+ runtime_get: [req(s("id")), s("path")],
115
+ runtime_set: [req(s("id")), s("path"), val("value"), o("position"), n("rotationY"), o("values"), b("writeBack")],
116
+ runtime_pause: [],
117
+ runtime_resume: [],
118
+ runtime_step: [n("frames")],
119
+ set_runtime_override: [req(o("entity"))],
120
+ clear_runtime_override: [req(s("id"))],
121
+ write_back_override: [req(s("id"))],
122
+ };
123
+ /** The set of known RPC method names — exported for meta/manifest tooling and tests. @internal */
124
+ export const EDITOR_BRIDGE_METHOD_NAMES = Object.keys(RPC_FIELD_SCHEMAS).sort();
125
+ /** JSON-Schema fragment for one field spec (no `required` — that is aggregated by the object builder). */
126
+ function fieldToJsonSchema(spec) {
127
+ switch (spec.kind) {
128
+ case "string":
129
+ return spec.oneOf !== undefined
130
+ ? { type: "string", enum: [...spec.oneOf] }
131
+ : spec.nullable === true
132
+ ? { type: ["string", "null"] }
133
+ : { type: "string" };
134
+ case "number":
135
+ return { type: "number" };
136
+ case "boolean":
137
+ return { type: "boolean" };
138
+ case "string[]":
139
+ return { type: "array", items: { type: "string" } };
140
+ case "object":
141
+ return spec.of !== undefined ? objectSchema(spec.of) : { type: "object" };
142
+ case "object[]":
143
+ return { type: "array", items: spec.of !== undefined ? objectSchema(spec.of) : { type: "object" } };
144
+ case "vec3":
145
+ return {
146
+ type: "object",
147
+ properties: { x: { type: "number" }, y: { type: "number" }, z: { type: "number" } },
148
+ required: ["x", "y", "z"],
149
+ additionalProperties: false,
150
+ };
151
+ case "value":
152
+ return {};
153
+ }
154
+ }
155
+ /** Assembles a closed object schema (properties + required + additionalProperties:false) from field specs. */
156
+ function objectSchema(specs) {
157
+ const properties = {};
158
+ const required = [];
159
+ for (const spec of specs) {
160
+ properties[spec.name] = fieldToJsonSchema(spec);
161
+ if (spec.required === true)
162
+ required.push(spec.name);
163
+ }
164
+ return {
165
+ type: "object",
166
+ properties,
167
+ ...(required.length > 0 ? { required } : {}),
168
+ additionalProperties: false,
169
+ };
170
+ }
171
+ /** Generates the MCP tool `inputSchema` for a method from its {@link RPC_FIELD_SCHEMAS} entry. */
172
+ export function buildInputSchema(method) {
173
+ return objectSchema(RPC_FIELD_SCHEMAS[method]);
174
+ }
@@ -1,4 +1,5 @@
1
1
  import type { EditorBridgeRequest } from "../session.js";
2
+ export { EDITOR_BRIDGE_METHOD_NAMES } from "./fieldSpec.js";
2
3
  /** One field-level failure surfaced while decoding an untrusted RPC request. */
3
4
  export interface RpcRequestDiagnostic {
4
5
  path: string;
@@ -13,10 +14,12 @@ export type DecodeRpcRequestResult = {
13
14
  errors: RpcRequestDiagnostic[];
14
15
  };
15
16
  /**
16
- * Validates an untrusted JSON-decoded RPC payload (from `--rpc` or the HTTP bridge) before it
17
- * reaches `EditorHostApi.handle`: confirms it is a plain object carrying a known `method` name.
18
- * Per-method field shape is still enforced by `handle`'s own dispatch, but a garbled or
19
- * unknown-method payload is rejected here with a path-specific diagnostic instead of flowing
20
- * through on a blind cast.
17
+ * Validates an untrusted JSON-decoded RPC payload (from `--rpc`, the HTTP bridge, or an agent tool
18
+ * call) before it reaches `EditorHostApi.handle`: confirms it is a plain object carrying a known
19
+ * `method`, then type-checks every field the method understands against {@link RPC_FIELD_SCHEMAS}.
20
+ * A garbled method, or a field whose value is the wrong type (a string where a number belongs, a
21
+ * scalar where an object belongs), is rejected here with a path-specific diagnostic instead of
22
+ * flowing into a live session on a blind cast. Missing fields and unknown extra fields are left for
23
+ * `handle` to interpret so the boundary stays forward-compatible.
21
24
  */
22
25
  export declare function decodeEditorBridgeRequest(raw: unknown): DecodeRpcRequestResult;
@@ -1,107 +1,86 @@
1
- /** Every `method` an {@link EditorBridgeRequest} may carry — kept in lockstep with the union in `session.ts`. */
2
- const EDITOR_BRIDGE_METHODS = new Set([
3
- "add_foliage",
4
- "add_grid_layer",
5
- "add_to_collection",
6
- "assign_material",
7
- "fill_grid_rect",
8
- "flood_fill_grid",
9
- "get_grid_cell",
10
- "import_grid",
11
- "list_grids",
12
- "paint_grid_cells",
13
- "remove_grid_layer",
14
- "resize_grid_layer",
15
- "set_grid_layer",
16
- "auto_paint",
17
- "batch_set_properties",
18
- "blend_terrain",
19
- "camera_frame",
20
- "camera_goto",
21
- "clear_runtime_override",
22
- "clear_selection",
23
- "convert_scatter",
24
- "create_collection",
25
- "create_prefab",
26
- "create_terrain",
27
- "delete_collection",
28
- "delete_prefab",
29
- "detach_prefab_instance",
30
- "dispatch",
31
- "document_revision",
32
- "editor_status",
33
- "export_document",
34
- "fill_terrain",
35
- "get_marker",
36
- "get_volume",
37
- "hierarchy",
38
- "import_document",
39
- "insert_prefab",
40
- "get_catalog_entry",
41
- "list_assets",
42
- "list_catalogs",
43
- "list_collections",
44
- "list_layers",
45
- "list_prefabs",
46
- "list_selection",
47
- "paint_terrain",
48
- "perf_report",
49
- "place_asset",
50
- "pull_document_patches",
51
- "pull_runtime_deltas",
52
- "push_document_patch",
53
- "push_runtime_delta",
54
- "redo",
55
- "remove_from_collection",
56
- "rename_collection",
57
- "runtime_get",
58
- "runtime_pause",
59
- "runtime_resume",
60
- "runtime_set",
61
- "runtime_snapshot",
62
- "runtime_step",
63
- "runtime_summary",
64
- "scatter_summary",
65
- "scene_summary",
66
- "sculpt_terrain",
67
- "select",
68
- "select_collection",
69
- "set_catalog_entry",
70
- "set_collection_flags",
71
- "set_collection_members",
72
- "set_marker",
73
- "set_meta",
74
- "set_mode",
75
- "set_note",
76
- "set_parent",
77
- "set_path",
78
- "set_runtime_override",
79
- "set_terrain_layers",
80
- "set_transform",
81
- "set_volume",
82
- "terrain_layers",
83
- "terrain_materials",
84
- "terrain_summary",
85
- "undo",
86
- "write_back_override",
87
- ]);
1
+ import { RPC_FIELD_SCHEMAS } from "./fieldSpec.js";
2
+ export { EDITOR_BRIDGE_METHOD_NAMES } from "./fieldSpec.js";
3
+ /** Every `method` an {@link EditorBridgeRequest} may carry, derived from the schema table's keys. */
4
+ const EDITOR_BRIDGE_METHODS = new Set(Object.keys(RPC_FIELD_SCHEMAS));
5
+ function isPlainObject(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function isVec3(value) {
9
+ return isPlainObject(value) && typeof value.x === "number" && typeof value.y === "number" && typeof value.z === "number";
10
+ }
11
+ function validateField(value, spec, errors) {
12
+ if (value === undefined)
13
+ return; // presence is enforced by handle's per-method guards, not here
14
+ const path = `$.${spec.name}`;
15
+ if (value === null) {
16
+ if (spec.nullable !== true)
17
+ errors.push({ path, message: `expected a ${spec.kind}` });
18
+ return;
19
+ }
20
+ switch (spec.kind) {
21
+ case "string":
22
+ if (typeof value !== "string")
23
+ errors.push({ path, message: "expected a string" });
24
+ else if (spec.oneOf !== undefined && !spec.oneOf.includes(value)) {
25
+ errors.push({ path, message: `expected one of ${spec.oneOf.join(" | ")}` });
26
+ }
27
+ return;
28
+ case "number":
29
+ if (typeof value !== "number" || Number.isNaN(value))
30
+ errors.push({ path, message: "expected a number" });
31
+ return;
32
+ case "boolean":
33
+ if (typeof value !== "boolean")
34
+ errors.push({ path, message: "expected a boolean" });
35
+ return;
36
+ case "string[]":
37
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
38
+ errors.push({ path, message: "expected an array of strings" });
39
+ }
40
+ return;
41
+ case "object":
42
+ if (!isPlainObject(value))
43
+ errors.push({ path, message: "expected an object" });
44
+ return;
45
+ case "object[]":
46
+ if (!Array.isArray(value) || value.some((entry) => !isPlainObject(entry))) {
47
+ errors.push({ path, message: "expected an array of objects" });
48
+ }
49
+ return;
50
+ case "vec3":
51
+ if (!isVec3(value))
52
+ errors.push({ path, message: "expected {x,y,z} numbers" });
53
+ return;
54
+ case "value":
55
+ return;
56
+ }
57
+ }
88
58
  /**
89
- * Validates an untrusted JSON-decoded RPC payload (from `--rpc` or the HTTP bridge) before it
90
- * reaches `EditorHostApi.handle`: confirms it is a plain object carrying a known `method` name.
91
- * Per-method field shape is still enforced by `handle`'s own dispatch, but a garbled or
92
- * unknown-method payload is rejected here with a path-specific diagnostic instead of flowing
93
- * through on a blind cast.
59
+ * Validates an untrusted JSON-decoded RPC payload (from `--rpc`, the HTTP bridge, or an agent tool
60
+ * call) before it reaches `EditorHostApi.handle`: confirms it is a plain object carrying a known
61
+ * `method`, then type-checks every field the method understands against {@link RPC_FIELD_SCHEMAS}.
62
+ * A garbled method, or a field whose value is the wrong type (a string where a number belongs, a
63
+ * scalar where an object belongs), is rejected here with a path-specific diagnostic instead of
64
+ * flowing into a live session on a blind cast. Missing fields and unknown extra fields are left for
65
+ * `handle` to interpret so the boundary stays forward-compatible.
94
66
  */
95
67
  export function decodeEditorBridgeRequest(raw) {
96
68
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
97
69
  return { ok: false, errors: [{ path: "$", message: "expected an RPC request object" }] };
98
70
  }
99
- const method = raw.method;
71
+ const record = raw;
72
+ const method = record.method;
100
73
  if (typeof method !== "string") {
101
74
  return { ok: false, errors: [{ path: "$.method", message: "expected a string" }] };
102
75
  }
103
76
  if (!EDITOR_BRIDGE_METHODS.has(method)) {
104
77
  return { ok: false, errors: [{ path: "$.method", message: `unknown method "${method}"` }] };
105
78
  }
79
+ const errors = [];
80
+ for (const spec of RPC_FIELD_SCHEMAS[method]) {
81
+ validateField(record[spec.name], spec, errors);
82
+ }
83
+ if (errors.length > 0)
84
+ return { ok: false, errors };
106
85
  return { ok: true, request: raw };
107
86
  }