@womp/spark-agent 6.5.0 → 8.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 (40) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +7 -4
  3. package/dist/agent.d.ts +3 -6
  4. package/dist/agent.js +41 -23
  5. package/dist/capabilities/geometry.d.ts +17 -5
  6. package/dist/capabilities/geometry.js +11 -12
  7. package/dist/compaction.d.ts +41 -8
  8. package/dist/compaction.js +241 -77
  9. package/dist/context-window.d.ts +23 -0
  10. package/dist/context-window.js +141 -0
  11. package/dist/generated/geometry-declarations.d.ts +1 -1
  12. package/dist/generated/geometry-declarations.js +1 -1
  13. package/dist/geometry/backendRegistry.d.ts +1 -0
  14. package/dist/geometry/common.d.ts +5 -2
  15. package/dist/geometry/common.js +18 -3
  16. package/dist/geometry/compiler.js +2 -2
  17. package/dist/geometry/diagnostics.d.ts +2 -7
  18. package/dist/geometry/diagnostics.js +3 -51
  19. package/dist/geometry/graph.d.ts +1 -1
  20. package/dist/geometry/graph.js +1 -0
  21. package/dist/geometry/index.d.ts +0 -1
  22. package/dist/geometry/index.js +0 -3
  23. package/dist/geometry/lift.js +105 -113
  24. package/dist/geometry/lower.js +29 -49
  25. package/dist/geometry/schemas/flowir-dialect.json +117 -117
  26. package/dist/geometry/writeParamData.js +4 -4
  27. package/dist/geometry.d.ts +2 -5
  28. package/dist/geometry.js +7 -11
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +2 -1
  31. package/dist/intent-bench.js +1 -1
  32. package/dist/sdk-geometry-host.d.ts +1 -1
  33. package/dist/sdk-geometry-host.js +253 -251
  34. package/dist/ui.d.ts +7 -0
  35. package/dist/ui.js +1 -0
  36. package/dist/womp-scene-api.d.ts +9 -1
  37. package/dist/womp-scene-api.js +41 -32
  38. package/package.json +2 -2
  39. package/dist/context-budget.d.ts +0 -65
  40. package/dist/context-budget.js +0 -364
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  export { runSparkAgent, } from "./agent.js";
2
2
  export { createSparkTools } from "./agent-tools.js";
3
3
  export { convertSparkMessages } from "./model-history.js";
4
- export * from "./context-budget.js";
4
+ export { applyCompaction, latestCompaction } from "./compaction.js";
5
+ export * from "./context-window.js";
5
6
  export * from "./capabilities.js";
6
7
  export { createSdkSceneEditor, createSdkSceneHost, } from "./sdk-scene-host.js";
7
8
  export { recoverSceneOperation } from "./scene-operations.js";
@@ -342,7 +342,7 @@ async function intentFixtures(testCase, controller) {
342
342
  if (objectId !== 0)
343
343
  return refuse("geometry.read", `Object ${objectId} has no Geometry Nodes graph.`);
344
344
  note("geometry.read");
345
- return { kind: "container", objectId: 0, title: "Scene", source: null, instances: [], diagnostics: [] };
345
+ return { kind: "container", objectId: 0, title: "Scene", source: null, transform: null, bounds: null, instances: [], diagnostics: [] };
346
346
  },
347
347
  write: async (options) => decide("geometry-nodes", "geometry.write", options),
348
348
  }, geometryReference()),
@@ -1,5 +1,5 @@
1
- import { type SdkSceneEditor } from "./sdk-scene-host.js";
2
1
  import type { GeometryHost } from "./capabilities.js";
2
+ import { type SdkSceneEditor } from "./sdk-scene-host.js";
3
3
  import { readSdkSceneHealth } from "./scene-health.js";
4
4
  import { type RepairStateStore } from "./repair-state.js";
