@womp/spark-agent 4.4.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.env.example +6 -0
  2. package/README.md +21 -2
  3. package/dist/capabilities/geometry.d.ts +0 -3
  4. package/dist/capabilities/geometry.js +7 -8
  5. package/dist/capabilities/openscad-instructions.d.ts +1 -1
  6. package/dist/capabilities/openscad-instructions.js +80 -81
  7. package/dist/capabilities/openscad.d.ts +0 -3
  8. package/dist/capabilities/openscad.js +1 -2
  9. package/dist/capabilities/scene.d.ts +4 -11
  10. package/dist/capabilities/scene.js +38 -55
  11. package/dist/generated/geometry-declarations.d.ts +1 -1
  12. package/dist/generated/geometry-declarations.js +1 -1
  13. package/dist/generated/openscad-declarations.d.ts +1 -1
  14. package/dist/generated/openscad-declarations.js +1 -1
  15. package/dist/generated/scene-declarations.d.ts +1 -1
  16. package/dist/generated/scene-declarations.js +1 -1
  17. package/dist/native-executor.js +2 -2
  18. package/dist/openscad.d.ts +1 -2
  19. package/dist/openscad.js +1 -19
  20. package/dist/quickjs-executor.js +1 -1
  21. package/dist/repair-state.d.ts +2 -0
  22. package/dist/repair-state.js +15 -0
  23. package/dist/runtime.d.ts +1 -5
  24. package/dist/runtime.js +8 -14
  25. package/dist/scene-environment.js +1 -1
  26. package/dist/scene-health.d.ts +1 -2
  27. package/dist/scene-health.js +0 -17
  28. package/dist/scene-operations.d.ts +5 -5
  29. package/dist/scene-operations.js +24 -43
  30. package/dist/sdk-geometry-host.d.ts +2 -2
  31. package/dist/sdk-geometry-host.js +44 -61
  32. package/dist/sdk-openscad-host.js +63 -102
  33. package/dist/sdk-scene-health-host.d.ts +2 -1
  34. package/dist/sdk-scene-health-host.js +54 -4
  35. package/dist/sdk-scene-host.d.ts +20 -5
  36. package/dist/sdk-scene-host.js +34 -31
  37. package/dist/sdk-scene-screenshot.d.ts +2 -2
  38. package/dist/sdk-scene-screenshot.js +3 -16
  39. package/dist/server/scene-access.d.ts +4 -0
  40. package/dist/server/scene-access.js +40 -0
  41. package/dist/server/standalone.js +24 -19
  42. package/dist/womp-scene-api.d.ts +15 -0
  43. package/dist/womp-scene-api.js +18 -1
  44. package/package.json +139 -139
  45. package/dist/server/scene-initialization.d.ts +0 -3
  46. package/dist/server/scene-initialization.js +0 -44
package/dist/runtime.js CHANGED
@@ -46,7 +46,7 @@ export class KakapoRuntime {
46
46
  this.eventSink(event);
47
47
  }
48
48
  async connect() {
49
- await this.sdkCall("connect", [], () => this.api.connect());
49
+ await this.sdkCall("connect", [], () => this.api.connect({ refreshScene: false }));
50
50
  this.fonts = await this.sdkCall("listFonts", [], () => this.api.listFonts());
51
51
  this.rootId = 0;
52
52
  }
@@ -135,31 +135,25 @@ export class KakapoRuntime {
135
135
  print,
136
136
  });
137
137
  }
