@vgai/editor-sdk 0.4.1 → 0.5.0-canary.20260719.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/README.md CHANGED
@@ -12,7 +12,18 @@ to the caller.
12
12
  import { EditorClient } from '@vgai/editor-sdk';
13
13
 
14
14
  const editor = new EditorClient(); // default http://localhost:5173
15
+ const projectEditor = new EditorClient({ url: 'http://localhost:25786' });
15
16
  await editor.play();
17
+
18
+ // Present the same editor subject/view to the connected human and get a
19
+ // compact share URL. This does not serialize document contents or layout.
20
+ const shown = await editor.present({
21
+ version: 1,
22
+ document: { kind: 'tool', id: 'walking-castle-builder' },
23
+ viewport: { camera: 'isometric', frame: 'document' },
24
+ utility: 'profiler',
25
+ });
26
+ console.log(shown.url);
16
27
  await editor.waitForState((s) => s.playState === 'playing');
17
28
  const entries = await editor.getLogEntries(); // proves frames actually ran
18
29
  ```
@@ -22,14 +33,15 @@ const entries = await editor.getLogEntries(); // proves frames actually ran
22
33
  One export, `EditorClient`, plus its types (`EditorState`, `ProjectInfo`,
23
34
  `AssetKind`, `ShadingMode`, ...). Methods, by group:
24
35
 
25
- - Play control: `play`, `stop`, `pause`, `resume`, `step`
36
+ - Play control: `play`, `restart`, `stop`, `pause`, `resume`, `step`
26
37
  - Selection: `select(id | null)`, `selectMultiple`, `selectAll`
27
38
  - Viewport: `focusEntity`, `focusSelection`, `viewPreset`, `setCamera`,
28
39
  `captureViewport`
29
40
  - Asset preview: `captureAssetPreview` for deterministic front, right, top,
30
41
  and three-quarter captures of a project model or authored entity hierarchy
31
42
  - Panels: `showViewport('scene'|'game')`, `showInspector`, `openAsset`,
32
- `closeAsset`, `toggleConsole`, `toggleCommandPalette`, `showBuild`
43
+ `closeAsset`, `toggleConsole`, `toggleCommandPalette`, `showBuild`, and
44
+ `present(EditorView)` for an atomic human-visible view plus share URL
33
45
  - Display: `setGrid`, `setHelpers`, `setStats`, `setShadingMode` (`solid`,
34
46
  `unlit`, `wireframe`, `normals`, or `overdraw`), `setHelperType` (including
35
47
  the independent `bounds` category). Shading targets the active Scene/Game
@@ -54,11 +66,12 @@ registered tool and an already-configured client:
54
66
  import { Button } from '@editor/widgets';
55
67
  import type { ToolContributionProps } from '@vgai/editor-sdk/contributions';
56
68
 
57
- export default function MapBuilder({ tool, client }: ToolContributionProps) {
69
+ export default function MapBuilder({ tool, client, account }: ToolContributionProps) {
58
70
  return (
59
71
  <Button
60
72
  variant="solid"
61
73
  onClick={() => void client.runProjectTool(tool.name, { seed: 42 }, { confirm: true })}
74
+ title={`Default execution: ${account.preferredRoute}`}
62
75
  >
63
76
  Build map
64
77
  </Button>
@@ -66,16 +79,71 @@ export default function MapBuilder({ tool, client }: ToolContributionProps) {
66
79
  }
67
80
  ```
68
81
 
69
- The package registration chooses `workspace.document`, `workspace.utility`, or
70
- `selection.inspector`. Inspector contributions additionally receive
71
- `node`/`nodeId` and export `match(node, adapter)`. There is no extension class,
72
- lifecycle, or proprietary UI description.
82
+ The package registration chooses `workspace.document`, `workspace.utility`,
83
+ `selection.inspector`, `asset.inspector`, or `generation.result`. Selection
84
+ Inspector contributions receive `node`/`nodeId` and export
85
+ `match(node, adapter)`; asset Inspector contributions receive `asset` and
86
+ export `match(asset)`. A generation-result contribution receives the durable
87
+ job and raw native poll result inside the editor-owned result document and
88
+ must export `match(job, result)`; registration order never selects a renderer.
89
+ All contributions receive `account`, a validated global projection containing
90
+ plan, credits, spend policy, and provider-specific route availability. It never
91
+ contains an access token. There is no extension class, lifecycle, or
92
+ proprietary UI description.
73
93
 
74
94
  Use ordinary React/CSS for composition and `@editor/widgets` for controls.
75
95
  Shape the UI for its contribution point: a bounded workspace for documents, a
76
96
  dense single column for inspector sections, and a compact row/status block for
77
97
  utilities. Badges are for short statuses and counts, not headings.
78
98
 
99
+ ### Extension contract (`@vgai/editor-sdk/extension`)
100
+
101
+ The typed contract for everything a game project contributes TO the editor —
102
+ four surfaces, one degradation ladder (`ExtensionContributionTier`:
103
+ `active` / `absent` / `failed`). An absent contribution hides its surface
104
+ (the editor never fabricates placeholder data); a failing one is contained
105
+ per-contribution and reported loudly on the editor console — never a crashed
106
+ editor, never silent fake output.
107
+
108
+ 1. **Editor panels** — `workspace.document` / `workspace.utility` tool
109
+ contributions (previous section). Panels join the Dockview workspace;
110
+ there is no parallel rail.
111
+ 2. **Inspector sections** — `selection.inspector` / `asset.inspector` tool
112
+ contributions with an exported `match`.
113
+ 3. **Per-component viewport gizmos** — a `GameComponent` subclass declares
114
+ `static editorGizmo: ComponentGizmoHook` (the Unity `OnDrawGizmos`
115
+ analog). The editor renders its objects for every scene entity carrying
116
+ that component — editor-only layer, Helpers ▸ Components toggle,
117
+ raycast-excluded, draw-only. Props arrive schema-parsed through the
118
+ component's own Zod `static schema`; build objects with `ctx.three` (the
119
+ editor's three module):
120
+
121
+ ```ts
122
+ import type { ComponentGizmoHook } from '@vgai/editor-sdk/extension';
123
+
124
+ export class TriggerZone extends GameComponent {
125
+ static schema = z.object({ radius: z.number().default(2) });
126
+ static editorGizmo: ComponentGizmoHook = ({ props, three }) => {
127
+ const geo = new three.RingGeometry(0.98 * (props.radius as number), props.radius as number);
128
+ return new three.Mesh(geo, new three.MeshBasicMaterial({ wireframe: true }));
129
+ };
130
+ update() {}
131
+ }
132
+ ```
133
+
134
+ The scaffold template's `SceneCamera` (`src/scripts/components/
135
+ scene-camera.ts`) is the worked reference.
136
+
137
+ Editing a component file in edit mode re-captures the registry and
138
+ redraws contributed gizmos (script-HMR); registry restructures may need a
139
+ project reopen. Recorded deferral: in the HOSTED (browser) editor,
140
+ contributed component gizmos resolve `absent` — example registries are
141
+ evaluated for play but not captured at design time.
142
+ 4. **System adapters** — runtime capabilities registered from game code via
143
+ `ctx.registerSystemAdapter?.(kind, impl)` (`SystemAdapters` in
144
+ `@vgai/engine`). Deliberately not re-exported here: the engine already
145
+ publishes that seam and every consumer of it also imports the engine.
146
+
79
147
  ### Asset Lab capture
80
148
 
81
149
  `captureAssetPreview` accepts exactly one source: an authored entity hierarchy
@@ -100,12 +168,12 @@ buffers and images must resolve on the same project origin. The thin CLI
100
168
  equivalent writes the four views and contact sheet to disk:
101
169
 
102
170
  ```bash
