@jgengine/editor 0.10.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.
- package/CHANGELOG.md +325 -0
- package/dist/AssetBrowser.d.ts +20 -0
- package/dist/AssetBrowser.js +49 -0
- package/dist/CatalogsPanel.d.ts +10 -0
- package/dist/CatalogsPanel.js +41 -0
- package/dist/CollectionsPanel.d.ts +10 -0
- package/dist/CollectionsPanel.js +35 -0
- package/dist/DebugDraw.d.ts +17 -0
- package/dist/DebugDraw.js +233 -0
- package/dist/EditorApp.d.ts +26 -0
- package/dist/EditorApp.js +333 -0
- package/dist/EditorCameraDriver.d.ts +5 -0
- package/dist/EditorCameraDriver.js +18 -0
- package/dist/EditorChrome.d.ts +22 -0
- package/dist/EditorChrome.js +559 -0
- package/dist/EditorContextMenu.d.ts +11 -0
- package/dist/EditorContextMenu.js +12 -0
- package/dist/InspectorPanel.d.ts +14 -0
- package/dist/InspectorPanel.js +110 -0
- package/dist/MaterialDropZone.d.ts +10 -0
- package/dist/MaterialDropZone.js +77 -0
- package/dist/OutlinerPanel.d.ts +14 -0
- package/dist/OutlinerPanel.js +84 -0
- package/dist/PerfProbe.d.ts +5 -0
- package/dist/PerfProbe.js +34 -0
- package/dist/PrefabsPanel.d.ts +13 -0
- package/dist/PrefabsPanel.js +42 -0
- package/dist/RuntimePlayBridge.d.ts +18 -0
- package/dist/RuntimePlayBridge.js +140 -0
- package/dist/ScatterPreview.d.ts +11 -0
- package/dist/ScatterPreview.js +30 -0
- package/dist/SchemaInspector.d.ts +19 -0
- package/dist/SchemaInspector.js +78 -0
- package/dist/SelectionGizmo.d.ts +25 -0
- package/dist/SelectionGizmo.js +455 -0
- package/dist/StandaloneEditor.d.ts +44 -0
- package/dist/StandaloneEditor.js +92 -0
- package/dist/TerrainPanel.d.ts +7 -0
- package/dist/TerrainPanel.js +59 -0
- package/dist/TerrainSculpt.d.ts +14 -0
- package/dist/TerrainSculpt.js +415 -0
- package/dist/TriggerInspector.d.ts +13 -0
- package/dist/TriggerInspector.js +60 -0
- package/dist/agent/AgentPanel.d.ts +13 -0
- package/dist/agent/AgentPanel.js +156 -0
- package/dist/agent/context.d.ts +27 -0
- package/dist/agent/context.js +39 -0
- package/dist/agent/endpoint.d.ts +68 -0
- package/dist/agent/endpoint.js +186 -0
- package/dist/agent/toolBridge.d.ts +35 -0
- package/dist/agent/toolBridge.js +93 -0
- package/dist/agent/turn.d.ts +71 -0
- package/dist/agent/turn.js +113 -0
- package/dist/chromeFields.d.ts +15 -0
- package/dist/chromeFields.js +13 -0
- package/dist/chromeStyles.d.ts +3 -0
- package/dist/chromeStyles.js +3 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +19 -0
- package/dist/mcp/bridgeServer.d.ts +13 -0
- package/dist/mcp/bridgeServer.js +1 -0
- package/dist/mcp/loadGameCatalogs.d.ts +24 -0
- package/dist/mcp/loadGameCatalogs.js +87 -0
- package/dist/mcp/rpcRequest.d.ts +22 -0
- package/dist/mcp/rpcRequest.js +97 -0
- package/dist/mcp/tools.d.ts +8 -0
- package/dist/mcp/tools.js +764 -0
- package/dist/outlinerModel.d.ts +76 -0
- package/dist/outlinerModel.js +95 -0
- package/dist/perfMarks.d.ts +25 -0
- package/dist/perfMarks.js +36 -0
- package/dist/session.d.ts +400 -0
- package/dist/session.js +1032 -0
- package/dist/uiStore.d.ts +108 -0
- package/dist/uiStore.js +128 -0
- package/dist/useF2Chord.d.ts +2 -0
- package/dist/useF2Chord.js +32 -0
- package/dist/useStoreSelector.d.ts +31 -0
- package/dist/useStoreSelector.js +51 -0
- package/dist/viewportContextMenu.d.ts +24 -0
- package/dist/viewportContextMenu.js +32 -0
- package/package.json +58 -0
- package/skills/jgengine/SKILL.md +823 -0
- package/skills/jgengine/api.md +416 -0
- package/skills/jgengine/capabilities.md +21 -0
- package/skills/jgengine/reference-cartridge.md +72 -0
- package/skills/jgengine/reference-orphans.md +9 -0
- package/skills/jgengine/reference-systems.md +60 -0
- package/skills/jgengine-editor/SKILL.md +382 -0
- package/skills/jgengine-editor/api.md +416 -0
- package/skills/jgengine-editor/capabilities.md +29 -0
- package/skills/jgengine-editor/reference.md +64 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { EditorHostApi } from "../session.js";
|
|
2
|
+
import { type AgentEditorContext } from "./context.js";
|
|
3
|
+
import type { AgentChatMessage, AgentEndpoint } from "./endpoint.js";
|
|
4
|
+
import { type AgentToolResult } from "./toolBridge.js";
|
|
5
|
+
/** One agent edit shown in the panel transcript — human can undo when it is the top of the stack. */
|
|
6
|
+
export interface AgentPatchEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
toolCallId: string;
|
|
9
|
+
method: string;
|
|
10
|
+
summary: string;
|
|
11
|
+
at: number;
|
|
12
|
+
undone: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** One rendered line in the agent panel transcript (user, assistant, tool, error, or patch). */
|
|
15
|
+
export type AgentTranscriptEntry = {
|
|
16
|
+
kind: "user";
|
|
17
|
+
id: string;
|
|
18
|
+
content: string;
|
|
19
|
+
at: number;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "assistant";
|
|
22
|
+
id: string;
|
|
23
|
+
content: string;
|
|
24
|
+
at: number;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "tool";
|
|
27
|
+
id: string;
|
|
28
|
+
result: AgentToolResult;
|
|
29
|
+
at: number;
|
|
30
|
+
} | {
|
|
31
|
+
kind: "error";
|
|
32
|
+
id: string;
|
|
33
|
+
content: string;
|
|
34
|
+
at: number;
|
|
35
|
+
} | {
|
|
36
|
+
kind: "patch";
|
|
37
|
+
id: string;
|
|
38
|
+
patch: AgentPatchEntry;
|
|
39
|
+
at: number;
|
|
40
|
+
};
|
|
41
|
+
/** Result of {@link runAgentTurn}: chat history, UI transcript, undoable patches, and packed context. */
|
|
42
|
+
export interface AgentTurnResult {
|
|
43
|
+
messages: AgentChatMessage[];
|
|
44
|
+
transcript: AgentTranscriptEntry[];
|
|
45
|
+
patches: AgentPatchEntry[];
|
|
46
|
+
context: AgentEditorContext;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Runs one user message against an agent endpoint: injects live editor context via
|
|
50
|
+
* `packAgentContext`, executes tool calls through `routeToolCall` (shared undo), and returns
|
|
51
|
+
* transcript + patch entries the human can reverse with `undoAgentPatch`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function runAgentTurn(options: {
|
|
54
|
+
api: EditorHostApi;
|
|
55
|
+
endpoint: AgentEndpoint;
|
|
56
|
+
history: readonly AgentChatMessage[];
|
|
57
|
+
userMessage: string;
|
|
58
|
+
maxRounds?: number;
|
|
59
|
+
now?: () => number;
|
|
60
|
+
}): Promise<AgentTurnResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Undoes one agent patch when it is still the top of the session undo stack.
|
|
63
|
+
* Newer live agent patches must be undone first; returns the updated list immutably.
|
|
64
|
+
*/
|
|
65
|
+
export declare function undoAgentPatch(api: EditorHostApi, patches: readonly AgentPatchEntry[], patchId: string): {
|
|
66
|
+
ok: true;
|
|
67
|
+
patches: AgentPatchEntry[];
|
|
68
|
+
} | {
|
|
69
|
+
ok: false;
|
|
70
|
+
error: string;
|
|
71
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { EDITOR_MCP_TOOLS } from "../mcp/tools.js";
|
|
2
|
+
import { formatAgentContext, packAgentContext } from "./context.js";
|
|
3
|
+
import { routeToolCall } from "./toolBridge.js";
|
|
4
|
+
function nextId(prefix) {
|
|
5
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.floor(Math.random() * 1296).toString(36)}`;
|
|
6
|
+
}
|
|
7
|
+
const DEFAULT_MAX_ROUNDS = 6;
|
|
8
|
+
/**
|
|
9
|
+
* Runs one user message against an agent endpoint: injects live editor context via
|
|
10
|
+
* `packAgentContext`, executes tool calls through `routeToolCall` (shared undo), and returns
|
|
11
|
+
* transcript + patch entries the human can reverse with `undoAgentPatch`.
|
|
12
|
+
*/
|
|
13
|
+
export async function runAgentTurn(options) {
|
|
14
|
+
const now = options.now ?? Date.now;
|
|
15
|
+
const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;
|
|
16
|
+
const context = packAgentContext(options.api);
|
|
17
|
+
const system = {
|
|
18
|
+
role: "system",
|
|
19
|
+
content: [
|
|
20
|
+
"You are the in-editor scene agent. Edit the live scene only via the provided tools.",
|
|
21
|
+
"Each tool name is an editor RPC verb — the same surface as the GUI and MCP bridge.",
|
|
22
|
+
"Current editor context:",
|
|
23
|
+
formatAgentContext(context),
|
|
24
|
+
].join("\n"),
|
|
25
|
+
};
|
|
26
|
+
const messages = [
|
|
27
|
+
system,
|
|
28
|
+
...options.history.filter((m) => m.role !== "system"),
|
|
29
|
+
{ role: "user", content: options.userMessage },
|
|
30
|
+
];
|
|
31
|
+
const transcript = [
|
|
32
|
+
{ kind: "user", id: nextId("user"), content: options.userMessage, at: now() },
|
|
33
|
+
];
|
|
34
|
+
const patches = [];
|
|
35
|
+
for (let round = 0; round < maxRounds; round += 1) {
|
|
36
|
+
const liveContext = packAgentContext(options.api);
|
|
37
|
+
let response;
|
|
38
|
+
try {
|
|
39
|
+
response = await options.endpoint.chat({
|
|
40
|
+
messages,
|
|
41
|
+
context: liveContext,
|
|
42
|
+
tools: EDITOR_MCP_TOOLS,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const content = error instanceof Error ? error.message : String(error);
|
|
47
|
+
transcript.push({ kind: "error", id: nextId("err"), content, at: now() });
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (typeof response.message === "string" && response.message.length > 0) {
|
|
51
|
+
messages.push({ role: "assistant", content: response.message });
|
|
52
|
+
transcript.push({ kind: "assistant", id: nextId("asst"), content: response.message, at: now() });
|
|
53
|
+
}
|
|
54
|
+
const toolCalls = response.toolCalls ?? [];
|
|
55
|
+
if (toolCalls.length === 0)
|
|
56
|
+
break;
|
|
57
|
+
for (const call of toolCalls) {
|
|
58
|
+
const result = routeToolCall(options.api, call);
|
|
59
|
+
messages.push({
|
|
60
|
+
role: "tool",
|
|
61
|
+
toolCallId: result.id,
|
|
62
|
+
name: result.name,
|
|
63
|
+
content: JSON.stringify(result.ok
|
|
64
|
+
? { ok: true, result: result.result, mutated: result.mutated }
|
|
65
|
+
: { ok: false, error: result.error }),
|
|
66
|
+
});
|
|
67
|
+
transcript.push({ kind: "tool", id: nextId("tool"), result, at: now() });
|
|
68
|
+
if (result.mutated) {
|
|
69
|
+
const patch = {
|
|
70
|
+
id: nextId("patch"),
|
|
71
|
+
toolCallId: result.id,
|
|
72
|
+
method: result.name,
|
|
73
|
+
summary: result.patchSummary,
|
|
74
|
+
at: now(),
|
|
75
|
+
undone: false,
|
|
76
|
+
};
|
|
77
|
+
patches.push(patch);
|
|
78
|
+
transcript.push({ kind: "patch", id: patch.id, patch, at: patch.at });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
messages: messages.filter((m) => m.role !== "system"),
|
|
84
|
+
transcript,
|
|
85
|
+
patches,
|
|
86
|
+
context,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Undoes one agent patch when it is still the top of the session undo stack.
|
|
91
|
+
* Newer live agent patches must be undone first; returns the updated list immutably.
|
|
92
|
+
*/
|
|
93
|
+
export function undoAgentPatch(api, patches, patchId) {
|
|
94
|
+
const index = patches.findIndex((p) => p.id === patchId);
|
|
95
|
+
if (index < 0)
|
|
96
|
+
return { ok: false, error: "patch not found" };
|
|
97
|
+
const patch = patches[index];
|
|
98
|
+
if (patch.undone)
|
|
99
|
+
return { ok: false, error: "already undone" };
|
|
100
|
+
const laterLive = patches.slice(index + 1).some((p) => !p.undone);
|
|
101
|
+
if (laterLive) {
|
|
102
|
+
return { ok: false, error: "undo newer agent edits first" };
|
|
103
|
+
}
|
|
104
|
+
if (!api.getSession().canUndo()) {
|
|
105
|
+
return { ok: false, error: "nothing to undo (human may have undone already)" };
|
|
106
|
+
}
|
|
107
|
+
const response = api.handle({ method: "undo" });
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
return { ok: false, error: response.error ?? "undo failed" };
|
|
110
|
+
}
|
|
111
|
+
const next = patches.map((p, i) => (i === index ? { ...p, undone: true } : p));
|
|
112
|
+
return { ok: true, patches: next };
|
|
113
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function NumberField({ label, value, onCommit, step, }: {
|
|
2
|
+
label: string;
|
|
3
|
+
value: number;
|
|
4
|
+
onCommit: (value: number) => void;
|
|
5
|
+
step?: number;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export declare function SliderRow({ label, value, min, max, step, onChange, format, }: {
|
|
8
|
+
label: string;
|
|
9
|
+
value: number;
|
|
10
|
+
min: number;
|
|
11
|
+
max: number;
|
|
12
|
+
step: number;
|
|
13
|
+
onChange: (value: number) => void;
|
|
14
|
+
format?: (value: number) => string;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { INPUT } from "./chromeStyles.js";
|
|
3
|
+
export function NumberField({ label, value, onCommit, step = 1, }) {
|
|
4
|
+
return (_jsxs("label", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-[9px] font-semibold uppercase tracking-wider text-neutral-500", children: label }), _jsx("input", { type: "number", step: step, className: `w-32 ${INPUT}`, value: value, onChange: (event) => {
|
|
5
|
+
const next = Number(event.target.value);
|
|
6
|
+
if (!Number.isFinite(next))
|
|
7
|
+
return;
|
|
8
|
+
onCommit(next);
|
|
9
|
+
} })] }));
|
|
10
|
+
}
|
|
11
|
+
export function SliderRow({ label, value, min, max, step, onChange, format, }) {
|
|
12
|
+
return (_jsxs("label", { className: "block space-y-1", children: [_jsxs("span", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-[9px] font-semibold uppercase tracking-wider text-neutral-500", children: label }), _jsx("span", { className: "text-cyan-200", children: format ? format(value) : value.toFixed(2) })] }), _jsx("input", { type: "range", min: min, max: max, step: step, value: value, onChange: (event) => onChange(Number(event.target.value)), className: "w-full accent-amber-400" })] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const BTN = "rounded-md bg-white/[0.04] px-2 py-1 text-neutral-300 ring-1 ring-inset ring-white/[0.06] transition-colors hover:bg-white/10 hover:text-neutral-100";
|
|
2
|
+
export declare const INPUT = "rounded-md border border-white/10 bg-black/40 px-2 py-1 outline-none transition-colors placeholder:text-neutral-600 focus:border-cyan-400/60 focus:bg-black/60";
|
|
3
|
+
export declare const MICRO = "text-[9px] font-semibold uppercase tracking-[0.14em] text-neutral-500";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const BTN = "rounded-md bg-white/[0.04] px-2 py-1 text-neutral-300 ring-1 ring-inset ring-white/[0.06] transition-colors hover:bg-white/10 hover:text-neutral-100";
|
|
2
|
+
export const INPUT = "rounded-md border border-white/10 bg-black/40 px-2 py-1 outline-none transition-colors placeholder:text-neutral-600 focus:border-cyan-400/60 focus:bg-black/60";
|
|
3
|
+
export const MICRO = "text-[9px] font-semibold uppercase tracking-[0.14em] text-neutral-500";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { EditorApp, type EditorAppProps, type EditorSaveFn } from "./EditorApp.js";
|
|
2
|
+
export { StandaloneEditor, createBlankPlayable, blankWorld, downloadSaver, type StandaloneEditorProps, type StandaloneAsset, type BlankPlayableOptions, } from "./StandaloneEditor.js";
|
|
3
|
+
export { EditorLayerOverlays, PathDraftPreview } from "./DebugDraw.js";
|
|
4
|
+
export { EditorChrome } from "./EditorChrome.js";
|
|
5
|
+
export { SelectionGizmo, ViewportSelect } from "./SelectionGizmo.js";
|
|
6
|
+
export { createEditorUiStore, newPlacementId, DEFAULT_SCULPT_SETTINGS, DEFAULT_PAINT_SETTINGS, TERRAIN_MATERIALS, TERRAIN_MATERIAL_COLORS, type EditorTool, type EditorUiState, type EditorUiStore, type GizmoMode, type PaintSettings, type PlacementTool, type SculptSettings, type SnapMode, type TerrainBrushKind, type TerrainMaterial, type TerrainMode, } from "./uiStore.js";
|
|
7
|
+
export { TerrainSculpt } from "./TerrainSculpt.js";
|
|
8
|
+
export { ScatterPreview } from "./ScatterPreview.js";
|
|
9
|
+
export { EditorCameraDriver } from "./EditorCameraDriver.js";
|
|
10
|
+
export { AssetBrowser, assetsFromCatalog, type EditorAssetEntry } from "./AssetBrowser.js";
|
|
11
|
+
export { createEditorHost, getEditorHost, installEditorHost, type EditorAssetInfo, type EditorBridgeRequest, type EditorBridgeResponse, type EditorHostApi, type EditorPerfSample, type EditorRunMode, } from "./session.js";
|
|
12
|
+
export { PerfProbe } from "./PerfProbe.js";
|
|
13
|
+
export { useStoreSelector, shallowArrayEqual, virtualWindow, type SubscribableStore, type VirtualWindow, } from "./useStoreSelector.js";
|
|
14
|
+
export { useF2Chord } from "./useF2Chord.js";
|
|
15
|
+
export type { EditorBridgeServerOptions, EditorBridgeServer } from "./mcp/bridgeServer.js";
|
|
16
|
+
export { EDITOR_MCP_TOOLS, type EditorMcpTool } from "./mcp/tools.js";
|
|
17
|
+
export { packAgentContext, type AgentEditorContext } from "./agent/context.js";
|
|
18
|
+
export { routeToolCall, type AgentToolCall, type AgentToolResult } from "./agent/toolBridge.js";
|
|
19
|
+
export { createDefaultAgentEndpoint, createHttpAgentEndpoint, resolveAgentEndpointConfig, EDITOR_AGENT_URL_ENV, EDITOR_AGENT_KEY_ENV, EDITOR_AGENT_KEY_FALLBACK_ENV, type AgentEndpoint, type AgentEndpointConfig, type AgentChatMessage, type AgentChatRequest, type AgentChatResponse, type AgentChatRole, } from "./agent/endpoint.js";
|
|
20
|
+
export { runAgentTurn, undoAgentPatch, type AgentPatchEntry, type AgentTranscriptEntry, type AgentTurnResult, } from "./agent/turn.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { EditorApp } from "./EditorApp.js";
|
|
2
|
+
export { StandaloneEditor, createBlankPlayable, blankWorld, downloadSaver, } from "./StandaloneEditor.js";
|
|
3
|
+
export { EditorLayerOverlays, PathDraftPreview } from "./DebugDraw.js";
|
|
4
|
+
export { EditorChrome } from "./EditorChrome.js";
|
|
5
|
+
export { SelectionGizmo, ViewportSelect } from "./SelectionGizmo.js";
|
|
6
|
+
export { createEditorUiStore, newPlacementId, DEFAULT_SCULPT_SETTINGS, DEFAULT_PAINT_SETTINGS, TERRAIN_MATERIALS, TERRAIN_MATERIAL_COLORS, } from "./uiStore.js";
|
|
7
|
+
export { TerrainSculpt } from "./TerrainSculpt.js";
|
|
8
|
+
export { ScatterPreview } from "./ScatterPreview.js";
|
|
9
|
+
export { EditorCameraDriver } from "./EditorCameraDriver.js";
|
|
10
|
+
export { AssetBrowser, assetsFromCatalog } from "./AssetBrowser.js";
|
|
11
|
+
export { createEditorHost, getEditorHost, installEditorHost, } from "./session.js";
|
|
12
|
+
export { PerfProbe } from "./PerfProbe.js";
|
|
13
|
+
export { useStoreSelector, shallowArrayEqual, virtualWindow, } from "./useStoreSelector.js";
|
|
14
|
+
export { useF2Chord } from "./useF2Chord.js";
|
|
15
|
+
export { EDITOR_MCP_TOOLS } from "./mcp/tools.js";
|
|
16
|
+
export { packAgentContext } from "./agent/context.js";
|
|
17
|
+
export { routeToolCall } from "./agent/toolBridge.js";
|
|
18
|
+
export { createDefaultAgentEndpoint, createHttpAgentEndpoint, resolveAgentEndpointConfig, EDITOR_AGENT_URL_ENV, EDITOR_AGENT_KEY_ENV, EDITOR_AGENT_KEY_FALLBACK_ENV, } from "./agent/endpoint.js";
|
|
19
|
+
export { runAgentTurn, undoAgentPatch, } from "./agent/turn.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { EditorHostApi } from "../session.js";
|
|
2
|
+
/** Options for starting the editor's HTTP bridge server: host api, port, hostname. */
|
|
3
|
+
export interface EditorBridgeServerOptions {
|
|
4
|
+
host: EditorHostApi;
|
|
5
|
+
port?: number;
|
|
6
|
+
hostname?: string;
|
|
7
|
+
}
|
|
8
|
+
/** A running editor bridge server: its bound port, URL, and a stop handle. */
|
|
9
|
+
export interface EditorBridgeServer {
|
|
10
|
+
port: number;
|
|
11
|
+
url: string;
|
|
12
|
+
stop: () => void;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EditorCatalogDefinition } from "@jgengine/core/editor/index";
|
|
2
|
+
/** Result of {@link loadGameCatalogs}: validated definitions, or diagnostics when the export is malformed. */
|
|
3
|
+
export type LoadGameCatalogsResult = {
|
|
4
|
+
ok: true;
|
|
5
|
+
catalogs: readonly EditorCatalogDefinition[];
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
errors: {
|
|
9
|
+
path: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Validates an already-resolved `editorCatalogs` export (post module-load, post factory-call).
|
|
15
|
+
* Missing/undefined yields an empty list — games without catalogs are valid.
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export declare function decodeGameCatalogs(resolved: unknown): LoadGameCatalogsResult;
|
|
19
|
+
/**
|
|
20
|
+
* Node-only: resolves a game's `editorCatalogs` export from Games/<id>/src and validates its shape
|
|
21
|
+
* before it reaches a live editor session.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export declare function loadGameCatalogs(gameId: string): Promise<LoadGameCatalogsResult>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
async function importOptionalEditorCatalogsModule(gameId) {
|
|
2
|
+
try {
|
|
3
|
+
const path = new URL(`../../../../Games/${gameId}/src/editorCatalogs.ts`, import.meta.url);
|
|
4
|
+
return (await import(path.href));
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function isPlainObject(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function decodeSchema(value, path, errors) {
|
|
14
|
+
if (!isPlainObject(value) || !Array.isArray(value.fields)) {
|
|
15
|
+
errors.push({ path, message: "expected { fields: ParamField[] }" });
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validates an already-resolved `editorCatalogs` export (post module-load, post factory-call).
|
|
22
|
+
* Missing/undefined yields an empty list — games without catalogs are valid.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export function decodeGameCatalogs(resolved) {
|
|
26
|
+
if (resolved === undefined || resolved === null)
|
|
27
|
+
return { ok: true, catalogs: [] };
|
|
28
|
+
if (!Array.isArray(resolved)) {
|
|
29
|
+
return { ok: false, errors: [{ path: "$", message: "editorCatalogs must be an array" }] };
|
|
30
|
+
}
|
|
31
|
+
const errors = [];
|
|
32
|
+
const catalogs = [];
|
|
33
|
+
resolved.forEach((item, index) => {
|
|
34
|
+
const path = `$[${index}]`;
|
|
35
|
+
if (!isPlainObject(item)) {
|
|
36
|
+
errors.push({ path, message: "expected an object" });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (typeof item.id !== "string")
|
|
40
|
+
errors.push({ path: `${path}.id`, message: "expected a string" });
|
|
41
|
+
if (typeof item.label !== "string")
|
|
42
|
+
errors.push({ path: `${path}.label`, message: "expected a string" });
|
|
43
|
+
const schema = decodeSchema(item.schema, `${path}.schema`, errors);
|
|
44
|
+
if (!Array.isArray(item.entries)) {
|
|
45
|
+
errors.push({ path: `${path}.entries`, message: "expected an array" });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (typeof item.id !== "string" || typeof item.label !== "string" || schema === null)
|
|
49
|
+
return;
|
|
50
|
+
const entries = [];
|
|
51
|
+
item.entries.forEach((entry, entryIndex) => {
|
|
52
|
+
const entryPath = `${path}.entries[${entryIndex}]`;
|
|
53
|
+
if (!isPlainObject(entry) || typeof entry.id !== "string") {
|
|
54
|
+
errors.push({ path: entryPath, message: "expected { id: string, label?, meta? }" });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
entries.push({
|
|
58
|
+
id: entry.id,
|
|
59
|
+
...(typeof entry.label === "string" ? { label: entry.label } : {}),
|
|
60
|
+
...(isPlainObject(entry.meta) ? { meta: entry.meta } : {}),
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
catalogs.push({ id: item.id, label: item.label, schema, entries });
|
|
64
|
+
});
|
|
65
|
+
if (errors.length > 0)
|
|
66
|
+
return { ok: false, errors };
|
|
67
|
+
return { ok: true, catalogs };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Node-only: resolves a game's `editorCatalogs` export from Games/<id>/src and validates its shape
|
|
71
|
+
* before it reaches a live editor session.
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
export async function loadGameCatalogs(gameId) {
|
|
75
|
+
const mod = await importOptionalEditorCatalogsModule(gameId);
|
|
76
|
+
if (mod?.editorCatalogs === undefined)
|
|
77
|
+
return { ok: true, catalogs: [] };
|
|
78
|
+
let resolved;
|
|
79
|
+
try {
|
|
80
|
+
const value = mod.editorCatalogs;
|
|
81
|
+
resolved = typeof value === "function" ? value() : value;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return { ok: false, errors: [{ path: "$", message: error instanceof Error ? error.message : String(error) }] };
|
|
85
|
+
}
|
|
86
|
+
return decodeGameCatalogs(resolved);
|
|
87
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EditorBridgeRequest } from "../session.js";
|
|
2
|
+
/** One field-level failure surfaced while decoding an untrusted RPC request. */
|
|
3
|
+
export interface RpcRequestDiagnostic {
|
|
4
|
+
path: string;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
/** Result of {@link decodeEditorBridgeRequest}: a request whose `method` is a real one, or the diagnostic that rejected it. */
|
|
8
|
+
export type DecodeRpcRequestResult = {
|
|
9
|
+
ok: true;
|
|
10
|
+
request: EditorBridgeRequest;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
errors: RpcRequestDiagnostic[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
export declare function decodeEditorBridgeRequest(raw: unknown): DecodeRpcRequestResult;
|
|
@@ -0,0 +1,97 @@
|
|
|
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_to_collection",
|
|
5
|
+
"assign_material",
|
|
6
|
+
"auto_paint",
|
|
7
|
+
"batch_set_properties",
|
|
8
|
+
"blend_terrain",
|
|
9
|
+
"camera_frame",
|
|
10
|
+
"camera_goto",
|
|
11
|
+
"clear_runtime_override",
|
|
12
|
+
"clear_selection",
|
|
13
|
+
"convert_scatter",
|
|
14
|
+
"create_collection",
|
|
15
|
+
"create_prefab",
|
|
16
|
+
"create_terrain",
|
|
17
|
+
"delete_collection",
|
|
18
|
+
"delete_prefab",
|
|
19
|
+
"detach_prefab_instance",
|
|
20
|
+
"dispatch",
|
|
21
|
+
"document_revision",
|
|
22
|
+
"editor_status",
|
|
23
|
+
"export_document",
|
|
24
|
+
"fill_terrain",
|
|
25
|
+
"get_marker",
|
|
26
|
+
"get_volume",
|
|
27
|
+
"hierarchy",
|
|
28
|
+
"import_document",
|
|
29
|
+
"insert_prefab",
|
|
30
|
+
"get_catalog_entry",
|
|
31
|
+
"list_assets",
|
|
32
|
+
"list_catalogs",
|
|
33
|
+
"list_collections",
|
|
34
|
+
"list_layers",
|
|
35
|
+
"list_prefabs",
|
|
36
|
+
"list_selection",
|
|
37
|
+
"paint_terrain",
|
|
38
|
+
"perf_report",
|
|
39
|
+
"place_asset",
|
|
40
|
+
"pull_document_patches",
|
|
41
|
+
"pull_runtime_deltas",
|
|
42
|
+
"push_document_patch",
|
|
43
|
+
"push_runtime_delta",
|
|
44
|
+
"redo",
|
|
45
|
+
"remove_from_collection",
|
|
46
|
+
"rename_collection",
|
|
47
|
+
"runtime_get",
|
|
48
|
+
"runtime_pause",
|
|
49
|
+
"runtime_resume",
|
|
50
|
+
"runtime_set",
|
|
51
|
+
"runtime_snapshot",
|
|
52
|
+
"runtime_step",
|
|
53
|
+
"runtime_summary",
|
|
54
|
+
"scatter_summary",
|
|
55
|
+
"scene_summary",
|
|
56
|
+
"sculpt_terrain",
|
|
57
|
+
"select",
|
|
58
|
+
"select_collection",
|
|
59
|
+
"set_catalog_entry",
|
|
60
|
+
"set_collection_flags",
|
|
61
|
+
"set_collection_members",
|
|
62
|
+
"set_marker",
|
|
63
|
+
"set_meta",
|
|
64
|
+
"set_mode",
|
|
65
|
+
"set_note",
|
|
66
|
+
"set_parent",
|
|
67
|
+
"set_path",
|
|
68
|
+
"set_runtime_override",
|
|
69
|
+
"set_terrain_layers",
|
|
70
|
+
"set_transform",
|
|
71
|
+
"set_volume",
|
|
72
|
+
"terrain_layers",
|
|
73
|
+
"terrain_materials",
|
|
74
|
+
"terrain_summary",
|
|
75
|
+
"undo",
|
|
76
|
+
"write_back_override",
|
|
77
|
+
]);
|
|
78
|
+
/**
|
|
79
|
+
* Validates an untrusted JSON-decoded RPC payload (from `--rpc` or the HTTP bridge) before it
|
|
80
|
+
* reaches `EditorHostApi.handle`: confirms it is a plain object carrying a known `method` name.
|
|
81
|
+
* Per-method field shape is still enforced by `handle`'s own dispatch, but a garbled or
|
|
82
|
+
* unknown-method payload is rejected here with a path-specific diagnostic instead of flowing
|
|
83
|
+
* through on a blind cast.
|
|
84
|
+
*/
|
|
85
|
+
export function decodeEditorBridgeRequest(raw) {
|
|
86
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
87
|
+
return { ok: false, errors: [{ path: "$", message: "expected an RPC request object" }] };
|
|
88
|
+
}
|
|
89
|
+
const method = raw.method;
|
|
90
|
+
if (typeof method !== "string") {
|
|
91
|
+
return { ok: false, errors: [{ path: "$.method", message: "expected a string" }] };
|
|
92
|
+
}
|
|
93
|
+
if (!EDITOR_BRIDGE_METHODS.has(method)) {
|
|
94
|
+
return { ok: false, errors: [{ path: "$.method", message: `unknown method "${method}"` }] };
|
|
95
|
+
}
|
|
96
|
+
return { ok: true, request: raw };
|
|
97
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** One MCP tool descriptor — same verbs as the in-browser host RPC. */
|
|
2
|
+
export interface EditorMcpTool {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
/** Full set of MCP tools an agent can call to drive the live scene editor. */
|
|
8
|
+
export declare const EDITOR_MCP_TOOLS: readonly EditorMcpTool[];
|