@volter/editor-sdk 0.5.57

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 (104) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +19 -0
  4. package/package.json +90 -0
  5. package/src/account.ts +210 -0
  6. package/src/chrome.ts +83 -0
  7. package/src/client.ts +1547 -0
  8. package/src/commands.ts +66 -0
  9. package/src/contributions.ts +985 -0
  10. package/src/document-probe.ts +237 -0
  11. package/src/editor-view.ts +220 -0
  12. package/src/extension.ts +40 -0
  13. package/src/generations.ts +178 -0
  14. package/src/host.ts +1167 -0
  15. package/src/http-transport.browser.ts +14 -0
  16. package/src/http-transport.node.ts +19 -0
  17. package/src/index.ts +128 -0
  18. package/src/layout-arrangements.ts +5 -0
  19. package/src/layouts.tsx +108 -0
  20. package/src/looks.ts +14 -0
  21. package/src/project/output-roots.ts +73 -0
  22. package/src/project/tab-census.ts +149 -0
  23. package/src/project-tool-catalog.ts +96 -0
  24. package/src/selection.tsx +108 -0
  25. package/src/services.ts +18 -0
  26. package/src/session/build-report.ts +19 -0
  27. package/src/session/collaboration-types.ts +262 -0
  28. package/src/session/command-table.ts +333 -0
  29. package/src/session/discovery.ts +90 -0
  30. package/src/session/editor-brand.ts +73 -0
  31. package/src/session/editor-compatibility.ts +248 -0
  32. package/src/session/editor-control-lifecycle.ts +68 -0
  33. package/src/session/editor-control-protocol.ts +5 -0
  34. package/src/session/entrypoint-selection-readers.ts +66 -0
  35. package/src/session/entrypoint-selection-source.ts +120 -0
  36. package/src/session/game-css-scope.ts +30 -0
  37. package/src/session/product-create.ts +24 -0
  38. package/src/session/product-locator.ts +389 -0
  39. package/src/session/project-module-url.ts +245 -0
  40. package/src/session/registry-format.ts +203 -0
  41. package/src/session/relative-path-guard.ts +56 -0
  42. package/src/session/source-glob.ts +15 -0
  43. package/src/session/tool-contribution-convention.ts +116 -0
  44. package/src/session/workbench-locator.ts +650 -0
  45. package/src/session.ts +41 -0
  46. package/src/share.ts +160 -0
  47. package/src/tools/errors.ts +91 -0
  48. package/src/tools/provider-execution.ts +70 -0
  49. package/src/tools/registry.ts +341 -0
  50. package/src/tools/types.ts +159 -0
  51. package/src/transport.ts +97 -0
  52. package/src/types.ts +1581 -0
  53. package/src/views.ts +164 -0
  54. package/src/widgets/design-system.ts +93 -0
  55. package/src/widgets/editor-appearance.ts +149 -0
  56. package/src/widgets/editor-material.ts +83 -0
  57. package/src/widgets/icon-set-registry.ts +105 -0
  58. package/src/widgets/index.ts +71 -0
  59. package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
  60. package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
  61. package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
  62. package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
  63. package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
  64. package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
  65. package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
  66. package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
  67. package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
  68. package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
  69. package/src/widgets/inspector-widgets/color-utils.ts +201 -0
  70. package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
  71. package/src/widgets/inspector-widgets/index.ts +24 -0
  72. package/src/widgets/inspector-widgets/shared.tsx +140 -0
  73. package/src/widgets/interactive-edit-scope.ts +33 -0
  74. package/src/widgets/patterns/Dialog.tsx +129 -0
  75. package/src/widgets/patterns/Fields.tsx +44 -0
  76. package/src/widgets/patterns/List.tsx +25 -0
  77. package/src/widgets/patterns/StateSurface.tsx +40 -0
  78. package/src/widgets/patterns/Surfaces.tsx +122 -0
  79. package/src/widgets/patterns/Tabs.tsx +80 -0
  80. package/src/widgets/patterns/Toolbar.tsx +72 -0
  81. package/src/widgets/patterns/Tree.tsx +72 -0
  82. package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
  83. package/src/widgets/primitives/Button.tsx +62 -0
  84. package/src/widgets/primitives/ColorInput.tsx +78 -0
  85. package/src/widgets/primitives/DraftTextInput.tsx +63 -0
  86. package/src/widgets/primitives/EditorIcon.tsx +157 -0
  87. package/src/widgets/primitives/FormControls.tsx +88 -0
  88. package/src/widgets/primitives/HoverPreview.tsx +96 -0
  89. package/src/widgets/primitives/JsonInput.tsx +113 -0
  90. package/src/widgets/primitives/Layout.tsx +100 -0
  91. package/src/widgets/primitives/Menu.tsx +140 -0
  92. package/src/widgets/primitives/NumberInput.tsx +169 -0
  93. package/src/widgets/primitives/Panel.tsx +80 -0
  94. package/src/widgets/primitives/SectionHeader.tsx +77 -0
  95. package/src/widgets/primitives/Text.tsx +54 -0
  96. package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
  97. package/src/widgets/primitives/Tooltip.tsx +204 -0
  98. package/src/widgets/primitives/Vec3Input.tsx +70 -0
  99. package/src/widgets/primitives/banner-tones.ts +32 -0
  100. package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
  101. package/src/widgets/primitives/editor-icons.ts +245 -0
  102. package/src/widgets/primitives/panel-header-styles.ts +42 -0
  103. package/src/widgets/theme.ts +2633 -0
  104. package/src/widgets/z-index.ts +25 -0