103
- vgai asset-preview --asset /models/storefront.glb --out artifacts/storefront
171
+ npm run vgai -- asset-preview --asset /models/storefront.glb --out artifacts/storefront
104
172
  ```
105
173
 
106
174
  ## Limitations
107
175
 
108
176
  - The published package exports raw TypeScript (`exports: "./src/index.ts"`),
109
177
  so consumers need a TypeScript-aware runner/bundler such as tsx or Vite.
110
- - Requires the editor dev server (`npm run dev` or `vgai edit`); there is no
178
+ - Requires the editor dev server (`npm run dev` or `npx @vgai/cli@latest edit <project>`); there is no
111
179
  offline mode.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@vgai/editor-sdk",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "Apache-2.0",
5
- "version": "0.4.1",
5
+ "version": "0.5.0-canary.20260719.0",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
@@ -17,6 +17,15 @@
17
17
  ],
18
18
  "exports": {
19
19
  ".": "./src/index.ts",
20
- "./contributions": "./src/contributions.ts"
20
+ "./contributions": "./src/contributions.ts",
21
+ "./extension": "./src/extension.ts"
22
+ },
23
+ "dependencies": {
24
+ "@types/three": "^0.180.0",
25
+ "@vgai/sdk": "0.5.0-canary.20260719.0"
26
+ },
27
+ "peerDependencies": {
28
+ "react": "^19.0.0",
29
+ "three": "^0.180.0"
21
30
  }
22
31
  }
package/src/client.ts CHANGED
@@ -1,11 +1,16 @@
1
+ import type { GenerationJobsDocument } from '@vgai/sdk/generations';
1
2
  import type {
3
+ ActiveDocumentCapture,
2
4
  AssetKind,
3
5
  AssetPreviewCapture,
4
6
  AssetPreviewOptions,
5
7
  AssetPreviewSource,
6
8
  EditorState,
9
+ EditorView,
7
10
  GameCapture,
8
11
  HelperVisibility,
12
+ HumanoidShotSetCapture,
13
+ PresentedEditorView,
9
14
  ProjectInfo,
10
15
  ProjectTemplate,
11
16
  ProjectToolCatalog,
@@ -25,6 +30,17 @@ export class EditorClient {
25
30
  private readonly baseUrl: string;
26
31
 
27
32
  constructor(opts?: { url?: string }) {
33
+ if (opts !== undefined && (typeof opts !== 'object' || opts === null || Array.isArray(opts))) {
34
+ throw new TypeError(
35
+ 'EditorClient options must be an object. Use new EditorClient({ url: "http://localhost:5173" }), not new EditorClient("...").',
36
+ );
37
+ }
38
+ const unknownOptions = Object.keys(opts ?? {}).filter((key) => key !== 'url');
39
+ if (unknownOptions.length > 0) {
40
+ throw new Error(
41
+ `EditorClient: unknown option${unknownOptions.length === 1 ? '' : 's'} ${unknownOptions.map((key) => `"${key}"`).join(', ')}. Use { url: "http://localhost:<port>" } to target an editor.`,
42
+ );
43
+ }
28
44
  this.baseUrl = (opts?.url ?? DEFAULT_URL).replace(/\/$/, '');
29
45
  }
30
46
 
@@ -54,6 +70,11 @@ export class EditorClient {
54
70
  await this.command({ type: 'play', ...(opts?.seed !== undefined ? { seed: opts.seed } : {}) });
55
71
  }
56
72
 
73
+ /** Dispose the current play session and mount it again from fresh project entry source. */
74
+ async restart(): Promise<void> {
75
+ await this.command({ type: 'play' });
76
+ }
77
+
57
78
  async stop(): Promise<void> {
58
79
  await this.command({ type: 'stop' });
59
80
  }
@@ -118,7 +139,7 @@ export class EditorClient {
118
139
  /**
119
140
  * Unit 4 (live-front-door wave) — capture the RUNNING GAME (`vgai