138
- async inspect() {
139
- await this.api.refreshScene();
140
- return { revision: await this.api.getSceneRevision(), scene: this.api.getScene() };
141
- }
142
- async edit(callback, expectedRevision, context) {
138
+ async access(callback, context) {
143
139
  let changed = false;
144
140
  const ordinal = this.editOrdinals.get(context) ?? 0;
145
141
  this.editOrdinals.set(context, ordinal + 1);
146
142
  const receipt = {
147
- id: `${context.runId}:${context.toolCallId}:scene.edit:${ordinal}`,
143
+ id: `${context.runId}:${context.toolCallId}:scene.access:${ordinal}`,
148
144
  capability: "scene",
149
- operation: "edit",
145
+ operation: "access",
150
146
  };
151
147
  let value;
152
148
  try {
153
- value = await this.api.editScene(async (scene) => {
149
+ const accessed = await this.api.accessScene(async (scene) => {
154
150
  context.signal.throwIfAborted();
155
- if ((await this.api.getSceneRevision()) !== expectedRevision) {
156
- throw new Error("Scene changed since inspection. Read the scene again before editing.");
157
- }
158
151
  const result = await callback(this.sceneApi(scene, () => undefined));
159
152
  context.signal.throwIfAborted();
160
- changed = (await this.api.getSceneRevision()) !== expectedRevision;
161
153
  return result;
162
154
  });
155
+ value = accessed.value;
156
+ changed = accessed.changed;
163
157
  }
164
158
  catch (error) {
165
159
  await context.recordReceipt({
@@ -191,7 +185,7 @@ export class KakapoRuntime {
191
185
  error: (...values) => consoleFacade.log("ERROR", ...values),
192
186
  };
193
187
  try {
194
- const value = await this.api.editScene((scene) => {
188
+ const { value } = await this.api.accessScene((scene) => {
195
189
  const api = this.sceneApi(scene, (...values) => consoleFacade.log(...values));
196
190
  return this.executor.execute(code, { ...api, console: consoleFacade }, 60_000);
197
191
  });
@@ -32,7 +32,7 @@ export function createSceneEnvironment(scene, hdrs, print) {
32
32
  op: "add",
33
33
  path: `/${key}`,
34
34
  value,
35
- })));
35
+ })), { returnScene: false });
36
36
  };
37
37
  return {
38
38
  scenePropertiesRead: () => {
@@ -63,10 +63,9 @@ export declare function receiptSceneHealth(health: SceneHealth, limit?: number):
63
63
  * changes the one payload that is unbounded by construction and nothing else.
64
64
  */
65
65
  export declare function boundedReceiptDetails(details: Record<string, unknown>): Record<string, unknown>;
66
- export declare function getSdkSceneAreaId(api: KakapoAPI, objectId: number): string;
66
+ export declare function getSdkSceneAreaId(api: Pick<KakapoAPI, "getNode">, objectId: number): string;
67
67
  export declare function getSdkSceneAreaIds(api: KakapoAPI): string[];
68
68
  export declare function hasGeometryOverflow(health: SceneHealth): boolean;
69
69
  export declare function hasSceneResourcePressure(health: SceneHealth): boolean;
70
70
  export declare function parseSceneHealth(response: unknown, affectedAreaIds: string[]): SceneHealth;
71
71
  export declare function readSdkSceneHealth(api: KakapoAPI, affectedAreaIds: string[], signal?: AbortSignal): Promise<SceneHealth>;
72
- export declare function readStableSdkSceneHealth(api: KakapoAPI, affectedAreaIds: string[], signal?: AbortSignal): Promise<SceneHealth>;
@@ -236,20 +236,3 @@ export async function readSdkSceneHealth(api, affectedAreaIds, signal) {
236
236
  removeAbortListener?.();
237
237
  }
238
238
  }
239
- export async function readStableSdkSceneHealth(api, affectedAreaIds, signal) {
240
- try {
241
- signal?.throwIfAborted();
242
- const before = await api.getSceneRevision();
243
- const health = await readSdkSceneHealth(api, affectedAreaIds, signal);
244
- signal?.throwIfAborted();
245
- await api.refreshScene();
246
- signal?.throwIfAborted();
247
- const after = await api.getSceneRevision();
248
- if (before === after)
249
- return health;
250
- return unverifiedSceneHealth(affectedAreaIds, "The scene changed while reading its health.");
251
- }
252
- catch (error) {
253
- return unverifiedSceneHealth(affectedAreaIds, String(error));
254
- }
255
- }
@@ -1,4 +1,4 @@
1
- import { type JsonValue, type KakapoAPI, type SceneEdit, type SceneSaveResult } from "@womp/kakapo-sdk";
1
+ import { type JsonValue, type KakapoAPI, type NativeSceneSnapshot, type SceneEdit, type SceneSaveResult } from "@womp/kakapo-sdk";
2
2
  export type SceneEditResult = {
3
3
  value: JsonValue;
4
4
  } | {
@@ -7,8 +7,6 @@ export type SceneEditResult = {
7
7
  export type SceneOperation = {
8
8
  id: string;
9
9
  targetKey: string;
10
- expectedRevision: string;
11
- resultRevision?: string;
12
10
  revision: number;
13
11
  status: "started" | "prepared" | "committed" | "rolled_back" | "unknown";
14
12
  result?: SceneEditResult;
@@ -24,7 +22,7 @@ export interface SceneOperationStore {
24
22
  createSceneOperation(operation: SceneOperation): Promise<boolean>;
25
23
  saveSceneOperation(operation: SceneOperation): Promise<SceneOperation>;
26
24
  }
27
- /** Resolve a journaled edit from a fresh renderer snapshot without executing its JavaScript. */
25
+ /** Recover acknowledged/no-op results without replaying or refetching an uncertain edit. */
28
26
  export declare function recoverSceneOperation(options: {
29
27
  api: KakapoAPI;
30
28
  store: SceneOperationStore;
@@ -36,12 +34,14 @@ export declare function recoverSceneOperation(options: {
36
34
  export declare function executeSceneOperation<T>(options: {
37
35
  api: KakapoAPI;
38
36
  store: SceneOperationStore;
39
- operation: Pick<SceneOperation, "id" | "targetKey" | "expectedRevision">;
37
+ operation: Pick<SceneOperation, "id" | "targetKey">;
40
38
  assertEditable(): void | Promise<void>;
41
39
  edit(draft: SceneEdit): T | Promise<T>;
42
40
  prepareCommit?(operation: SceneOperation): Promise<void>;
41
+ onCommittedScene?(scene: NativeSceneSnapshot): void;
43
42
  saveToRenderer?: boolean;
44
43
  }): Promise<{
45
44
  value: T;
46
45
  operation: SceneOperation;
46
+ reused?: boolean;
47
47
  }>;
@@ -1,5 +1,4 @@
1
1
  import { KakapoSceneCommitError } from "@womp/kakapo-sdk";
2
- import { metadataObject } from "./scene-metadata.js";
3
2
  function savedResult(value) {
4
3
  if (value === undefined)
5
4
  return { empty: true };
@@ -11,9 +10,9 @@ function savedResult(value) {
11
10
  return;
12
11
  if (typeof item !== "object" || item === null ||
13
12
  (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null))
14
- throw new Error("scene.edit must return JSON data or undefined");
13
+ throw new Error("scene.access must return JSON data or undefined");
15
14
  if (active.has(item))
16
- throw new Error("scene.edit cannot return cyclic data");
15
+ throw new Error("scene.access cannot return cyclic data");
17
16
  active.add(item);
18
17
  for (const nested of Object.values(item))
19
18
  validate(nested);
@@ -27,26 +26,17 @@ function resultValue(operation) {
27
26
  throw new Error("The saved scene operation result is missing");
28
27
  return ("value" in operation.result ? structuredClone(operation.result.value) : undefined);
29
28
  }
30
- function committedIds(ext) {
31
- const ids = metadataObject(ext.spark).committedSceneEdits;
32
- return Array.isArray(ids) ? ids.filter((id) => typeof id === "string") : [];
33
- }
34
29
  function complete(operation, status) {
35
30
  return { ...operation, status, completedAt: operation.completedAt ?? new Date().toISOString() };
36
31
  }
37
- /** Resolve a journaled edit from a fresh renderer snapshot without executing its JavaScript. */
32
+ /** Recover acknowledged/no-op results without replaying or refetching an uncertain edit. */
38
33
  export async function recoverSceneOperation(options) {
39
34
  await options.assertActive();
40
35
  const previous = await options.store.getSceneOperation(options.targetKey, options.id);
41
36
  if (!previous || previous.status === "committed" || previous.status === "rolled_back")
42
37
  return previous;
43
- await options.api.refreshScene();
44
- const ext = options.api.getSceneExt();
45
- const revision = await options.api.getSceneRevision();
46
38
  await options.assertActive();
47
- const matches = previous.resultRevision !== undefined &&
48
- previous.resultRevision === revision && committedIds(ext).includes(previous.id);
49
- if ((previous.changed === false || matches) && previous.result) {
39
+ if (previous.changed === false && previous.result) {
50
40
  return options.store.saveSceneOperation(complete(previous, "committed"));
51
41
  }
52
42
  return previous;
@@ -58,19 +48,17 @@ export async function executeSceneOperation(options) {
58
48
  await options.assertEditable();
59
49
  const previous = await store.getSceneOperation(identity.targetKey, identity.id);
60
50
  if (previous) {
61
- if (previous.expectedRevision !== identity.expectedRevision)
62
- throw new Error("Scene operation identity was reused with a different revision");
63
51
  if ((previous.save !== undefined) !== savesScene)
64
52
  throw new Error("Scene operation identity was reused with a different save policy");
65
53
  if (previous.status === "committed")
66
- return { value: resultValue(previous), operation: previous };
54
+ return { value: resultValue(previous), operation: previous, reused: true };
67
55
  if (previous.status === "rolled_back")
68
56
  throw new Error(previous.error ?? "Scene operation was rolled back");
69
57
  const recovered = await recoverSceneOperation({
70
58
  api, store, targetKey: identity.targetKey, id: identity.id, assertActive: options.assertEditable,
71
59
  });
72
60
  if (recovered?.status === "committed")
73
- return { value: resultValue(recovered), operation: recovered };
61
+ return { value: resultValue(recovered), operation: recovered, reused: true };
74
62
  throw new Error("Scene edit outcome is uncertain. Inspect the scene before making a new edit; this operation was not replayed.");
75
63
  }
76
64
  let operation = {
@@ -82,33 +70,26 @@ export async function executeSceneOperation(options) {
82
70
  try {
83
71
  const edit = async (draft) => {
84
72
  await options.assertEditable();
85
- if (await api.getSceneRevision() !== identity.expectedRevision)
86
- throw new Error("Scene changed since inspection. Read the scene again before editing.");
87
- const result = savedResult(await options.edit(draft));
88
- await options.assertEditable();
89
- const changed = await api.getSceneRevision() !== identity.expectedRevision;
90
- if (changed) {
91
- const spark = metadataObject(draft.getSceneExt().spark);
92
- const ids = [...committedIds(api.getSceneExt()), identity.id].slice(-64);
93
- draft.patchSceneExt([{ op: "add", path: "/spark", value: { ...spark, committedSceneEdits: ids } }]);
94
- }
95
- operation = await store.saveSceneOperation({
96
- ...operation, status: "prepared", result, changed,
97
- ...(changed ? { resultRevision: await api.getSceneRevision() } : {}),
98
- ...(savesScene && !changed ? { save: { status: "not_needed" } } : {}),
99
- });
100
- await options.assertEditable();
101
- if (changed)
102
- await options.prepareCommit?.(operation);
73
+ operation = { ...operation, result: savedResult(await options.edit(draft)) };
103
74
  await options.assertEditable();
104
75
  };
105
- if (options.saveToRenderer) {
106
- const committed = await api.editSceneAndSave(edit);
76
+ const committed = await api.accessScene(edit, {
77
+ save: savesScene,
78
+ beforeCommit: async (changed) => {
79
+ await options.assertEditable();
80
+ operation = await store.saveSceneOperation({
81
+ ...operation, status: "prepared", changed,
82
+ ...(savesScene && !changed ? { save: { status: "not_needed" } } : {}),
83
+ });
84
+ await options.assertEditable();
85
+ if (changed)
86
+ await options.prepareCommit?.(operation);
87
+ },
88
+ });
89
+ if (savesScene)
107
90
  operation = { ...operation, save: committed.save };
108
- }
109
- else {
110
- await api.editScene(edit);
111
- }
91
+ if (committed.changed)
92
+ options.onCommittedScene?.(committed.scene);
112
93
  }
113
94
  catch (error) {
114
95
  const status = error instanceof KakapoSceneCommitError ? error.outcome
@@ -125,7 +106,7 @@ export async function executeSceneOperation(options) {
125
106
  }
126
107
  throw error;
127
108
  }
128
- // Recovery requires both the operation marker and the exact prepared scene revision.
109
+ // A prepared record alone never proves an uncertain commit succeeded.
129
110
  operation = await store.saveSceneOperation(complete(operation, "committed"));
130
111
  return { value: resultValue(operation), operation };
131
112
  }
@@ -1,12 +1,12 @@
1
1
  import { type SdkSceneEditor } from "./sdk-scene-host.js";
2
2
  import type { GeometryHost } from "./capabilities.js";
3
- import { readStableSdkSceneHealth } from "./scene-health.js";
3
+ import { readSdkSceneHealth } from "./scene-health.js";
4
4
  import { type RepairStateStore } from "./repair-state.js";
5
5
  export type SdkGeometryHostOptions = {
6
6
  editor: SdkSceneEditor;
7
7
  repairs: RepairStateStore;
8
8
  canGrow?: () => boolean;
9
- onHealth?: (health: Awaited<ReturnType<typeof readStableSdkSceneHealth>>) => void;
9
+ onHealth?: (health: Awaited<ReturnType<typeof readSdkSceneHealth>>) => void;
10
10
  onSelect?: (objectId: number) => void | Promise<void>;
11
11
  };
12
12
  export declare function createSdkGeometryHost(options: SdkGeometryHostOptions): GeometryHost;
@@ -2,7 +2,7 @@ import { compileGeometry, migrateGraphExt, readEditableGeometry, } from "./geome
2
2
  import { graphExtSchema } from "./geometry/graph.js";
3
3
  import { boundedDiagnostics } from "./geometry/diagnostics.js";
4
4
  import { migrateParameterOverrides, readParameterOverrides, } from "./geometry/parameters.js";
5
- import { getSdkSceneAreaId, readStableSdkSceneHealth } from "./scene-health.js";
5
+ import { unverifiedSceneHealth } from "./scene-health.js";
6
6
  import { createGeometryRepairTracker, } from "./repair-state.js";
7
7
  function errorMessage(error) {
8
8
  return error instanceof Error ? error.message : String(error);
@@ -57,7 +57,6 @@ async function readTarget(api, objectId, target = resolveTarget(api, objectId))
57
57
  instanceId: target.instanceId,
58
58
  source: null,
59
59
  instances: [],
60
- revision: await api.getSceneRevision(),
61
60
  diagnostics: [],
62
61
  };
63
62
  if (target.graphId === undefined || target.graph === undefined)
@@ -93,7 +92,7 @@ function writeGraphStorage(draft, graphId, graph, instances) {
93
92
  path: "/scripts",
94
93
  value: { graphs: {}, instances: {} },
95
94
  },
96
- ]);
95
+ ], { returnScene: false });
97
96
  }
98
97
  else {
99
98
  const record = scripts;
@@ -111,7 +110,7 @@ function writeGraphStorage(draft, graphId, graph, instances) {
111
110
  value: {},
112
111
  });
113
112
  if (additions.length)
114
- draft.patchSceneStorage(additions);
113
+ draft.patchSceneStorage(additions, { returnScene: false });
115
114
  }
116
115
  draft.patchSceneStorage([
117
116
  {
@@ -124,7 +123,7 @@ function writeGraphStorage(draft, graphId, graph, instances) {
124
123
  path: `/scripts/instances/${id}`,
125
124
  value: instance,
126
125
  })),
127
- ]);
126
+ ], { returnScene: false });
128
127
  }
129
128
  export function createSdkGeometryHost(options) {
130
129
  const editor = options.editor;
@@ -134,17 +133,7 @@ export function createSdkGeometryHost(options) {
134
133
  read: (objectId, invocation) => editor.read(invocation, (api) => readTarget(api, objectId)),
135
134
  write: async (input, invocation) => {
136
135
  invocation.signal.throwIfAborted();
137
- const inspected = await editor.read(invocation, async (api) => {
138
- const current = resolveTarget(api, input.objectId);
139
- return {
140
- target: await readTarget(api, input.objectId, current),
141
- current,
142
- graphExt: current.graphId === undefined
143
- ? undefined
144
- : api.getSceneExt().graphsExt?.[current.graphId],
145
- };
146
- });
147
- const { target } = inspected;
136
+ const target = await editor.read(invocation, api => readTarget(api, input.objectId));
148
137
  const pendingKey = target.graphId === undefined
149
138
  ? `object:${input.objectId}`
150
139
  : `graph:${target.graphId}`;
@@ -152,8 +141,6 @@ export function createSdkGeometryHost(options) {
152
141
  throw new Error("This Geometry Nodes target is still being updated. Await its current update first.");
153
142
  pendingTargets.add(pendingKey);
154
143
  try {
155
- if (target.revision !== input.expectedRevision)
156
- throw new Error("The scene changed. Read it again before editing.");
157
144
  const repairingGeometry = target.graphId !== undefined &&
158
145
  (await repairs.isPending(String(target.graphId)));
159
146
  if (target.graphId !== undefined)
@@ -164,39 +151,13 @@ export function createSdkGeometryHost(options) {
164
151
  graphId: target.graphId,
165
152
  instanceId: target.instanceId,
166
153
  affectedObjectIds: [],
167
- revision: target.revision,
168
154
  diagnostics: boundedDiagnostics(diagnostics),
169
155
  });
170
156
  let compiled;
171
- let nextExt;
172
- const updates = new Map();
173
157
  const diagnostics = [];
174
158
  try {
175
159
  compiled = compileGeometry(input.source);
176
160
  readEditableGeometry(compiled.graph, compiled.graphExt);
177
- if (target.graphId === undefined) {
178
- nextExt = migrateGraphExt({ nodes: {} }, {}, compiled);
179
- }
180
- else {
181
- const { current } = inspected;
182
- if (current.graph === undefined)
183
- throw new Error("The Geometry Nodes graph is missing.");
184
- const ext = graphExtSchema.parse(inspected.graphExt ?? {});
185
- const previous = readEditableGeometry(current.graph, ext);
186
- nextExt = migrateGraphExt(ext, previous.nodeIds, compiled);
187
- for (const [id, instance] of Object.entries(current.scripts.instances)) {
188
- if (instance.graph_id !== target.graphId)
189
- continue;
190
- const old = readParameterOverrides(previous.graph, ext, instance.data ?? {});
191
- const migrated = migrateParameterOverrides(previous.graph, ext, compiled.graph, nextExt, old.data);
192
- updates.set(id, {
193
- graph_id: target.graphId,
194
- data: migrated.data,
195
- });
196
- if (migrated.removed.length)
197
- diagnostics.push(`Instance ${id}: removed or type-changed overrides ${migrated.removed.join(", ")} now use graph defaults where present.`);
198
- }
199
- }
200
161
  }
201
162
  catch (error) {
202
163
  return rejected([errorMessage(error)]);
@@ -209,7 +170,7 @@ export function createSdkGeometryHost(options) {
209
170
  let operationId;
210
171
  let applied;
211
172
  try {
212
- applied = await editor.edit({
173
+ applied = await editor.access({
213
174
  capability: "geometry",
214
175
  operation: "write",
215
176
  target: () => receiptTarget,
@@ -220,6 +181,36 @@ export function createSdkGeometryHost(options) {
220
181
  throw new Error("Scene memory limits prevent applying this graph.");
221
182
  if (current.graphId !== target.graphId)
222
183
  throw new Error("The Geometry Nodes target changed. Read it again.");
184
+ let nextExt;
185
+ const updates = new Map();
186
+ try {
187
+ if (target.graphId === undefined) {
188
+ nextExt = migrateGraphExt({ nodes: {} }, {}, compiled);
189
+ }
190
+ else {
191
+ if (current.graph === undefined)
192
+ throw new Error("The Geometry Nodes graph is missing.");
193
+ const graphExt = draft.getSceneExt(["graphsExt"]).graphsExt;
194
+ const ext = graphExtSchema.parse(graphExt?.[target.graphId] ?? {});
195
+ const previous = readEditableGeometry(current.graph, ext);
196
+ nextExt = migrateGraphExt(ext, previous.nodeIds, compiled);
197
+ for (const [id, instance] of Object.entries(current.scripts.instances)) {
198
+ if (instance.graph_id !== target.graphId)
199
+ continue;
200
+ const old = readParameterOverrides(previous.graph, ext, instance.data ?? {});
201
+ const migrated = migrateParameterOverrides(previous.graph, ext, compiled.graph, nextExt, old.data);
202
+ updates.set(id, {
203
+ graph_id: target.graphId,
204
+ data: migrated.data,
205
+ });
206
+ if (migrated.removed.length)
207
+ diagnostics.push(`Instance ${id}: removed or type-changed overrides ${migrated.removed.join(", ")} now use graph defaults where present.`);
208
+ }
209
+ }
210
+ }
211
+ catch (error) {
212
+ return { rejected: [errorMessage(error)] };
213
+ }
223
214
  if (current.graphId !== undefined)
224
215
  await repairs.reserve(String(current.graphId), id);
225
216
  let objectId = input.objectId;
@@ -247,34 +238,25 @@ export function createSdkGeometryHost(options) {
247
238
  if (!ext.graphsExt)
248
239
  draft.patchSceneExt([
249
240
  { op: "add", path: "/graphsExt", value: {} },
250
- ]);
241
+ ], { returnScene: false });
251
242
  draft.patchSceneExt([
252
243
  {
253
244
  op: "add",
254
245
  path: `/graphsExt/${graphId}`,
255
246
  value: nextExt,
256
247
  },
257
- ]);
248
+ ], { returnScene: false });
258
249
  const affectedObjectIds = Object.entries(scriptStorage(draft).instances)
259
250
  .filter(([, instance]) => instance.graph_id === graphId)
260
251
  .flatMap(([id]) => objectIdsFor(draft, Number(id)));
261
252
  Object.assign(receiptTarget, { objectId, graphId, instanceId });
262
253
  return { objectId, graphId, instanceId, affectedObjectIds };
263
- }, input.expectedRevision, invocation, async (_operation, api) => {
264
- const committed = resolveTarget(api, receiptTarget.objectId);
265
- if (committed.graphId === undefined)
254
+ }, invocation, async (operation) => {
255
+ const graphId = receiptTarget.graphId;
256
+ if (graphId === undefined)
266
257
  return;
267
- const affectedObjectIds = Object.entries(committed.scripts.instances)
268
- .filter(([, instance]) => instance.graph_id === committed.graphId)
269
- .flatMap(([id]) => objectIdsFor(api, Number(id)));
270
- const areaIds = [
271
- ...new Set(affectedObjectIds.map((id) => getSdkSceneAreaId(api, id))),
272
- ];
273
- const sceneHealth = await readStableSdkSceneHealth(api, areaIds, invocation.signal);
274
- options.onHealth?.(sceneHealth);
275
- const recorded = await repairs.record(String(committed.graphId), _operation.id, sceneHealth);
258
+ const recorded = await repairs.record(String(graphId), operation.id, unverifiedSceneHealth([], "Health was not requested."));
276
259
  return {
277
- sceneHealth,
278
260
  geometryRepairAttempts: recorded.repairAttempts,
279
261
  geometryRepairLimit: recorded.repairLimit,
280
262
  };
@@ -288,6 +270,8 @@ export function createSdkGeometryHost(options) {
288
270
  }
289
271
  throw error;
290
272
  }
273
+ if (applied.value.rejected)
274
+ return rejected(applied.value.rejected);
291
275
  invocation.signal.throwIfAborted();
292
276
  try {
293
277
  await options.onSelect?.(applied.value.objectId);
@@ -296,7 +280,6 @@ export function createSdkGeometryHost(options) {
296
280
  return {
297
281
  status: "applied",
298
282
  ...applied.value,
299
- revision: applied.operation.resultRevision ?? input.expectedRevision,
300
283
  diagnostics: boundedDiagnostics(diagnostics),
301
284
  };
302
285
  }