@@ -0,0 +1,341 @@
1
+ import type { z } from 'zod';
2
+ import {
3
+ CORE_ERROR_CODES,
4
+ isOperationError,
5
+ type StructuredOperationError,
6
+ toStructuredIssues,
7
+ } from './errors.js';
8
+ import {
9
+ type ExecutionHost,
10
+ type ExecutionRequirements,
11
+ type PermissionMetadata,
12
+ TOOL_NAMESPACES,
13
+ type ToolContext,
14
+ type ToolNamespace,
15
+ } from './types.js';
16
+
17
+ /** One declared, machine-readable failure mode of an operation (§8 B1: "structured error codes and data schemas"). */
18
+ export interface ToolErrorDefinition<TCode extends string = string> {
19
+ code: TCode;
20
+ /** One-line human summary of when this code fires — for docs/help text, never parsed by callers. */
21
+ summary: string;
22
+ /** Optional schema for this code's `data` payload. When present, `dispatch()` validates a thrown ToolError's data against it. */
23
+ data?: z.ZodType;
24
+ }
25
+
26
+ /**
27
+ * One operation, fully self-describing: identity, both schemas, every
28
+ * declared failure mode, where/how it runs, and its own implementation.
29
+ * Built via `defineTool` (below), which validates the name shape and
30
+ * error-code uniqueness at definition time.
31
+ */
32
+ export interface ToolDefinition<
33
+ TInput extends z.ZodType = z.ZodType,
34
+ TResult extends z.ZodType = z.ZodType,
35
+ TErrorCode extends string = string,
36
+ > {
37
+ /** Fully qualified dotted name, e.g. "project.scene.read" (§5.7 namespaces). */
38
+ name: string;
39
+ /** Short, one-line summary (for CLI help / listOperations tables). */
40
+ summary: string;
41
+ /** Longer prose description of behavior, side effects, and caveats. */
42
+ description: string;
43
+ /** Zod schema every `dispatch()` input is validated against before `impl` runs. */
44
+ input: TInput;
45
+ /** Zod schema every `impl` return value is validated against before `dispatch()` succeeds. */
46
+ result: TResult;
47
+ /** Every structured failure mode this operation may raise via `ToolError`. */
48
+ errors: ReadonlyArray<ToolErrorDefinition<TErrorCode>>;
49
+ /** Which live contexts this operation needs (project/editor/play/render). */
50
+ requires: ExecutionRequirements;
51
+ /** Which of the three hosts this operation executes on (node / editor-browser / runtime-page). */
52
+ host: ExecutionHost;
53
+ /** True if this operation writes/changes state (files, editor, runtime). */
54
+ mutates: boolean;
55
+ /** True if this operation supports a dry-run mode (mutations only, meaningful subset). */
56
+ supportsDryRun: boolean;
57
+ /** True for jobs that must not ride a short request/response timeout (e.g. `cinematic.render` on the editor relay). */
58
+ longRunning?: boolean;
59
+ /** Coarse permission/risk metadata for gated callers (agents, HTTP/MCP auth). */
60
+ permission: PermissionMetadata;
61
+ /** The actual implementation. Receives already-schema-validated input. */
62
+ impl: (input: z.infer<TInput>, ctx: ToolContext) => Promise<z.infer<TResult>>;
63
+ }
64
+
65
+ /** `listOperations()`'s element shape — every field of `ToolDefinition` except `impl`, so enumerating never risks invoking anything. */
66
+ export type ToolSummary<
67
+ TInput extends z.ZodType = z.ZodType,
68
+ TResult extends z.ZodType = z.ZodType,
69
+ TErrorCode extends string = string,
70
+ > = Omit<ToolDefinition<TInput, TResult, TErrorCode>, 'impl'>;
71
+
72
+ const NAME_PATTERN = /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)+$/;
73
+
74
+ function namespaceOf(name: string): string {
75
+ const dot = name.indexOf('.');
76
+ return dot === -1 ? name : name.slice(0, dot);
77
+ }
78
+
79
+ /**
80
+ * Typed helper that builds an `ToolDefinition`. Pure and synchronous —
81
+ * it does not touch a registry (so it can never itself throw "duplicate
82
+ * name"; that check happens at `ToolRegistry.register`, which has the
83
+ * cross-operation state to detect it) — but it DOES validate the two things
84
+ * that are decidable from the definition alone: the name is a valid dotted
85
+ * `namespace.rest` string under one of the four operation namespaces
86
+ * (§5.7), and no two declared error codes on the same operation collide.
87
+ *
88
+ * Exists mainly for type inference: it pins `impl`'s parameter/return types
89
+ * to `z.infer<TInput>` / `z.infer<TResult>` so a mismatched implementation
90
+ * fails to compile rather than failing at runtime.
91
+ */
92
+ export function defineTool<
93
+ TInput extends z.ZodType,
94
+ TResult extends z.ZodType,
95
+ TErrorCode extends string = string,
96
+ >(def: ToolDefinition<TInput, TResult, TErrorCode>): ToolDefinition<TInput, TResult, TErrorCode> {
97
+ if (!NAME_PATTERN.test(def.name)) {
98
+ throw new Error(
99
+ `defineTool: "${def.name}" is not a valid dotted operation name ` +
100
+ '(expected e.g. "project.scene.read" — lowercase-leading segments joined by dots).',
101
+ );
102
+ }
103
+ const ns = namespaceOf(def.name);
104
+ if (!(TOOL_NAMESPACES as readonly string[]).includes(ns)) {
105
+ throw new Error(
106
+ `defineTool: "${def.name}" has unknown namespace "${ns}" — expected one of ` +
107
+ `${TOOL_NAMESPACES.join(', ')} (§5.7).`,
108
+ );
109
+ }
110
+ const seen = new Set<string>();
111
+ for (const err of def.errors) {
112
+ if (seen.has(err.code)) {
113
+ throw new Error(`defineTool: "${def.name}" declares duplicate error code "${err.code}".`);
114
+ }
115
+ seen.add(err.code);
116
+ }
117
+ return def;
118
+ }
119
+
120
+ /** `dispatch()`'s result — a discriminated union, never a thrown exception, so every projection (CLI/HTTP/MCP) gets one uniform JSON-able shape for both success and failure. */
121
+ export type ToolOutcome<TResult = unknown> =
122
+ | { ok: true; data: TResult }
123
+ | { ok: false; error: StructuredOperationError };
124
+
125
+ /**
126
+ * Normalize whatever an `impl` threw into a `StructuredOperationError`.
127
+ * Three cases:
128
+ * 1. A declared `ToolError` whose code IS in `def.errors` and whose
129
+ * `data` (if the code declares a schema) validates — forwarded as-is,
130
+ * `data` replaced by its *parsed* form.
131
+ * 2. A declared code whose `data` fails its own schema — that is itself an
132
+ * implementation bug, surfaced as INVALID_OUTPUT (never silently
133
+ * forwarding unvalidated data).
134
+ * 3. Anything else — an `ToolError` with an undeclared code, a plain
135
+ * `Error`, or a non-Error throw — normalized into INTERNAL_ERROR. The
136
+ * raw exception/message is never used as the identifying `code`, but it
137
+ * IS carried in `message` as well as `data.message`: every projection
138
+ * (the CLI's `vgai tool`, the oclif commands, `vgai screenshot`'s module
139
+ * lane) shows `error.message` and only some of them dump `data`, so a
140
+ * `message` that said nothing but "threw an unstructured exception"
141
+ * hid the one sentence the caller needed ("No editor connected — open
142
+ * the editor in a browser tab, then retry.") behind whichever surface
143
+ * happened to print the whole outcome.
144
+ */
145
+ function normalizeThrown(def: ToolDefinition, err: unknown): StructuredOperationError {
146
+ if (isOperationError(err)) {
147
+ const declared = def.errors.find((e) => e.code === err.code);
148
+ if (!declared) {
149
+ return {
150
+ code: CORE_ERROR_CODES.INTERNAL_ERROR,
151
+ message: `"${def.name}" threw undeclared error code "${err.code}".`,
152
+ data: { undeclaredCode: err.code, message: err.message },
153
+ };
154
+ }
155
+ if (declared.data) {
156
+ const parsed = declared.data.safeParse(err.data);
157
+ if (!parsed.success) {
158
+ return {
159
+ code: CORE_ERROR_CODES.INVALID_OUTPUT,
160
+ message:
161
+ `"${def.name}" threw declared code "${err.code}" but its data failed that ` +
162
+ "code's own schema.",
163
+ issues: toStructuredIssues(parsed.error.issues),
164
+ };
165
+ }
166
+ return { code: err.code, message: err.message, data: parsed.data };
167
+ }
168
+ return {
169
+ code: err.code,
170
+ message: err.message,
171
+ ...(err.data !== undefined ? { data: err.data } : {}),
172
+ };
173
+ }
174
+
175
+ const message = err instanceof Error ? err.message : String(err);
176
+ return {
177
+ code: CORE_ERROR_CODES.INTERNAL_ERROR,
178
+ message: `"${def.name}" failed: ${message}`,
179
+ data: { message },
180
+ };
181
+ }
182
+
183
+ function toSummary(def: ToolDefinition): ToolSummary {
184
+ const { impl: _impl, ...summary } = def;
185
+ return summary;
186
+ }
187
+
188
+ /**
189
+ * Zod object schemas strip unknown keys by default. That is unsafe at an
190
+ * operation boundary: a misspelled `dryRun` could disappear and let the
191
+ * operation use its write-default. Detect anything parsing removed, at any
192
+ * nested object level, and reject it before the implementation can run.
193
+ */
194
+ function firstStrippedInputPath(
195
+ raw: unknown,
196
+ parsed: unknown,
197
+ path: Array<string | number> = [],
198
+ ): Array<string | number> | null {
199
+ if (Array.isArray(raw) && Array.isArray(parsed)) {
200
+ for (let index = 0; index < raw.length; index++) {
201
+ const stripped = firstStrippedInputPath(raw[index], parsed[index], [...path, index]);
202
+ if (stripped) return stripped;
203
+ }
204
+ return null;
205
+ }
206
+ if (
207
+ raw === null ||
208
+ parsed === null ||
209
+ typeof raw !== 'object' ||
210
+ typeof parsed !== 'object' ||
211
+ Array.isArray(raw) ||
212
+ Array.isArray(parsed)
213
+ ) {
214
+ return null;
215
+ }
216
+ const parsedRecord = parsed as Record<string, unknown>;
217
+ for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
218
+ if (!Object.hasOwn(parsedRecord, key)) return [...path, key];
219
+ const stripped = firstStrippedInputPath(value, parsedRecord[key], [...path, key]);
220
+ if (stripped) return stripped;
221
+ }
222
+ return null;
223
+ }
224
+
225
+ /**
226
+ * The one registry of operation definitions (§8 B1). Holds definitions
227
+ * keyed by their fully-qualified name; `register` rejects a duplicate name
228
+ * outright (names are unique and stable per the AC), `listOperations`
229
+ * enumerates metadata without ever touching `impl`, and `dispatch` is the
230
+ * single validated call path: input schema -> impl -> result schema, with
231
+ * every failure normalized into `StructuredOperationError`.
232
+ */
233
+ export class ToolRegistry {
234
+ private readonly definitions = new Map<string, ToolDefinition>();
235
+
236
+ /** Register a definition. Throws synchronously on a duplicate name — names are unique and stable by construction, not by convention. */
237
+ register<TInput extends z.ZodType, TResult extends z.ZodType, TErrorCode extends string>(
238
+ def: ToolDefinition<TInput, TResult, TErrorCode>,
239
+ ): void {
240
+ if (this.definitions.has(def.name)) {
241
+ throw new Error(
242
+ `ToolRegistry.register: "${def.name}" is already registered — operation names ` +
243
+ 'must be unique and stable.',
244
+ );
245
+ }
246
+ this.definitions.set(def.name, def as unknown as ToolDefinition);
247
+ }
248
+
249
+ /** Enumerate every registered operation's metadata. Never invokes `impl`. */
250
+ listOperations(): ToolSummary[] {
251
+ return [...this.definitions.values()].map(toSummary);
252
+ }
253
+
254
+ /** Look up one operation's full definition (including `impl`) by name, or `undefined`. */
255
+ getOperation(name: string): ToolDefinition | undefined {
256
+ return this.definitions.get(name);
257
+ }
258
+
259
+ has(name: string): boolean {
260
+ return this.definitions.has(name);
261
+ }
262
+
263
+ /**
264
+ * Validate `input` against the named operation's input schema, run its
265
+ * `impl`, validate the return value against its result schema, and return
266
+ * a uniform `ToolOutcome` — success or a `StructuredOperationError`.
267
+ * Never throws for an expected failure (unknown name, bad input, impl
268
+ * throw, bad output); those are exactly what this method exists to turn
269
+ * into a machine-readable result instead of an exception a caller has to
270
+ * parse prose out of.
271
+ */
272
+ async dispatch<TResult = unknown>(
273
+ name: string,
274
+ input: unknown,
275
+ ctx: ToolContext = {},
276
+ ): Promise<ToolOutcome<TResult>> {
277
+ const def = this.definitions.get(name);
278
+ if (!def) {
279
+ return {
280
+ ok: false,
281
+ error: {
282
+ code: CORE_ERROR_CODES.OPERATION_NOT_FOUND,
283
+ message: `No operation is registered as "${name}".`,
284
+ data: { name },
285
+ },
286
+ };
287
+ }
288
+
289
+ const parsedInput = def.input.safeParse(input);
290
+ if (!parsedInput.success) {
291
+ return {
292
+ ok: false,
293
+ error: {
294
+ code: CORE_ERROR_CODES.INVALID_INPUT,
295
+ message: `Input for "${name}" failed schema validation.`,
296
+ issues: toStructuredIssues(parsedInput.error.issues),
297
+ },
298
+ };
299
+ }
300
+ const strippedPath = firstStrippedInputPath(input, parsedInput.data);
301
+ if (strippedPath) {
302
+ return {
303
+ ok: false,
304
+ error: {
305
+ code: CORE_ERROR_CODES.INVALID_INPUT,
306
+ message: `Input for "${name}" contains an unknown field.`,
307
+ issues: [
308
+ {
309
+ path: strippedPath,
310
+ message: 'Unknown input field. Check spelling; unknown fields are never ignored.',
311
+ code: 'unrecognized_key',
312
+ },
313
+ ],
314
+ },
315
+ };
316
+ }
317
+
318
+ let rawResult: unknown;
319
+ try {
320
+ rawResult = await def.impl(parsedInput.data, ctx);
321
+ } catch (err) {
322
+ return { ok: false, error: normalizeThrown(def, err) };
323
+ }
324
+
325
+ const parsedResult = def.result.safeParse(rawResult);
326
+ if (!parsedResult.success) {
327
+ return {
328
+ ok: false,
329
+ error: {
330
+ code: CORE_ERROR_CODES.INVALID_OUTPUT,
331
+ message: `Result of "${name}" failed schema validation (implementation bug).`,
332
+ issues: toStructuredIssues(parsedResult.error.issues),
333
+ },
334
+ };
335
+ }
336
+
337
+ return { ok: true, data: parsedResult.data as TResult };
338
+ }
339
+ }
340
+
341
+ export type { ExecutionHost, ExecutionRequirements, ToolContext, ToolNamespace };
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Shared vocabulary for the operation registry (B1).
3
+ */
4
+
5
+ /** The four namespaces every operation name must live under (§5.7). */
6
+ export const TOOL_NAMESPACES = ['project', 'editor', 'play', 'cinematic'] as const;
7
+ export type ToolNamespace = (typeof TOOL_NAMESPACES)[number];
8
+
9
+ /**
10
+ * Which of the three hosts an operation executes on (§8 B1, the
11
+ * review-hardened addition):
12
+ * - `node` — runs in the SDK's own Node process against files (or, for
13
+ * long-running jobs like `cinematic.render`, launches its own browser).
14
+ * - `editor-browser` — invoked through the existing
15
+ * `POST /__editor/command` SSE relay in
16
+ * `packages/editor/server/editor-server.ts`, which broadcasts to the
17
+ * connected browser editor and awaits its callback under a short timeout
18
+ * (~5s; 120s for `play`). The projection generates the relay stub.
19
+ * - `runtime-page` — invoked through the play/render harness in a launched
20
+ * runtime page.
21
+ *
22
+ * `cinematic.render` is pinned `node` and MUST NOT ride the editor relay's
23
+ * short timeout — it is a long-running job (see `longRunning` on
24
+ * `ToolDefinition`).
25
+ */
26
+ export type ExecutionHost = 'node' | 'editor-browser' | 'runtime-page';
27
+
28
+ /** Which live contexts an operation needs before it can run. */
29
+ export interface ExecutionRequirements {
30
+ /** Needs a project directory on disk (no editor process required). */
31
+ project?: boolean;
32
+ /** Needs an existing, connected editor session. */
33
+ editor?: boolean;
34
+ /** Needs a playable runtime (starts or targets one). */
35
+ play?: boolean;
36
+ /** Needs a controlled render runtime. */
37
+ render?: boolean;
38
+ }
39
+
40
+ /** Coarse risk classification surfaced to permission-gated callers (agents, HTTP/MCP auth). */
41
+ export type PermissionRisk = 'read' | 'write' | 'destructive';
42
+
43
+ export interface PermissionMetadata {
44
+ risk: PermissionRisk;
45
+ /** Human summary of what this operation is permitted to touch or do. */
46
+ summary: string;
47
+ }
48
+
49
+ /** One completed file handed to the host-managed generated-output boundary. */
50
+ export interface ProjectOutputFile {
51
+ /** Project-relative destination. Slice 1 deliberately permits only public/**. */
52
+ path: string;
53
+ content: string | Uint8Array;
54
+ mediaType?: string;
55
+ role?: 'asset' | 'provenance' | 'other';
56
+ }
57
+
58
+ /** JSON-safe summary returned after an atomic generated-output transaction. */
59
+ export interface ProjectGeneratedOutputFile {
60
+ path: string;
61
+ bytes: number;
62
+ mediaType?: string;
63
+ role?: ProjectOutputFile['role'];
64
+ }
65
+
66
+ export interface ProjectGeneratedOutput {
67
+ files: ProjectGeneratedOutputFile[];
68
+ totalBytes: number;
69
+ dryRun: boolean;
70
+ /** Host-created logical record in .vgai/provenance.json; absent for dry runs. */
71
+ provenanceOperationId?: string;
72
+ }
73
+
74
+ export interface ProjectProviderExecution {
75
+ mode: 'mock' | 'direct' | 'managed';
76
+ provider: string;
77
+ operation?: string;
78
+ model?: string;
79
+ requestId?: string;
80
+ taskId?: string;
81
+ managedJobId?: string;
82
+ /** Route-aware charge snapshot when this execution produced accepted output. */
83
+ billing?: import('../generations.js').GenerationBilling;
84
+ }
85
+
86
+ /** Facts the operation host already owns and stamps onto every committed batch. */
87
+ export interface ProjectOutputProvenanceContext {
88
+ operationName: string;
89
+ operationSource?: string;
90
+ /** Sanitized facts for a single provider execution. */
91
+ execution?: ProjectProviderExecution;
92
+ /** Ordered facts for a native multi-task pipeline that produced one output batch. */
93
+ executions?: readonly ProjectProviderExecution[];
94
+ /** WHICH editor session produced the bytes, for a `project.session.write`
95
+ * batch — the kind whose producer is a script an agent ran inside a live
96
+ * session rather than an operation that ran here. Shape and meaning:
97
+ * `project/provenance.ts`'s `ProjectProvenanceSessionSchema`, which is the
98
+ * validated form; this mirror exists because `types.ts` carries no zod. */
99
+ session?: {
100
+ id: string;
101
+ port: number;
102
+ revision: number;
103
+ callId?: string;
104
+ };
105
+ /** Schema-validated, JSON-safe operation input. */
106
+ input?: unknown;
107
+ /** Project-relative paths of the project FILES these bytes were produced
108
+ * from — a session-written GLB names the `src/models/<name>.blend` it was
109
+ * exported from. Shape and meaning: `project/provenance.ts`'s `inputs`,
110
+ * which is the validated form; this mirror exists because `types.ts`
111
+ * carries no zod. It is what lets a reader drill DOWN by kind from a
112
+ * prefab's glTF to the model it came from. */
113
+ inputs?: readonly string[];
114
+ }
115
+
116
+ /**
117
+ * Host capability supplied only to Node-hosted project operations. Generator
118
+ * implementations may stay ordinary native JS; their thin operation wrapper
119
+ * calls this once it has a complete batch ready to commit.
120
+ */
121
+ export interface ProjectOutputWriter {
122
+ write(
123
+ files: readonly ProjectOutputFile[],
124
+ options?: { dryRun?: boolean },
125
+ ): Promise<ProjectGeneratedOutput>;
126
+ }
127
+
128
+ /**
129
+ * Runtime context passed to every operation's `impl`. Intentionally an open
130
+ * record (`[key: string]: unknown`) — later units (B2-B8) will grow this with
131
+ * concrete fields (editor session ids, play session handles, render job
132
+ * state); B1 only needs the two seams its sample operations touch.
133
+ */
134
+ export interface ToolContext {
135
+ /** Absolute path to the target project's root, for `project`-context ops. */
136
+ projectRoot?: string;
137
+ /** Base URL of a connected editor session, for `editor`-context ops. */
138
+ editorUrl?: string;
139
+ /** Cooperative cancellation for long-running (`node`, `longRunning`) ops. */
140
+ signal?: AbortSignal;
141
+ /** Atomic writer for generated project assets (Node project operations). */
142
+ projectOutputs?: ProjectOutputWriter;
143
+ /** WHICH mounted instance a game-driving tool should address, when the
144
+ * editor has several live (multiplayer authoring). A tool that drives the
145
+ * game binds `game.instance(ctx.instance)` from it; omitted means the sole
146
+ * live instance. */
147
+ instance?: string;
148
+ /** The HOST's project-module loader (the editor server's Vite SSR loader,
149
+ * wrapped with dependency-change invalidation). A tool that imports the
150
+ * project's own source at run time — `project.bake.preview` importing
151
+ * `src/models/barrel.ts` — MUST load through this rather than a raw
152
+ * `import()`: Node's ESM cache never invalidates, so a raw import returned
153
+ * the FIRST version of a model for the life of the server and every later
154
+ * edit re-rendered byte-identical until `vgai restart` (measured on the
155
+ * blind modeling bench, 2026-09-05: three restarts in one barrel). Absent
156
+ * only when the tool runs outside an editor server. */
157
+ loadProjectModule?: (absolutePath: string) => Promise<unknown>;
158
+ [key: string]: unknown;
159
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * THE STAGE TRANSPORT'S PUBLISHED TYPES — the vocabulary a contribution needs
3
+ * to attach something to a stage's transport and to draw a look over it.
4
+ *
5
+ * Declared HERE rather than beside the implementation
6
+ * (`@editor/animation/stage-transport`) so `@vgai/blender` — and any other
7
+ * skew package — reaches them through `@volter/editor-sdk/host` with no
8
+ * dependency on the editor's own source and no engine value import. The
9
+ * implementation imports these; nothing imports the implementation.
10
+ *
11
+ * TIME IS A POSITION ON FACTS A WORLD OWNS, AND A POSITION HAS A DRIVER
12
+ * (WORK.md §The stage transport and the animation door). In Edit, time is
13
+ * WRITTEN: nothing is running, and "show me frame 24" originates in the
14
+ * transport and is seeked down into the world. In Play, time is READ: the
15
+ * game's loop advances its own time and the editor may not write it — the
16
+ * transport's `driver` says which of the two is true, and the write verbs
17
+ * refuse by name while it answers `'world'`.
18
+ *
19
+ * SECONDS ARE THE SEAM. `fps` is a subject's DISPLAY rate, carried so a look
20
+ * that thinks in frames (Blender's Timeline) can convert at its own edge and
21
+ * nowhere else. Keys are deliberately NOT here: Blender's authored keys are
22
+ * the door's key COLUMNS rather than a per-frame bake, and its bone names
23
+ * break three's `PropertyBinding` grammar, so no honest common drawing
24
+ * projection exists — each look draws its own from its own door.
25
+ */
26
+
27
+ /**
28
+ * What a world attaches to a stage so the transport can show it at a time.
29
+ *
30
+ * A subject is the world's own object: `seek` calls the world's own code
31
+ * (Blender's `mixer.setTime` through the wire, three's `AnimationMixer`).
32
+ * The transport owns the position; the subject owns what that position MEANS.
33
+ */
34
+ export interface TransportSubject {
35
+ /** Stable for the lifetime of the attachment. Blender: the action name;
36
+ * three: the clip name (or the object's uuid when it carries several). */
37
+ readonly id: string;
38
+ readonly label: string;
39
+ /** The subject's own valid time domain and its display rate. */
40
+ range(): { start: number; end: number; fps: number };
41
+ /** THE ONE WRITE. Must be idempotent and synchronous: the transport calls
42
+ * it for every crossing, including a re-seek to the time already shown. */
43
+ seek(seconds: number): void;
44
+ /** Optional — which of several clips this subject is showing (three).
45
+ * Blender omits it: an action IS the subject there. */
46
+ clips?(): readonly { id: string; label: string }[];
47
+ setClip?(id: string): void;
48
+ }
49
+
50
+ export type TransportPlaybackState = 'stopped' | 'paused' | 'playing';
51
+
52
+ /** Everything a look needs to draw the transport, in one immutable read. */
53
+ export interface StageTransportSnapshot {
54
+ readonly time: number;
55
+ readonly range: { start: number; end: number; fps: number; loop: boolean };
56
+ readonly playbackState: TransportPlaybackState;
57
+ readonly timeScale: number;
58
+ readonly activeSubject: string | null;
59
+ readonly subjects: readonly { id: string; label: string }[];
60
+ /**
61
+ * `'world'` ⇔ this stage's store is in Play, so the game's own loop is
62
+ * advancing time and the editor did not cause it. Every write verb below
63
+ * refuses while this answers `'world'`; a look draws itself read-only and
64
+ * says why rather than disabling a button with no reason.
65
+ */
66
+ readonly driver: 'editor' | 'world';
67
+ }
68
+
69
+ /**
70
+ * The transport as a CONTRIBUTION sees it — the editor's own class minus its
71
+ * construction and disposal, which belong to the stage that owns it.
72
+ */
73
+ export interface StageTransportHandle {
74
+ snapshot(): StageTransportSnapshot;
75
+ subscribe(listener: () => void): () => void;
76
+ /** Attach a subject; the first attach becomes active. Returns detach. */
77
+ attach(subject: TransportSubject): () => void;
78
+ setActiveSubject(id: string): void;
79
+ /** `'reverse'` plays backwards — Blender's Timeline has a play-reverse
80
+ * button beside play, and the engine's clock has carried the direction all
81
+ * along (`AnimationClock.play(direction)`). */
82
+ play(direction?: 'forward' | 'reverse'): void;
83
+ pause(): void;
84
+ stop(): void;
85
+ seek(seconds: number): void;
86
+ seekFrame(frame: number): void;
87
+ setLoop(loop: boolean): void;
88
+ setTimeScale(scale: number): void;
89
+ /**
90
+ * Fires when the playhead SETTLES — on `pause()`, and at scrub-end (the
91
+ * first 150 ms with no further `seek` while not playing). A world with a
92
+ * persisted playhead (Blender's `scene.frame_current`) writes its bookmark
93
+ * here, so a slider drag writes once instead of once per frame. A `seek`
94
+ * while playing never settles.
95
+ */
96
+ onSettled(listener: (seconds: number) => void): () => void;
97
+ }