120
141
  * screenshot`'s wire leg). Sends the SAME `bridge-screenshot` relay op
121
- * `@vgai/probe`'s `RelayTransport.screenshot` (and therefore
142
+ * `@vgai/e2e`'s `RelayTransport.screenshot` (and therefore
122
143
  * `game.screenshot()` on the relay path) already sends, so all three
123
144
  * surfaces composite the identical full game stack — canvas(es) plus the
124
145
  * HUD/react DOM layers — rather than any of them inventing a second,
@@ -166,6 +187,33 @@ export class EditorClient {
166
187
  };
167
188
  }
168
189
 
190
+ /**
191
+ * B7.6 — the canonical humanoid verify shot set (`vgai asset-preview
192
+ * --shots humanoid`): four turntable angles, tight zooms on the
193
+ * historical seam sites (head/hands/feet/shoulder), and a deep-bend pose
194
+ * pair, all framed by the editor from the loaded GLB's OWN skeleton — see
195
+ * `packages/editor/src/asset-preview.ts`'s `captureHumanoidShotSetAssetPreview`.
196
+ * Throws (via `command`'s `{ok:false}` unwrap) with a clear message naming
197
+ * the missing joint(s) when the asset has no Mixamo-named skeleton.
198
+ */
199
+ async captureHumanoidShotSetPreview(
200
+ source: AssetPreviewSource,
201
+ options: AssetPreviewOptions = {},
202
+ ): Promise<HumanoidShotSetCapture> {
203
+ const data = await this.command<HumanoidShotSetCapture>({
204
+ type: 'capture-asset-preview',
205
+ ...source,
206
+ ...options,
207
+ shots: 'humanoid',
208
+ });
209
+ return {
210
+ width: data.width,
211
+ height: data.height,
212
+ shots: data.shots,
213
+ contactSheet: data.contactSheet,
214
+ };
215
+ }
216
+
169
217
  // --- Panels ---
170
218
 
171
219
  async showViewport(tab: 'scene' | 'game'): Promise<void> {
@@ -196,6 +244,26 @@ export class EditorClient {
196
244
  await this.command({ type: 'show-build' });
197
245
  }
198
246
 
247
+ /** Atomically present a durable editor view and return its shareable URL. */
248
+ async present(view: EditorView): Promise<PresentedEditorView> {
249
+ const presented = await this.command<PresentedEditorView>({ type: 'present-view', view });
250
+ return { view: presented.view, url: presented.url, warnings: presented.warnings };
251
+ }
252
+
253
+ /** Read the editor's actual current durable projection. */
254
+ async currentView(): Promise<EditorView> {
255
+ const data = await this.command<{ view: EditorView }>({ type: 'current-view' });
256
+ return data.view;
257
+ }
258
+
259
+ /** Capture the active center document exactly as presented to the user. */
260
+ async captureActiveDocument(size?: number): Promise<ActiveDocumentCapture> {
261
+ return this.command<ActiveDocumentCapture>({
262
+ type: 'capture-active-document',
263
+ ...(size === undefined ? {} : { size }),
264
+ });
265
+ }
266
+
199
267
  // --- Display (set semantics) ---
200
268
 
201
269
  async setGrid(enabled: boolean): Promise<void> {
@@ -315,6 +383,26 @@ export class EditorClient {
315
383
  return body;
316
384
  }
317
385
 
386
+ // --- First-party generation job activity ---
387
+
388
+ /** Read the one project-local generation job ledger. Provider-native
389
+ * request/result shapes remain on their registered operations. */
390
+ async listGenerationJobs(): Promise<GenerationJobsDocument> {
391
+ const res = await fetch(`${this.baseUrl}/__editor/generations`);
392
+ if (!res.ok) throw new Error(`Failed to list generation jobs: ${res.status}`);
393
+ return (await res.json()) as GenerationJobsDocument;
394
+ }
395
+
396
+ /** Forget operational job state. Accepted provenance and project assets
397
+ * are deliberately unaffected. */
398
+ async forgetGenerationJob(id: string): Promise<boolean> {
399
+ const res = await fetch(`${this.baseUrl}/__editor/generations/${encodeURIComponent(id)}`, {
400
+ method: 'DELETE',
401
+ });
402
+ if (!res.ok) throw new Error(`Failed to forget generation job: ${res.status}`);
403
+ return ((await res.json()) as { removed: boolean }).removed;
404
+ }
405
+
318
406
  // --- Logs ---
319
407
 
320
408
  async getLogEntries(): Promise<
@@ -4,6 +4,16 @@
4
4
  * metadata names a module and the editor renders its default export.
5
5
  */
6
6
 
7
+ import type { GenerationAccountProjection } from '@vgai/sdk/account';
8
+ import type { GenerationJob } from '@vgai/sdk/generations';
9
+ import type {
10
+ AnimationClip,
11
+ Camera,
12
+ ColorRepresentation,
13
+ Object3D,
14
+ Scene,
15
+ WebGLRenderer,
16
+ } from 'three';
7
17
  import type { EditorClient } from './client.js';
8
18
  import type { ProjectToolCatalogEntry } from './types.js';
9
19
 
@@ -19,11 +29,122 @@ export interface ToolContributionNode {
19
29
  readonly flags: object;
20
30
  }
21
31
 
32
+ /**
33
+ * One disposable native Three.js build supplied to the editor's generic
34
+ * preview surface. This is a host-lifetime boundary, not a model format or
35
+ * procedural-asset base class: project code still constructs ordinary
36
+ * Object3D and AnimationClip instances directly.
37
+ */
38
+ export interface ToolObject3DPreviewSource {
39
+ readonly root: Object3D;
40
+ readonly animations?: readonly AnimationClip[];
41
+ dispose(): void;
42
+ }
43
+
44
+ /** Direct native handles for optional project-owned preview presentation. */
45
+ export interface ToolObject3DPreviewContext {
46
+ readonly scene: Scene;
47
+ readonly camera: Camera;
48
+ readonly renderer: WebGLRenderer;
49
+ /**
50
+ * A cloned Object3D graph, never the source root itself. Geometry,
51
+ * materials, and textures are shared read-only with the disposable source;
52
+ * clone a resource before applying preview-only mutations to it.
53
+ */
54
+ readonly model: Object3D;
55
+ }
56
+
57
+ export interface ToolObject3DPreviewExtension {
58
+ /** When present, replaces the viewer's default render call for this frame. */
59
+ render?(deltaSeconds: number): void;
60
+ /** Resize project-owned composers and render targets with the host viewport. */
61
+ resize?(width: number, height: number, pixelRatio: number): void;
62
+ /** Release every light, ground mesh, pass, target, and listener added here. */
63
+ dispose(): void;
64
+ }
65
+
66
+ export interface ToolObject3DPreviewProps {
67
+ /** Build a fresh source. The host disposes it after its preview snapshot. */
68
+ readonly build: () => ToolObject3DPreviewSource;
69
+ readonly displayName?: string;
70
+ readonly height?: number | string;
71
+ readonly background?: ColorRepresentation;
72
+ readonly showSkeleton?: boolean;
73
+ readonly autoplay?: boolean;
74
+ readonly cameraDirection?: readonly [number, number, number];
75
+ /** Host diagnostic grid. Defaults off with setupPreview, on otherwise. */
76
+ readonly showGrid?: boolean;
77
+ /** Host studio lights. Defaults off with setupPreview, on otherwise. */
78
+ readonly useDefaultLighting?: boolean;
79
+ /** Explicit renderer exposure. Custom presentation otherwise owns it. */
80
+ readonly exposure?: number;
81
+ /**
82
+ * Optional raw Three.js presentation hook for project-specific lights,
83
+ * ground, or postprocessing. Host grid/studio lights default off when this
84
+ * is present. It is preview chrome and is never exported.
85
+ */
86
+ readonly setupPreview?: (
87
+ context: ToolObject3DPreviewContext,
88
+ ) => ToolObject3DPreviewExtension | undefined;
89
+ readonly active?: boolean;
90
+ }
91
+
92
+ /**
93
+ * A native source graph hosted by the editor's normal Three.js authoring
94
+ * viewport, hierarchy, selection, and Inspector. Unlike Object3DPreview this
95
+ * surface exposes the live graph as the active workspace document context.
96
+ */
97
+ export interface ToolObject3DAuthoringProps {
98
+ readonly documentId: string;
99
+ readonly sourcePath: string;
100
+ readonly build: () => ToolObject3DPreviewSource;
101
+ readonly displayName?: string;
102
+ readonly background?: ColorRepresentation;
103
+ readonly cameraDirection?: readonly [number, number, number];
104
+ readonly autoplay?: boolean;
105
+ /**
106
+ * Optional binding from live native clips back to ordinary project source.
107
+ * Project code only serializes its own TypeScript shape; the editor owns the
108
+ * checksum-guarded write and records it in the canonical project history.
109
+ * Without this binding the animation workspace remains honestly read-only.
110
+ */
111
+ readonly animationSource?: ToolAnimationSourceBinding;
112
+ readonly active?: boolean;
113
+ }
114
+
115
+ export interface ToolAnimationSourceBinding {
116
+ /** Project-relative TypeScript file below src/. */
117
+ readonly path: string;
118
+ /** Undo/redo label shown by the editor history. */
119
+ readonly label?: string;
120
+ /**
121
+ * Serialize the same native clips returned by build into project-owned source.
122
+ * The source loader must accept replacement typed arrays when key counts change.
123
+ */
124
+ readonly serialize: (animations: readonly AnimationClip[]) => string;
125
+ }
126
+
127
+ /** Editor-owned native surfaces available to ordinary project React tools. */
128
+ export interface ToolContributionSurfaces {
129
+ readonly Object3DPreview: import('react').ComponentType<ToolObject3DPreviewProps>;
130
+ readonly Object3DAuthoring: import('react').ComponentType<ToolObject3DAuthoringProps>;
131
+ }
132
+
22
133
  export interface ToolContributionProps {
23
134
  /** The exact registered callable this contribution presents. */
24
135
  readonly tool: ProjectToolCatalogEntry;
136
+ /** Stable id of this presentation within the registered callable. */
137
+ readonly contributionId?: string;
25
138
  /** Direct editor SDK client; invoke with `client.runProjectTool(tool.name, ...)`. */
26
139
  readonly client: EditorClient;
140
+ /** Generic editor-owned presentation surfaces; project source remains native. */
141
+ readonly surfaces: ToolContributionSurfaces;
142
+ /** Sanitized product-account projection. Never contains an access token. */
143
+ readonly account: GenerationAccountProjection;
144
+ /** Present when mounted as a workspace document contribution. */
145
+ readonly documentId?: string;
146
+ /** Whether that workspace document is the active center subject. */
147
+ readonly active?: boolean;
27
148
  }
28
149
 
29
150
  export interface ToolInspectorContributionProps extends ToolContributionProps {
@@ -31,6 +152,43 @@ export interface ToolInspectorContributionProps extends ToolContributionProps {
31
152
  readonly nodeId: string | null;
32
153
  }
33
154
 
155
+ /** Stable editor projection of one selected project or external-library asset. */
156
+ export interface ToolContributionAsset {
157
+ readonly path: string;
158
+ readonly name: string;
159
+ readonly kind: string;
160
+ readonly origin: 'project' | 'library';
161
+ readonly sourcePath?: string;
162
+ }
163
+
164
+ export interface ToolAssetInspectorContributionProps extends ToolContributionProps {
165
+ readonly asset: ToolContributionAsset | null;
166
+ }
167
+
168
+ /** Optional named export required by `asset.inspector` contributions. */
169
+ export type ToolAssetInspectorContributionMatch = (asset: ToolContributionAsset | null) => boolean;
170
+
171
+ /**
172
+ * Provider-owned presentation mounted inside the editor-owned generation
173
+ * result document. The host owns job state, billing, and acceptance; this
174
+ * component only interprets the native poll result.
175
+ */
176
+ export interface ToolGenerationResultContributionProps extends ToolContributionProps {
177
+ readonly job: GenerationJob;
178
+ readonly result: unknown;
179
+ }
180
+
181
+ /**
182
+ * Required named export for `generation.result` contributions. A single poll
183
+ * callable may serve many native operations, so presentation selection is
184
+ * based on the durable job and the provider's unmodified poll result rather
185
+ * than registration order or a host-owned media taxonomy.
186
+ */
187
+ export type ToolGenerationResultContributionMatch = (
188
+ job: GenerationJob,
189
+ result: unknown,
190
+ ) => boolean;
191
+
34
192
  /** Optional named export required by `selection.inspector` contributions. */
35
193
  export type ToolInspectorContributionMatch = (
36
194
  node: ToolContributionNode | null,
@@ -0,0 +1,223 @@
1
+ import type { AssetKind, EditorCameraState, EditorView, ViewPreset } from './types.js';
2
+
3
+ const PREFIX = 'view.';
4
+ const MAX_QUERY_LENGTH = 4096;
5
+ const ASSET_KINDS = new Set<AssetKind>([
6
+ 'model',
7
+ 'image',
8
+ 'audio',
9
+ 'animation',
10
+ 'json',
11
+ 'scene',
12
+ 'prefab',
13
+ 'material',
14
+ ]);
15
+ const CAMERAS = new Set<ViewPreset | 'isometric'>([
16
+ 'top',
17
+ 'front',
18
+ 'right',
19
+ 'perspective',
20
+ 'isometric',
21
+ ]);
22
+ type EditorViewDiagnostic = NonNullable<NonNullable<EditorView['viewport']>['diagnostic']>;
23
+ type EditorViewUtility = NonNullable<EditorView['utility']>;
24
+ const DIAGNOSTICS = new Set<EditorViewDiagnostic>([
25
+ 'solid',
26
+ 'unlit',
27
+ 'wireframe',
28
+ 'normals',
29
+ 'overdraw',
30
+ 'uv',
31
+ 'vertex-colors',
32
+ 'bounds',
33
+ 'skeleton',
34
+ ]);
35
+ const UTILITIES = new Set<EditorViewUtility>(['profiler', 'console', 'animation', 'debugger']);
36
+
37
+ function nonEmpty(value: string | null): string | null {
38
+ const trimmed = value?.trim();
39
+ return trimmed ? trimmed : null;
40
+ }
41
+
42
+ function writeDocument(params: URLSearchParams, document: EditorView['document']): void {
43
+ if (!document) return;
44
+ params.set(`${PREFIX}docKind`, document.kind);
45
+ if (document.kind === 'asset' && document.entityId) {
46
+ params.set(`${PREFIX}doc`, document.entityId);
47
+ params.set(`${PREFIX}assetSource`, 'entity');
48
+ } else if (document.kind === 'asset') {
49
+ params.set(`${PREFIX}doc`, document.path!);
50
+ } else if (document.kind === 'scene' || document.kind === 'data') {
51
+ params.set(`${PREFIX}doc`, document.path);
52
+ } else if (document.kind === 'story') {
53
+ params.set(`${PREFIX}doc`, document.modulePath);
54
+ params.set(`${PREFIX}story`, document.storyName);
55
+ if (document.mode === 'docs') params.set(`${PREFIX}storyMode`, 'docs');
56
+ } else if (document.kind === 'project-tool') {
57
+ params.set(`${PREFIX}doc`, document.name);
58
+ } else {
59
+ params.set(`${PREFIX}doc`, document.id);
60
+ }
61
+ if (document.kind === 'asset' && document.assetKind) {
62
+ params.set(`${PREFIX}assetKind`, document.assetKind);
63
+ }
64
+ }
65
+
66
+ function writeViewport(params: URLSearchParams, viewport: EditorView['viewport']): void {
67
+ if (!viewport) return;
68
+ if (typeof viewport.camera === 'string') params.set(`${PREFIX}camera`, viewport.camera);
69
+ else if (viewport.camera) {
70
+ params.set(`${PREFIX}camera`, 'pose');
71
+ params.set(
72
+ `${PREFIX}cameraPosition`,
73
+ [viewport.camera.position.x, viewport.camera.position.y, viewport.camera.position.z].join(
74
+ ',',
75
+ ),
76
+ );
77
+ params.set(
78
+ `${PREFIX}cameraTarget`,
79
+ [viewport.camera.target.x, viewport.camera.target.y, viewport.camera.target.z].join(','),
80
+ );
81
+ if (viewport.camera.fov !== undefined)
82
+ params.set(`${PREFIX}cameraFov`, String(viewport.camera.fov));
83
+ }
84
+ if (viewport.diagnostic) params.set(`${PREFIX}diagnostic`, viewport.diagnostic);
85
+ if (viewport.frame) params.set(`${PREFIX}frame`, viewport.frame);
86
+ if (viewport.grid !== undefined) params.set(`${PREFIX}grid`, viewport.grid ? '1' : '0');
87
+ }
88
+
89
+ function parseVec3(value: string | null): EditorCameraState['position'] | null {
90
+ if (!value) return null;
91
+ const parts = value.split(',').map(Number);
92
+ if (parts.length !== 3 || parts.some((part) => !Number.isFinite(part))) return null;
93
+ return { x: parts[0]!, y: parts[1]!, z: parts[2]! };
94
+ }
95
+
96
+ function parseCamera(params: URLSearchParams): NonNullable<EditorView['viewport']>['camera'] {
97
+ const camera = params.get(`${PREFIX}camera`);
98
+ if (camera && CAMERAS.has(camera as ViewPreset | 'isometric')) {
99
+ return camera as ViewPreset | 'isometric';
100
+ }
101
+ if (camera !== 'pose') return undefined;
102
+ const position = parseVec3(params.get(`${PREFIX}cameraPosition`));
103
+ const target = parseVec3(params.get(`${PREFIX}cameraTarget`));
104
+ const fovValue = params.get(`${PREFIX}cameraFov`);
105
+ const fov = fovValue === null ? undefined : Number(fovValue);
106
+ if (!position || !target || (fov !== undefined && !Number.isFinite(fov))) return undefined;
107
+ return { position, target, ...(fov !== undefined ? { fov } : {}) };
108
+ }
109
+
110
+ function parseStoryDocument(params: URLSearchParams, modulePath: string): EditorView['document'] {
111
+ const storyName = nonEmpty(params.get(`${PREFIX}story`));
112
+ if (!storyName) return undefined;
113
+ return {
114
+ kind: 'story',
115
+ modulePath,
116
+ storyName,
117
+ ...(params.get(`${PREFIX}storyMode`) === 'docs' ? { mode: 'docs' as const } : {}),
118
+ };
119
+ }
120
+
121
+ function parseAssetDocument(params: URLSearchParams, value: string): EditorView['document'] {
122
+ const assetKind = params.get(`${PREFIX}assetKind`);
123
+ if (params.get(`${PREFIX}assetSource`) === 'entity') {
124
+ return { kind: 'asset', entityId: value, assetKind: 'model' };
125
+ }
126
+ return {
127
+ kind: 'asset',
128
+ path: value,
129
+ ...(assetKind && ASSET_KINDS.has(assetKind as AssetKind)
130
+ ? { assetKind: assetKind as AssetKind }
131
+ : {}),
132
+ };
133
+ }
134
+
135
+ function parseDocument(params: URLSearchParams): EditorView['document'] {
136
+ const kind = params.get(`${PREFIX}docKind`);
137
+ const value = nonEmpty(params.get(`${PREFIX}doc`));
138
+ if (!value) return undefined;
139
+ if (kind === 'scene') return { kind, path: value };
140
+ if (kind === 'tool') return { kind, id: value };
141
+ if (kind === 'world') return { kind, id: value };
142
+ if (kind === 'data') return { kind, path: value };
143
+ if (kind === 'project-tool') return { kind, name: value };
144
+ if (kind === 'generation') return { kind, id: value };
145
+ if (kind === 'story') return parseStoryDocument(params, value);
146
+ if (
147
+ kind === 'workspace' &&
148
+ [
149
+ 'workspace:scene',
150
+ 'workspace:game',
151
+ 'workspace:data-assets',
152
+ 'workspace:build-profiles',
153
+ 'account',
154
+ 'project-tools',
155
+ ].includes(value)
156
+ ) {
157
+ return {
158
+ kind,
159
+ id: value as Extract<NonNullable<EditorView['document']>, { kind: 'workspace' }>['id'],
160
+ };
161
+ }
162
+ return kind === 'asset' ? parseAssetDocument(params, value) : undefined;
163
+ }
164
+
165
+ function parseViewport(params: URLSearchParams): EditorView['viewport'] {
166
+ const camera = parseCamera(params);
167
+ const diagnostic = params.get(`${PREFIX}diagnostic`);
168
+ const frame = params.get(`${PREFIX}frame`);
169
+ const grid = params.get(`${PREFIX}grid`);
170
+ const viewport = {
171
+ ...(camera ? { camera } : {}),
172
+ ...(diagnostic && DIAGNOSTICS.has(diagnostic as EditorViewDiagnostic)
173
+ ? { diagnostic: diagnostic as EditorViewDiagnostic }
174
+ : {}),
175
+ ...(frame === 'document' || frame === 'selection'
176
+ ? { frame: frame as 'document' | 'selection' }
177
+ : {}),
178
+ ...(grid === '0' || grid === '1' ? { grid: grid === '1' } : {}),
179
+ };
180
+ return Object.keys(viewport).length > 0 ? viewport : undefined;
181
+ }
182
+
183
+ /** Apply only the view-owned query parameters, preserving project/scene routing params. */
184
+ export function editorViewUrl(view: EditorView, baseUrl: string | URL): string {
185
+ const url = new URL(baseUrl);
186
+ for (const key of [...url.searchParams.keys()]) {
187
+ if (key.startsWith(PREFIX)) url.searchParams.delete(key);
188
+ }
189
+ url.searchParams.set(`${PREFIX}v`, '1');
190
+ writeDocument(url.searchParams, view.document);
191
+ if (view.selection?.ids.length) {
192
+ for (const id of view.selection.ids) url.searchParams.append(`${PREFIX}select`, id);
193
+ if (view.selection.focus) url.searchParams.set(`${PREFIX}focus`, '1');
194
+ }
195
+ writeViewport(url.searchParams, view.viewport);
196
+ if (view.utility) url.searchParams.set(`${PREFIX}utility`, view.utility);
197
+ if (url.search.length > MAX_QUERY_LENGTH) {
198
+ throw new Error(`Editor view URL exceeds the ${MAX_QUERY_LENGTH}-character share limit.`);
199
+ }
200
+ return url.toString();
201
+ }
202
+
203
+ /** Parse a shareable editor projection. Malformed projections are ignored at boot. */
204
+ export function editorViewFromUrl(value: string | URL): EditorView | null {
205
+ const url = value instanceof URL ? value : new URL(value, 'http://editor.invalid');
206
+ const params = url.searchParams;
207
+ if (params.get(`${PREFIX}v`) !== '1') return null;
208
+ const view: EditorView = { version: 1 };
209
+ const document = parseDocument(params);
210
+ if (document) view.document = document;
211
+ const ids = params
212
+ .getAll(`${PREFIX}select`)
213
+ .filter((id) => id.length > 0)
214
+ .slice(0, 32);
215
+ if (ids.length) view.selection = { ids, focus: params.get(`${PREFIX}focus`) === '1' };
216
+ const viewport = parseViewport(params);
217
+ if (viewport) view.viewport = viewport;
218
+ const utility = params.get(`${PREFIX}utility`);
219
+ if (utility && UTILITIES.has(utility as EditorViewUtility)) {
220
+ view.utility = utility as EditorViewUtility;
221
+ }
222
+ return view;
223
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Extension contract (W3a) — the published surface a game project uses to
3
+ * contribute to the editor, with the degradation ladder made API.
4
+ *
5
+ * A project extends the editor through exactly four surfaces:
6
+ *
7
+ * (a) **Editor panels** — registered into the Dockview workspace as documents
8
+ * or utilities via a tool contribution (`package.json#vgai.tools` →
9
+ * `contributes: [{ point: 'workspace.document' | 'workspace.utility' }]`).
10
+ * Types: `ToolContributionProps` in `@vgai/editor-sdk/contributions`.
11
+ * Panels are never a parallel rail — Dockview owns all layout.
12
+ * (b) **Inspector sections** — `point: 'selection.inspector'` /
13
+ * `'asset.inspector'` tool contributions with an exported `match`.
14
+ * Types: `ToolInspectorContributionProps` and friends, same module.
15
+ * (c) **Per-component viewport gizmos** (this module) — a `GameComponent`
16
+ * subclass declares `static editorGizmo: ComponentGizmoHook`; the editor
17
+ * renders its objects for every scene entity carrying that component,
18
+ * on the editor-only layer, alongside the built-in gizmos.
19
+ * (d) **System adapters** — runtime capabilities (networking, debug, …)
20
+ * registered from game code via `ctx.registerSystemAdapter?.(kind, impl)`
21
+ * (`SystemAdapters` in `@vgai/engine`'s `runtime/types` /
22
+ * `adapter/system-adapter`). Deliberately NOT re-exported here: the
23
+ * engine already publishes that seam and every consumer of it also
24
+ * imports the engine — an alias would be a dead surface.
25
+ *
26
+ * Degradation ladder (the anti-shim rule, as API): every contribution
27
+ * resolves to a {@link ExtensionContributionTier} —
28
+ *
29
+ * - `'active'` — the contribution loaded and produced its surface.
30
+ * - `'absent'` — nothing was contributed. The editor HIDES the surface
31
+ * entirely; it never fabricates placeholder data for a missing
32
+ * contribution.
33
+ * - `'failed'` — the contribution exists but threw / violated the contract.
34
+ * The failure is contained per-contribution (the editor never crashes),
35
+ * the surface is hidden, and the error is reported LOUDLY (editor
36
+ * console) — never silently swallowed into fake output.
37
+ */
38
+
39
+ import type * as THREE from 'three';
40
+
41
+ /**
42
+ * Capability tier of a single extension contribution — the degradation
43
+ * ladder made API (see the module doc above for the exact semantics).
44
+ */
45
+ export type ExtensionContributionTier = 'active' | 'absent' | 'failed';
46
+
47
+ /** Resolved status of one per-component gizmo contribution. */
48
+ export interface ComponentGizmoStatus {
49
+ tier: ExtensionContributionTier;
50
+ /** Present only for `'failed'` — the contained error's message. */
51
+ error?: string;
52
+ }
53
+
54
+ /**
55
+ * What a component gizmo hook receives. Draw with `ctx.three` — the EDITOR's
56
+ * three module — so returned objects render in the editor viewport
57
+ * regardless of which physical `three` copy the project itself resolves.
58
+ */
59
+ export interface ComponentGizmoContext {
60
+ /** The component's registry name (the key in `entity.components`). */
61
+ componentName: string;
62
+ /**
63
+ * The entity's authored component data. When the component class declares
64
+ * a `static schema` (Zod), this is the schema-parsed value — defaults
65
+ * applied, invalid data already rejected (a parse failure fails the
66
+ * contribution loudly instead of fabricating defaults).
67
+ */
68
+ props: Record<string, unknown>;
69
+ /** The editor's `three` module — build all gizmo objects with this. */
70
+ three: typeof THREE;
71
+ }
72
+
73
+ /**
74
+ * Per-component viewport gizmo contribution (critique O11 — the
75
+ * `OnDrawGizmos` half of the extension contract). Declared as a STATIC on a
76
+ * `GameComponent` subclass:
77
+ *
78
+ * ```ts
79
+ * import type { ComponentGizmoHook } from '@vgai/editor-sdk/extension';
80
+ *
81
+ * export class TriggerZone extends GameComponent {
82
+ * static schema = z.object({ radius: z.number().default(2) });
83
+ * static editorGizmo: ComponentGizmoHook = ({ props, three }) => {
84
+ * const ring = new three.LineLoop(circleGeometry(props.radius as number),
85
+ * new three.LineBasicMaterial({ color: 0x44ff88 }));
86
+ * return ring;
87
+ * };
88
+ * // …
89
+ * }
90
+ * ```
91
+ *
92
+ * Contract:
93
+ * - Runs at DESIGN TIME in the editor (never in the running game). Called
94
+ * whenever the editor (re)builds gizmos for an entity carrying the
95
+ * component; treat it as pure — read `props`, return fresh objects.
96
+ * - Returned objects are parented to the entity's `Object3D` (local space),
97
+ * tagged as editor helpers on the editor-only render layer, excluded from
98
+ * raycast selection, and toggled by the viewport's Helpers ▸ Components
99
+ * filter. The editor disposes them with the entity's other gizmos.
100
+ * - Return `null` (or an empty array) to draw nothing for this entity.
101
+ * - Draw-only, v1: gizmos visualize; they do not edit. Interactive handles
102
+ * stay editor-internal until a consumer needs them (no dead surface).
103
+ * - Throwing (or returning a non-Object3D) marks the contribution
104
+ * `'failed'`: contained, hidden, reported loudly. The editor never
105
+ * substitutes a fallback drawing.
106
+ */
107
+ export type ComponentGizmoHook = (
108
+ ctx: ComponentGizmoContext,
109
+ ) => THREE.Object3D | readonly THREE.Object3D[] | null;
package/src/index.ts CHANGED
@@ -1,11 +1,29 @@
1
+ export type {
2
+ GenerationBilling,
3
+ GenerationJob,
4
+ GenerationJobStatus,
5
+ GenerationJobsDocument,
6
+ } from '@vgai/sdk/generations';
1
7
  export { EditorClient } from './client.js';