5
5
  export type SdkGeometryHostOptions = {
@@ -1,293 +1,295 @@
1
+ import { KakapoError, } from "@womp/kakapo-sdk";
1
2
  import { SceneOperationError } from "./sdk-scene-host.js";
2
- import { compileGeometry, migrateGraphExt, readEditableGeometry, } from "./geometry/compiler.js";
3
- import { graphExtSchema } from "./geometry/graph.js";
3
+ import { compileGeometry, migrateGraphExt, readEditableGeometry } from "./geometry/compiler.js";
4
4
  import { boundedDiagnostics } from "./geometry/diagnostics.js";
5
- import { migrateParameterOverrides, readParameterOverrides, } from "./geometry/parameters.js";
5
+ import { graphExtSchema } from "./geometry/graph.js";
6
+ import { migrateParameterOverrides, readParameterOverrides } from "./geometry/parameters.js";
7
+ import { convertAABB, sceneNodePosition, sceneNodeRotation, sceneNodeSize } from "./womp-scene-api.js";
8
+ import { sceneUnitMultiplier } from "./coordinates.js";
6
9
  import { unverifiedSceneHealth } from "./scene-health.js";
7
- import { createGeometryRepairTracker, } from "./repair-state.js";
10
+ import { createGeometryRepairTracker } from "./repair-state.js";
11
+ const MEMORY_LIMIT = "Scene memory limits prevent applying this graph.";
8
12
  function errorMessage(error) {
9
13
  return error instanceof Error ? error.message : String(error);
10
14
  }
15
+ function isRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
11
18
  function unusedId(entries) {
12
19
  let id = 0;
13
20
  while (id in entries)
14
21
  id++;
15
22
  return id;
16
23
  }
17
- function scriptStorage(source) {
18
- const scripts = source.getSceneStorage().scripts;
19
- if (!scripts || typeof scripts !== "object" || Array.isArray(scripts))
20
- return { graphs: {}, instances: {} };
21
- const value = scripts;
24
+ function readScripts(scene) {
25
+ const stored = scene.getSceneStorage().scripts;
26
+ const scripts = isRecord(stored) ? stored : {};
22
27
  return {
23
- graphs: value.graphs && typeof value.graphs === "object"
24
- ? value.graphs
25
- : {},
26
- instances: value.instances && typeof value.instances === "object"
27
- ? value.instances
28
- : {},
28
+ ...scripts,
29
+ graphs: isRecord(scripts.graphs) ? scripts.graphs : {},
30
+ instances: isRecord(scripts.instances) ? scripts.instances : {},
29
31
  };
30
32
  }
31
- function objectIdsFor(source, instanceId) {
32
- return source
33
- .listNodes({ kind: "union" })
34
- .filter((node) => node.kind === "union" && node.scriptInstanceId === instanceId)
35
- .map((node) => node.id);
33
+ function readGraphExts(scene) {
34
+ const graphExts = scene.getSceneExt(["graphsExt"]).graphsExt;
35
+ return isRecord(graphExts) ? graphExts : {};
36
+ }
37
+ function graphExtOf(graphExts, graphId) {
38
+ return graphExtSchema.parse(graphExts[graphId] ?? {});
36
39
  }
37
- function resolveTarget(source, objectId) {
38
- const node = source.getNode(objectId);
40
+ function graphInstances(scripts, graphId) {
41
+ return Object.entries(scripts.instances).filter(([, instance]) => instance.graph_id === graphId);
42
+ }
43
+ function objectIdsByInstance(scene) {
44
+ const objectIds = new Map();
45
+ for (const node of scene.listNodes({ kind: "union" })) {
46
+ if (node.kind !== "union" || node.scriptInstanceId === undefined)
47
+ continue;
48
+ const ids = objectIds.get(node.scriptInstanceId);
49
+ if (ids)
50
+ ids.push(node.id);
51
+ else
52
+ objectIds.set(node.scriptInstanceId, [node.id]);
53
+ }
54
+ return objectIds;
55
+ }
56
+ function resolveTarget(scene, objectId) {
57
+ const node = scene.getNode(objectId);
39
58
  if (objectId !== 0 && node.kind !== "union" && node.kind !== "group")
40
59
  throw new Error("Geometry Nodes requires an Area, Group, or scene root target.");
60
+ const scripts = readScripts(scene);
41
61
  const instanceId = node.kind === "union" ? node.scriptInstanceId : undefined;
42
- const scripts = scriptStorage(source);
43
- const instance = instanceId === undefined ? undefined : scripts.instances[instanceId];
44
- if (instanceId !== undefined && !instance)
62
+ if (instanceId === undefined)
63
+ return { node, scripts };
64
+ const instance = scripts.instances[instanceId];
65
+ if (!instance)
45
66
  throw new Error("The target's Geometry Nodes instance is missing. Inspect it before editing.");
46
- const graphId = instance?.graph_id;
47
- const graph = graphId === undefined ? undefined : scripts.graphs[graphId];
48
- if (instance && (graph === undefined || !Number.isInteger(graphId)))
67
+ const graphId = instance.graph_id;
68
+ const graph = scripts.graphs[graphId];
69
+ if (graph === undefined || !Number.isInteger(graphId))
49
70
  throw new Error("The target's Geometry Nodes graph is missing. Inspect it before editing.");
50
- return { node, graph, graphId, instanceId, scripts };
71
+ return { node, scripts, graphId, instanceId, graph };
51
72
  }
52
- async function readTarget(api, objectId, target = resolveTarget(api, objectId)) {
53
- const result = {
54
- kind: target.graphId === undefined ? "container" : "graph",
55
- objectId,
56
- title: target.node.name,
57
- graphId: target.graphId,
58
- instanceId: target.instanceId,
59
- source: null,
60
- instances: [],
61
- diagnostics: [],
62
- };
63
- if (target.graphId === undefined || target.graph === undefined)
64
- return result;
73
+ function describeGraph(api, target) {
74
+ if (target.graph === undefined)
75
+ return { source: null, instances: [], diagnostics: [] };
65
76
  try {
66
- const ext = graphExtSchema.parse(api.getSceneExt().graphsExt?.[target.graphId] ?? {});
77
+ const ext = graphExtOf(readGraphExts(api), target.graphId);
67
78
  const editable = readEditableGeometry(target.graph, ext);
68
- result.source = editable.source;
69
- for (const [id, instance] of Object.entries(target.scripts.instances)) {
70
- if (instance.graph_id !== target.graphId)
71
- continue;
72
- const { params } = readParameterOverrides(editable.graph, ext, instance.data ?? {});
73
- result.instances.push({
79
+ const objectIds = objectIdsByInstance(api);
80
+ return {
81
+ source: editable.source,
82
+ instances: graphInstances(target.scripts, target.graphId).map(([id, instance]) => ({
74
83
  instanceId: Number(id),
75
- objectIds: objectIdsFor(api, Number(id)),
76
- params,
77
- });
78
- }
84
+ objectIds: objectIds.get(Number(id)) ?? [],
85
+ params: readParameterOverrides(editable.graph, ext, instance.data ?? {}).params,
86
+ })),
87
+ diagnostics: [],
88
+ };
79
89
  }
80
90
  catch (error) {
81
- result.source = null;
82
- // Bounded: a compiler diagnostic is unbounded text (./geometry/diagnostics.ts).
83
- result.diagnostics.push(...boundedDiagnostics([errorMessage(error)]));
91
+ return { source: null, instances: [], diagnostics: boundedDiagnostics([errorMessage(error)]) };
84
92
  }
85
- return structuredClone(result);
86
93
  }
87
- function writeGraphStorage(draft, graphId, graph, instances) {
88
- const scripts = draft.getSceneStorage().scripts;
89
- if (!scripts || typeof scripts !== "object" || Array.isArray(scripts)) {
90
- draft.patchSceneStorage([
91
- {
92
- op: "add",
93
- path: "/scripts",
94
- value: { graphs: {}, instances: {} },
95
- },
96
- ], { returnScene: false });
94
+ async function readPlacement(api, objectId, target) {
95
+ if (objectId === 0)
96
+ return { transform: null, bounds: null, diagnostics: [] };
97
+ const reader = { requireNode: (id) => api.getNode(id), getNode: (id) => api.getNode(id) };
98
+ const unitMultiplier = sceneUnitMultiplier(api.getSceneProperties().unit_multiplier);
99
+ const transform = {
100
+ position: sceneNodePosition(reader, 0, unitMultiplier, objectId),
101
+ rotation: sceneNodeRotation(reader, objectId),
102
+ size: sceneNodeSize(reader, 0, unitMultiplier, objectId),
103
+ };
104
+ if (target.graph === undefined || target.node.parentId !== 0)
105
+ return { transform, bounds: null, diagnostics: [] };
106
+ try {
107
+ return { transform, bounds: convertAABB(await api.getNodeBoundingBox(objectId), unitMultiplier), diagnostics: [] };
97
108
  }
98
- else {
99
- const record = scripts;
100
- const additions = [];
101
- if (!record.graphs)
102
- additions.push({
103
- op: "add",
104
- path: "/scripts/graphs",
105
- value: {},
106
- });
107
- if (!record.instances)
108
- additions.push({
109
- op: "add",
110
- path: "/scripts/instances",
111
- value: {},
112
- });
113
- if (additions.length)
114
- draft.patchSceneStorage(additions, { returnScene: false });
109
+ catch (error) {
110
+ if (error instanceof KakapoError && error.code === "EMPTY_BOUNDING_BOX")
111
+ return { transform, bounds: null, diagnostics: [] };
112
+ return { transform, bounds: null, diagnostics: [`Bounds are unavailable: ${errorMessage(error)}`] };
115
113
  }
116
- draft.patchSceneStorage([
117
- {
118
- op: "add",
119
- path: `/scripts/graphs/${graphId}`,
120
- value: graph,
121
- },
122
- ...[...instances].map(([id, instance]) => ({
123
- op: "add",
124
- path: `/scripts/instances/${id}`,
125
- value: instance,
126
- })),
127
- ], { returnScene: false });
114
+ }
115
+ async function readTarget(api, objectId) {
116
+ const target = resolveTarget(api, objectId);
117
+ const graph = describeGraph(api, target);
118
+ const placement = await readPlacement(api, objectId, target);
119
+ return {
120
+ kind: target.graph === undefined ? "container" : "graph",
121
+ objectId,
122
+ title: target.node.name,
123
+ graphId: target.graphId,
124
+ instanceId: target.instanceId,
125
+ source: graph.source,
126
+ transform: placement.transform,
127
+ bounds: placement.bounds,
128
+ instances: graph.instances,
129
+ diagnostics: boundedDiagnostics([...graph.diagnostics, ...placement.diagnostics]),
130
+ };
131
+ }
132
+ function planUpdate(target, graphExts, compiled) {
133
+ const ext = graphExtOf(graphExts, target.graphId);
134
+ const previous = readEditableGeometry(target.graph, ext);
135
+ const nextExt = migrateGraphExt(ext, previous.nodeIds, compiled);
136
+ const instances = {};
137
+ const diagnostics = [];
138
+ for (const [id, instance] of graphInstances(target.scripts, target.graphId)) {
139
+ const migrated = migrateParameterOverrides(previous.graph, ext, compiled.graph, nextExt, instance.data ?? {});
140
+ instances[id] = { graph_id: target.graphId, data: migrated.data };
141
+ if (migrated.removed.length)
142
+ diagnostics.push(`Instance ${id}: removed or type-changed overrides ${migrated.removed.join(", ")} now use graph defaults where present.`);
143
+ }
144
+ return { graphId: target.graphId, instanceId: target.instanceId, graph: compiled.graph, ext: nextExt, instances, diagnostics };
145
+ }
146
+ function planCreation(target, compiled) {
147
+ const graphId = unusedId(target.scripts.graphs);
148
+ const instanceId = unusedId(target.scripts.instances);
149
+ return {
150
+ graphId,
151
+ instanceId,
152
+ graph: compiled.graph,
153
+ ext: migrateGraphExt({ nodes: {} }, {}, compiled),
154
+ instances: { [instanceId]: { graph_id: graphId, data: {} } },
155
+ diagnostics: [],
156
+ };
157
+ }
158
+ function attachArea(draft, objectId, node, instanceId) {
159
+ const areaId = objectId !== 0 && node.kind === "union"
160
+ ? objectId
161
+ : draft.createNode({ kind: "union", parentId: objectId, name: "Area" }).id;
162
+ draft.updateNode(areaId, { scriptInstanceId: instanceId });
163
+ return areaId;
164
+ }
165
+ function writeGraph(draft, scripts, graphExts, change) {
166
+ const nextScripts = {
167
+ ...scripts,
168
+ graphs: { ...scripts.graphs, [change.graphId]: change.graph },
169
+ instances: { ...scripts.instances, ...change.instances },
170
+ };
171
+ draft.patchSceneStorage([{ op: "add", path: "/scripts", value: nextScripts }], { returnScene: false });
172
+ draft.patchSceneExt([{ op: "add", path: "/graphsExt", value: { ...graphExts, [change.graphId]: change.ext } }], { returnScene: false });
128
173
  }
129
174
  export function createSdkGeometryHost(options) {
130
- const editor = options.editor;
175
+ const { editor } = options;
131
176
  const repairs = createGeometryRepairTracker(options.repairs, "geometry");
132
177
  const pendingTargets = new Set();
133
- return {
134
- read: (objectId, invocation) => editor.read(invocation, (api) => readTarget(api, objectId)),
135
- write: async (input, invocation) => {
136
- invocation.signal.throwIfAborted();
137
- const target = await editor.read(invocation, api => readTarget(api, input.objectId));
138
- const pendingKey = target.graphId === undefined
139
- ? `object:${input.objectId}`
140
- : `graph:${target.graphId}`;
141
- if (pendingTargets.has(pendingKey))
142
- throw new Error("This Geometry Nodes target is still being updated. Await its current update first.");
143
- pendingTargets.add(pendingKey);
178
+ const write = async (input, invocation) => {
179
+ const target = await editor.read(invocation, (api) => resolveTarget(api, input.objectId));
180
+ const rejected = (diagnostics) => ({
181
+ status: "rejected",
182
+ objectId: input.objectId,
183
+ graphId: target.graphId,
184
+ instanceId: target.instanceId,
185
+ affectedObjectIds: [],
186
+ diagnostics: boundedDiagnostics(diagnostics),
187
+ });
188
+ const pendingKey = target.graphId === undefined ? `object:${input.objectId}` : `graph:${target.graphId}`;
189
+ if (pendingTargets.has(pendingKey))
190
+ throw new Error("This Geometry Nodes target is still being updated. Await its current update first.");
191
+ pendingTargets.add(pendingKey);
192
+ try {
193
+ const repairKey = target.graphId === undefined ? undefined : String(target.graphId);
194
+ const repairing = repairKey !== undefined && (await repairs.isPending(repairKey));
195
+ if (repairKey !== undefined)
196
+ await repairs.assertCanWrite(repairKey);
197
+ let compiled;
144
198
  try {
145
- const repairingGeometry = target.graphId !== undefined &&
146
- (await repairs.isPending(String(target.graphId)));
147
- if (target.graphId !== undefined)
148
- await repairs.assertCanWrite(String(target.graphId));
149
- const rejected = (diagnostics) => ({
150
- status: "rejected",
151
- objectId: input.objectId,
152
- graphId: target.graphId,
153
- instanceId: target.instanceId,
154
- affectedObjectIds: [],
155
- diagnostics: boundedDiagnostics(diagnostics),
156
- });
157
- let compiled;
158
- const diagnostics = [];
159
- try {
160
- compiled = compileGeometry(input.source);
161
- readEditableGeometry(compiled.graph, compiled.graphExt);
162
- }
163
- catch (error) {
164
- return rejected([errorMessage(error)]);
165
- }
166
- if (repairingGeometry === false && options.canGrow?.() === false)
167
- return rejected(["Scene memory limits prevent applying this graph."]);
168
- const receiptTarget = {
169
- objectId: input.objectId,
170
- };
171
- let operationId;
172
- let applied;
173
- try {
174
- applied = await editor.access({
175
- capability: "geometry",
176
- operation: "write",
177
- target: () => receiptTarget,
178
- }, async (_api, draft, id) => {
179
- operationId = id;
180
- const current = resolveTarget(draft, input.objectId);
181
- if (repairingGeometry === false && options.canGrow?.() === false)
182
- throw new Error("Scene memory limits prevent applying this graph.");
183
- if (current.graphId !== target.graphId)
184
- throw new Error("The Geometry Nodes target changed. Read it again.");
185
- let nextExt;
186
- const updates = new Map();
187
- try {
188
- if (target.graphId === undefined) {
189
- nextExt = migrateGraphExt({ nodes: {} }, {}, compiled);
190
- }
191
- else {
192
- if (current.graph === undefined)
193
- throw new Error("The Geometry Nodes graph is missing.");
194
- const graphExt = draft.getSceneExt(["graphsExt"]).graphsExt;
195
- const ext = graphExtSchema.parse(graphExt?.[target.graphId] ?? {});
196
- const previous = readEditableGeometry(current.graph, ext);
197
- nextExt = migrateGraphExt(ext, previous.nodeIds, compiled);
198
- for (const [id, instance] of Object.entries(current.scripts.instances)) {
199
- if (instance.graph_id !== target.graphId)
200
- continue;
201
- const old = readParameterOverrides(previous.graph, ext, instance.data ?? {});
202
- const migrated = migrateParameterOverrides(previous.graph, ext, compiled.graph, nextExt, old.data);
203
- updates.set(id, {
204
- graph_id: target.graphId,
205
- data: migrated.data,
206
- });
207
- if (migrated.removed.length)
208
- diagnostics.push(`Instance ${id}: removed or type-changed overrides ${migrated.removed.join(", ")} now use graph defaults where present.`);
209
- }
210
- }
211
- }
212
- catch (error) {
213
- return { rejected: [errorMessage(error)] };
214
- }
215
- if (current.graphId !== undefined)
216
- await repairs.reserve(String(current.graphId), id);
217
- let objectId = input.objectId;
218
- let graphId = current.graphId;
219
- let instanceId = current.instanceId;
220
- const nextInstances = new Map(updates);
221
- if (graphId === undefined) {
222
- graphId = unusedId(current.scripts.graphs);
223
- instanceId = unusedId(current.scripts.instances);
224
- if (objectId === 0 || current.node.kind !== "union") {
225
- objectId = draft.createNode({
226
- kind: "union",
227
- parentId: objectId,
228
- name: "Area",
229
- }).id;
230
- }
231
- draft.updateNode(objectId, { scriptInstanceId: instanceId });
232
- nextInstances.set(String(instanceId), {
233
- graph_id: graphId,
234
- data: {},
235
- });
236
- }
237
- writeGraphStorage(draft, graphId, compiled.graph, nextInstances);
238
- const ext = draft.getSceneExt();
239
- if (!ext.graphsExt)
240
- draft.patchSceneExt([
241
- { op: "add", path: "/graphsExt", value: {} },
242
- ], { returnScene: false });
243
- draft.patchSceneExt([
244
- {
245
- op: "add",
246
- path: `/graphsExt/${graphId}`,
247
- value: nextExt,
248
- },
249
- ], { returnScene: false });
250
- const affectedObjectIds = Object.entries(scriptStorage(draft).instances)
251
- .filter(([, instance]) => instance.graph_id === graphId)
252
- .flatMap(([id]) => objectIdsFor(draft, Number(id)));
253
- Object.assign(receiptTarget, { objectId, graphId, instanceId });
254
- return { objectId, graphId, instanceId, affectedObjectIds };
255
- }, invocation, async (operation) => {
256
- const graphId = receiptTarget.graphId;
257
- if (graphId === undefined)
258
- return;
259
- const recorded = await repairs.record(String(graphId), operation.id, unverifiedSceneHealth([], "Health was not requested."));
260
- return {
261
- geometryRepairAttempts: recorded.repairAttempts,
262
- geometryRepairLimit: recorded.repairLimit,
263
- };
264
- });
265
- }
266
- catch (error) {
267
- if (operationId !== undefined && target.graphId !== undefined) {
268
- const operation = error instanceof SceneOperationError
269
- ? error.operation : await editor.getOperation(operationId);
270
- if (operation?.status === "rolled_back")
271
- await repairs.refund(String(target.graphId), operationId);
199
+ compiled = compileGeometry(input.source);
200
+ readEditableGeometry(compiled.graph, compiled.graphExt);
201
+ }
202
+ catch (error) {
203
+ return rejected([errorMessage(error)]);
204
+ }
205
+ const memoryBlocked = () => repairing === false && options.canGrow?.() === false;
206
+ if (memoryBlocked())
207
+ return rejected([MEMORY_LIMIT]);
208
+ const receipt = { objectId: input.objectId };
209
+ let operationId;
210
+ let applied;
211
+ try {
212
+ applied = await editor.access({ capability: "geometry", operation: "write", target: () => receipt }, async (_api, draft, id) => {
213
+ operationId = id;
214
+ const current = resolveTarget(draft, input.objectId);
215
+ if (memoryBlocked())
216
+ throw new Error(MEMORY_LIMIT);
217
+ if (current.graphId !== target.graphId)
218
+ throw new Error("The Geometry Nodes target changed. Read it again.");
219
+ const graphExts = readGraphExts(draft);
220
+ let change;
221
+ try {
222
+ change = current.graph === undefined ? planCreation(current, compiled) : planUpdate(current, graphExts, compiled);
272
223
  }
273
- throw error;
274
- }
275
- if (applied.value.rejected)
276
- return rejected(applied.value.rejected);
277
- invocation.signal.throwIfAborted();
278
- try {
279
- await options.onSelect?.(applied.value.objectId);
224
+ catch (error) {
225
+ return { rejected: [errorMessage(error)] };
226
+ }
227
+ if (current.graph !== undefined)
228
+ await repairs.reserve(String(current.graphId), id);
229
+ const objectId = current.graph === undefined ? attachArea(draft, input.objectId, current.node, change.instanceId) : input.objectId;
230
+ writeGraph(draft, current.scripts, graphExts, change);
231
+ const objectIds = objectIdsByInstance(draft);
232
+ Object.assign(receipt, { objectId, graphId: change.graphId, instanceId: change.instanceId });
233
+ return {
234
+ objectId,
235
+ graphId: change.graphId,
236
+ instanceId: change.instanceId,
237
+ affectedObjectIds: Object.keys(change.instances).flatMap((instanceId) => objectIds.get(Number(instanceId)) ?? []),
238
+ diagnostics: change.diagnostics,
239
+ };
240
+ }, invocation, async (operation) => {
241
+ if (receipt.graphId === undefined)
242
+ return;
243
+ const recorded = await repairs.record(String(receipt.graphId), operation.id, unverifiedSceneHealth([], "Health was not requested."));
244
+ return { geometryRepairAttempts: recorded.repairAttempts, geometryRepairLimit: recorded.repairLimit };
245
+ });
246
+ }
247
+ catch (error) {
248
+ if (operationId !== undefined && repairKey !== undefined) {
249
+ const operation = error instanceof SceneOperationError ? error.operation : await editor.getOperation(operationId);
250
+ if (operation?.status === "rolled_back")
251
+ await repairs.refund(repairKey, operationId);
280
252
  }
281
- catch { }
282
- return {
283
- status: "applied",
284
- ...applied.value,
285
- diagnostics: boundedDiagnostics(diagnostics),
286
- };
253
+ throw error;
287
254
  }
288
- finally {
289
- pendingTargets.delete(pendingKey);
255
+ const result = applied.value;
256
+ if (result.rejected)
257
+ return rejected(result.rejected);
258
+ invocation.signal.throwIfAborted();
259
+ try {
260
+ await options.onSelect?.(result.objectId);
261
+ }
262
+ catch { }
263
+ let committed;
264
+ try {
265
+ committed = await editor.read(invocation, (api) => readTarget(api, result.objectId));
290
266
  }
291
- },
267
+ catch (error) {
268
+ committed = {
269
+ source: null,
270
+ transform: null,
271
+ bounds: null,
272
+ diagnostics: [`The write was applied, but reading it back failed: ${errorMessage(error)} Read the target before editing it again.`],
273
+ };
274
+ }
275
+ return {
276
+ status: "applied",
277
+ objectId: result.objectId,
278
+ graphId: result.graphId,
279
+ instanceId: result.instanceId,
280
+ affectedObjectIds: result.affectedObjectIds,
281
+ source: committed.source ?? undefined,
282
+ transform: committed.transform,
283
+ bounds: committed.bounds,
284
+ diagnostics: boundedDiagnostics([...result.diagnostics, ...committed.diagnostics]),
285
+ };
286
+ }
287
+ finally {
288
+ pendingTargets.delete(pendingKey);
289
+ }
290
+ };
291
+ return {
292
+ read: (objectId, invocation) => editor.read(invocation, (api) => readTarget(api, objectId)),
293
+ write,
292
294
  };
293
295
  }
package/dist/ui.d.ts CHANGED
@@ -106,6 +106,12 @@ export type SparkSelectionData = {
106
106
  export type SparkCompaction = {
107
107
  /** The user message the model reads verbatim from; everything before it is summarized. */
108
108
  firstKeptMessageId: string;
109
+ /**
110
+ * Set when the summary was written during its turn: the first step of the
111
+ * recording message the model still reads, counted from 0. Its earlier steps
112
+ * are summarized too.
113
+ */
114
+ firstKeptStep?: number;
109
115
  summary: string;
110
116
  /** Skills read in the summarized messages; their current text is sent after the summary. */
111
117
  skills: string[];
@@ -363,6 +369,7 @@ export declare const sparkDataPartSchemas: {
363
369
  }, z.core.$strip>;
364
370
  readonly compaction: z.ZodObject<{
365
371
  firstKeptMessageId: z.ZodString;
372
+ firstKeptStep: z.ZodOptional<z.ZodNumber>;
366
373
  summary: z.ZodString;
367
374
  skills: z.ZodArray<z.ZodString>;
368
375
  modelId: z.ZodString;
package/dist/ui.js CHANGED
@@ -88,6 +88,7 @@ export const sparkDataPartSchemas = {
88
88
  selection: z.object({ nodeIds: z.array(z.string().min(1).max(64)).min(1).max(8192) }),
89
89
  compaction: z.object({
90
90
  firstKeptMessageId: z.string().min(1),
91
+ firstKeptStep: z.number().int().positive().optional(),
91
92
  summary: z.string().min(1),
92
93
  skills: z.array(z.string().min(1)),
93
94
  modelId: z.string(),
@@ -1,4 +1,4 @@
1
- import type { FontFamily, KakapoAPI, KakapoNode, NodeKind, SceneEdit, Vec2, Vec3 } from "@womp/kakapo-sdk";
1
+ import type { BoundingBox, FontFamily, KakapoAPI, KakapoNode, NodeKind, SceneEdit, Vec2, Vec3 } from "@womp/kakapo-sdk";
2
2
  import { type SceneHdr } from "./scene-environment.js";
3
3
  import { type MirrorAxis } from "./coordinates.js";
4
4
  export declare const NodeType: {
@@ -81,6 +81,14 @@ export type WompSceneApiContext = {
81
81
  };
82
82
  export type WompSceneApi = ReturnType<typeof createWompSceneApi>;
83
83
  export declare function sceneNodeType(kind: NodeKind): number;
84
+ export declare function convertAABB(box: BoundingBox, unitMultiplier: number): AABB;
85
+ export type SceneNodeReader = {
86
+ requireNode(id: number): KakapoNode;
87
+ getNode(id: number): KakapoNode;
88
+ };
89
+ export declare function sceneNodePosition(reader: SceneNodeReader, rootId: number, unitMultiplier: number, id: number): Vec3;
90
+ export declare function sceneNodeRotation(reader: SceneNodeReader, id: number): Vec3;
91
+ export declare function sceneNodeSize(reader: SceneNodeReader, rootId: number, unitMultiplier: number, id: number): Vec3;
84
92
  /** Align local +Z toward target. Both points are in the same parent-local coordinates. */
85
93
  declare function lookAt(origin: Vec3, target: Vec3): Vec3;
86
94
  /** XYZ degrees aligning the selected positive local axis to a parent-local direction. */