2
8
  export type {
9
+ ToolAssetInspectorContributionMatch,
10
+ ToolAssetInspectorContributionProps,
11
+ ToolContributionAsset,
3
12
  ToolContributionNode,
4
13
  ToolContributionProps,
14
+ ToolGenerationResultContributionProps,
5
15
  ToolInspectorContributionMatch,
6
16
  ToolInspectorContributionProps,
7
17
  } from './contributions.js';
18
+ export { editorViewFromUrl, editorViewUrl } from './editor-view.js';
19
+ export type {
20
+ ComponentGizmoContext,
21
+ ComponentGizmoHook,
22
+ ComponentGizmoStatus,
23
+ ExtensionContributionTier,
24
+ } from './extension.js';
8
25
  export type {
26
+ ActiveDocumentCapture,
9
27
  AssetKind,
10
28
  AssetPreviewBackground,
11
29
  AssetPreviewCapture,
@@ -15,8 +33,13 @@ export type {
15
33
  EditorCameraState,
16
34
  EditorEntitySummary,
17
35
  EditorState,
36
+ EditorView,
37
+ EditorViewDocument,
18
38
  GameCapture,
19
39
  HelperVisibility,
40
+ HumanoidShotLabel,
41
+ HumanoidShotSetCapture,
42
+ PresentedEditorView,
20
43
  ProjectInfo,
21
44
  ProjectTemplate,
22
45
  ProjectToolCatalog,
@@ -31,3 +54,4 @@ export type {
31
54
  ViewPreset,
32
55
  ViewportCapture,
33
56
  } from './types.js';
57
+ export { HUMANOID_SHOT_LABELS } from './types.js';
package/src/types.ts CHANGED
@@ -13,10 +13,14 @@ export interface HelperVisibility {
13
13
  lights: boolean;
14
14
  cameras: boolean;
15
15
  colliders: boolean;
16
+ joints: boolean;
17
+ lod: boolean;
16
18
  audio: boolean;
17
19
  splines: boolean;
18
20
  navmesh: boolean;
19
21
  skeletons: boolean;
22
+ /** Per-component gizmo contributions (W3a extension contract). */
23
+ components: boolean;
20
24
  }
21
25
 
22
26
  export interface Vec3Value {
@@ -84,6 +88,33 @@ export interface AssetPreviewCapture {
84
88
  contactSheet: ViewportCapture & { width: number; height: number };
85
89
  }
86
90
 
91
+ /**
92
+ * B7.6 — the canonical humanoid verify shot set (`vgai asset-preview --shots
93
+ * humanoid`): four turntable angles, tight zooms on the historical seam
94
+ * sites (head/hands/feet/shoulder), and a deep-bend pose pair, all framed
95
+ * from the loaded GLB's OWN skeleton (see `packages/editor/src/asset-preview.ts`).
96
+ */
97
+ export const HUMANOID_SHOT_LABELS = [
98
+ 'front',
99
+ 'back',
100
+ 'left',
101
+ 'right',
102
+ 'zoom-head',
103
+ 'zoom-hands',
104
+ 'zoom-feet',
105
+ 'zoom-shoulder',
106
+ 'bend-front',
107
+ 'bend-quarter',
108
+ ] as const;
109
+ export type HumanoidShotLabel = (typeof HUMANOID_SHOT_LABELS)[number];
110
+
111
+ export interface HumanoidShotSetCapture {
112
+ width: number;
113
+ height: number;
114
+ shots: Array<ViewportCapture & { label: HumanoidShotLabel }>;
115
+ contactSheet: ViewportCapture & { width: number; height: number };
116
+ }
117
+
87
118
  export interface EditorState {
88
119
  playState: 'stopped' | 'playing' | 'paused';
89
120
  /**
@@ -97,9 +128,21 @@ export interface EditorState {
97
128
  * or against an older server that predates this field.
98
129
  */
99
130
  loopLiveness?: 'running' | 'hidden-paused' | 'stopped' | null;
131
+ /**
132
+ * R1 (docs/R3F-FOLLOW-THROUGH-SPEC.md) — the pending-restart reason when
133
+ * source changed while the game was RUNNING and the running session is now
134
+ * stale (e.g. an R3F entry-file write-back during play, a registry.ts
135
+ * edit). The editor's Restart button surfaces the same reason; one restart
136
+ * (`vgai play`, or the button) remounts every root from fresh source and
137
+ * clears it. `null` when the running session is fresh; absent against an
138
+ * older server that predates the field.
139
+ */
140
+ restartRequired?: string | null;
100
141
  selectedEntityId: string | null;
101
142
  selectedEntityIds: string[];
102
143
  activeViewportTab: 'scene' | 'game';
144
+ /** The actual active center document, including tool/source documents. */
145
+ activeDocumentId?: string | null;
103
146
  activeTabKey: string;
104
147
  showGrid: boolean;
105
148
  showHelpers: boolean;
@@ -172,6 +215,69 @@ export type ShadingMode = 'solid' | 'unlit' | 'wireframe' | 'normals' | 'overdra
172
215
  export type TransformMode = 'translate' | 'rotate' | 'scale';
173
216
  export type TransformSpace = 'world' | 'local';
174
217
 
218
+ /**
219
+ * A durable, intentionally small projection of what an editor is presenting.
220
+ * This is not workspace persistence: panel sizes, transient tool state, and
221
+ * authored document contents remain outside the URL.
222
+ */
223
+ export type EditorViewDocument =
224
+ | { kind: 'scene'; path: string }
225
+ | { kind: 'asset'; path: string; entityId?: never; assetKind?: AssetKind }
226
+ | { kind: 'asset'; entityId: string; path?: never; assetKind?: 'model' }
227
+ | { kind: 'tool'; id: string }
228
+ | { kind: 'world'; id: string }
229
+ | { kind: 'story'; modulePath: string; storyName: string; mode?: 'preview' | 'docs' }
230
+ | { kind: 'data'; path: string }
231
+ | { kind: 'project-tool'; name: string }
232
+ | { kind: 'generation'; id: string }
233
+ | {
234
+ kind: 'workspace';
235
+ id:
236
+ | 'workspace:scene'
237
+ | 'workspace:game'
238
+ | 'workspace:data-assets'
239
+ | 'workspace:build-profiles'
240
+ | 'account'
241
+ | 'project-tools';
242
+ };
243
+
244
+ export interface EditorView {
245
+ version: 1;
246
+ document?: EditorViewDocument;
247
+ selection?: { ids: string[]; focus?: boolean };
248
+ viewport?: {
249
+ camera?: ViewPreset | 'isometric' | EditorCameraState;
250
+ diagnostic?: ShadingMode | 'uv' | 'vertex-colors' | 'bounds' | 'skeleton';
251
+ frame?: 'document' | 'selection';
252
+ grid?: boolean;
253
+ };
254
+ utility?: 'profiler' | 'console' | 'animation' | 'debugger';
255
+ }
256
+
257
+ export interface PresentedEditorView {
258
+ view: EditorView;
259
+ /** Shareable URL for the durable projection that was applied. */
260
+ url: string;
261
+ /** Honest degradations; an unsupported requested view is never silent. */
262
+ warnings: string[];
263
+ }
264
+
265
+ /** The pixels of the active center document, with enough provenance for an
266
+ * agent to prove which user-visible subject it captured. Editor chrome is
267
+ * deliberately excluded. */
268
+ export interface ActiveDocumentCapture extends ViewportCapture {
269
+ document: {
270
+ id: string;
271
+ title: string;
272
+ kind: string;
273
+ sourcePath?: string;
274
+ rootId?: string;
275
+ };
276
+ view: EditorView;
277
+ source: 'scene-viewport' | 'game-composite' | 'object3d-document' | 'document-composite';
278
+ layers?: { canvases: number; domOverlays: number };
279
+ }
280
+
175
281
  /** Template ids accepted by the editor's create-project endpoint. */
176
282
  export type ProjectTemplate = 'default' | '2d' | 'react' | 'example';
177
283
 
@@ -187,43 +293,8 @@ export interface RecentProject {
187
293
  thumbnail?: string;
188
294
  }
189
295
 
190
- export interface ProjectToolCatalogEntry {
191
- name: string;
192
- summary: string;
193
- description: string;
194
- sourcePath: string;
195
- inputSchema: unknown;
196
- resultSchema: unknown;
197
- errors: Array<{ code: string; summary: string; dataSchema?: unknown }>;
198
- requires: Record<string, boolean | undefined>;
199
- host: 'node' | 'editor-browser' | 'runtime-page';
200
- mutates: boolean;
201
- supportsDryRun: boolean;
202
- longRunning: boolean;
203
- permission: { risk: 'read' | 'write' | 'destructive'; summary: string };
204
- contributions: ProjectToolContribution[];
205
- }
206
-
207
- export type ToolContributionPoint =
208
- | 'workspace.document'
209
- | 'selection.inspector'
210
- | 'workspace.utility';
211
-
212
- export interface ProjectToolContribution {
213
- id: string;
214
- point: ToolContributionPoint;
215
- title: string;
216
- /** Project-relative or package-absolute browser module path. */
217
- entryPath: string;
218
- }
219
-
220
- export interface ProjectToolCatalog {
221
- tools: ProjectToolCatalogEntry[];
222
- loadErrors: Array<{ sourcePath: string; message: string }>;
223
- }
224
-
225
296
  export type ProjectToolOutcome =
226
- | { ok: true; data: unknown }
297
+ | { ok: true; data: unknown; generation?: GenerationJob; generationWarning?: string }
227
298
  | {
228
299
  ok: false;
229
300
  error: {
@@ -233,3 +304,12 @@ export type ProjectToolOutcome =
233
304
  issues?: Array<{ path?: PropertyKey[]; message?: string; [key: string]: unknown }>;
234
305
  };
235
306
  };
307
+
308
+ import type { GenerationJob } from '@vgai/sdk/generations';
309
+
310
+ export type {
311
+ ProjectToolCatalog,
312
+ ProjectToolCatalogEntry,
313
+ ProjectToolContribution,
314
+ ToolContributionPoint,
315
+ } from '@vgai/sdk/project-tool-catalog';